210 Commits

Author SHA1 Message Date
Vassyli 2e9f0dc564 Sets cli column width to 120 to standardise testing environment 2021-02-16 22:13:52 +01:00
Vassyli 03fde83fc1 Fixes the actual command to import the correct commands. 2021-02-16 22:13:52 +01:00
Vassyli 669085e65d Added the possibilities to set character and scene settings over cli, with module hooks. 2021-02-16 22:13:52 +01:00
Vassyli f72adb1a38 Adds tests for scene commands. 2021-02-16 22:13:52 +01:00
Vassyli 0292a66252 Adds new commands to manage modules and corresponding tests. 2021-02-16 22:13:52 +01:00
Vassyli eea72f848b Adds tests for all character commands. 2021-02-16 22:13:52 +01:00
Vassyli c2a139d192 Adds a new CharacterEditCommand including a test. 2021-02-16 22:13:52 +01:00
Vassyli d83e437b4a Removed the same wrong assumption from LotGDTestCase, too. 2021-02-04 16:37:07 +01:00
Vassyli 99d554e5d7 Fixes a bug in HasAction->searchAction that caused actions not to be found if no groupTitle was given. 2021-02-04 16:37:07 +01:00
Vassyli 75dd7494c3 Fixes a bug in Action->setTitle() where a return type was expected instead of void. 2021-02-04 12:03:43 +01:00
Vassyli f7504bbb60 Adds a test and the corresponding bugfix. 2021-02-03 20:15:00 +01:00
Vassyli 60a3a8bd2b Adds a trait 'UserAssignable' to mark models as user-assignable. Refactored from SceneTemplates, and applied to SceneAttachments, too. 2021-01-29 20:16:51 +01:00
Vassyli 147692d722 Removes the ability of attachments to add actions. This should be done by modules via scene templates instead. 2021-01-29 20:16:51 +01:00
Vassyli 454248ed0f Game loop now creates attachment instances from SceneAttachments. 2021-01-29 20:16:51 +01:00
Vassyli 27347d0677 Changed SceneAttachment to require the class to implement AttachmentInterface. 2021-01-29 20:16:51 +01:00
Vassyli 69434c7bbf Adds an AttachmentInterface and adjusted the abstract class Attachment. 2021-01-29 20:16:51 +01:00
Vassyli 2a3a3e7bc2 Introduces a SceneAttachment model for registering attachments. 2021-01-29 20:16:51 +01:00
Vassyli 47693f6127 Adds commands to add, show and remove characters. 2021-01-26 21:19:12 +01:00
Vassyli e3534ea88d Adds logs to scene commands. 2021-01-26 21:19:12 +01:00
Vassyli 5dbe8fdca3 Regression fix: Adds composer requirement for d11wtq/boris back in. 2021-01-26 21:19:12 +01:00
Vassyli 31387c1840 Adds a command to remove scenes. 2021-01-26 21:19:12 +01:00
Vassyli dfd39cd1f8 Adds a feature to scene:show command that shows the corresponding group name in the connections. 2021-01-26 21:19:12 +01:00
Vassyli f25bab7af8 Adds commands to add and remove scene connection groups. 2021-01-26 21:19:12 +01:00
Vassyli c0ed663ef4 Adds the possibility to disconnect two scenes. 2021-01-26 21:19:12 +01:00
Vassyli b3b1c4af0a Adds a SceneConnect command to connect two scenes. 2021-01-26 21:19:12 +01:00
Vassyli 2153df5cc8 Small fix to the SceneAdd command. 2021-01-26 21:19:12 +01:00
Vassyli 13dfab2321 Adds improvements to the SceneTemplate object, and integrates them into the console command. 2021-01-26 21:19:12 +01:00
Vassyli 29ad369c88 Adds some additional commands to denerys cli. 2021-01-26 21:19:12 +01:00
Vassyli a576bfd2a0 Moved console commands to subdirectories. 2021-01-26 21:19:12 +01:00
Vassyli aa5b32c03d Fixes typed property to pass tests. 2021-01-18 10:24:17 +01:00
Vassyli c2aaf025f6 Adds a method AddAttachment to the viewpoint. Small changes. 2021-01-18 10:24:17 +01:00
Vassyli 9b60cc0ebb Connects the SceneRenderer to the Game bootstrap process, and parses Scenes in Viewpoint->changeFromScene(). 2021-01-16 18:55:49 +01:00
Vassyli eca1343b5f Adds events to TwigSceneRenderer, and tests to show that they correctly overwrite the data. 2021-01-16 18:55:49 +01:00
Vassyli 1fb2eb023e Adds SceneRenderer class based on twig to convert some logic and variables from Scenes, allowing them to be more flexible. 2021-01-16 18:55:49 +01:00
Vassyli 1a0b6dbe3e Adds tests to check scene properties. 2021-01-14 20:52:19 +01:00
Vassyli 96feb12fb5 Adds the ability to set properties on Scene objects. 2021-01-14 20:52:19 +01:00
Vassyli 512d875609 Updates some documentation in the Game object. 2021-01-14 20:52:19 +01:00
Vassyli 0a2313b5e5 Fixes a bug where phpunit marked tests only running our custom assertions as risky. 2021-01-13 19:37:08 +01:00
Vassyli c4634b90b7 Adds a new common LotGDTestCase providing additional assertions. Adds a HasAction constraint, too. 2021-01-08 20:00:25 +01:00
Vassyli 6325b8bd36 Adds a git add command to the update package workflow. 2021-01-04 19:40:12 +01:00
Vassyli dd074a8bea Fixed a bug that used a misspelled stdout stream for StreamHandler 2021-01-04 19:14:23 +01:00
Vassyli fdb53972d2 Removed travis configuration file 2020-12-31 11:37:50 +01:00
Vassyli ad3ea14bb2 Exchanged the travis badge with a github action badge. 2020-12-31 11:34:08 +01:00
Vassyli bb1ca987b5 Renames test-runner to reflect tests 2020-12-31 11:30:07 +01:00
Vassyli 57b8045c1a Updated composer.json to pass composer validate. 2020-12-31 11:15:27 +01:00
Vassyli f658b421f6 Added a new testrunner github workflow, and changed the update-package to also act on releases. 2020-12-31 10:52:07 +01:00
Vassyli 7ab55c1cb0 Bugfix 2020-12-30 18:11:20 +01:00
Vassyli 05d72a11bb Bugfix 2020-12-30 18:05:05 +01:00
Vassyli d006aa3ded Bugfix 2020-12-30 17:59:05 +01:00
Vassyli 147d449a27 Adds additional credentials for pushing 2020-12-30 17:55:08 +01:00
Vassyli c18cdef3ff Fixes the commit-changes part which wrongly called composer. 2020-12-30 17:34:09 +01:00
Vassyli 3eb538145e Adds github token to composer config. 2020-12-30 17:29:29 +01:00
Vassyli 696a5f3305 Adds authentication steps to github workflow 2020-12-30 17:23:15 +01:00
Vassyli 765b65a9d2 Sets PHP version to 7.4 in worker 2020-12-30 15:07:12 +01:00
Vassyli 96afd4b87e Updated workflow 2020-12-30 15:03:15 +01:00
Vassyli ea6be63bb3 Adds a test github workflow. 2020-12-30 14:58:39 +01:00
Vassyli 126fdee80e Adds additional methods to ModelTestCase for modules to refine their tests. 2020-12-30 14:28:53 +01:00
Vassyli 81c72ca91a Adds an assertDataWasKeptIntact method to ModelTestCase in lieu of the DBUnit features. 2020-12-30 14:11:07 +01:00
Vassyli 29e5ced0f9 Adds a test for null-template scenes. 2020-12-28 13:40:19 +01:00
Vassyli c7dbf33f6e Adds missing return in CharacterResetViewpointCommand 2020-12-26 15:22:57 +01:00
Vassyli d4124a71ef Adds additional typehints to LotGD\Core\Tools\* 2020-12-24 12:47:28 +01:00
Vassyli 28c12a47f1 Collection of fixes to make tests run. 2020-12-24 12:36:26 +01:00
Vassyli 709b96ea52 Adds additional typehints to LotGD\Core\Events\* 2020-12-24 12:20:56 +01:00
Vassyli 363c4678d3 Added typehints do \LotGD\Core\Doctrine\* 2020-12-24 10:00:40 +01:00
Vassyli d8b8fb69d9 Adjusted typehints in LotGD\Core\PermissionManager 2020-12-24 10:00:39 +01:00
Vassyli 0a20c8a968 Adjusted typehints in LotGD\Core\LibraryConfigurationManager 2020-12-24 10:00:37 +01:00
Vassyli f051d72aea Adjusted typehints in LotGD\Core\LibraryConfiguration 2020-12-24 10:00:35 +01:00
Vassyli af17f434d4 Adjusted typehints in LotGD\Core\GameBuilder 2020-12-24 10:00:33 +01:00
Vassyli 2680a41cee Adjusted typehints in LotGD\Core\EventManager 2020-12-24 10:00:32 +01:00
Vassyli c7d91230bd Adjusted typehints in LotGD\Core\DiceBag 2020-12-24 10:00:30 +01:00
Vassyli 541748a168 Adjusted typehints in LotGD\Core\Configuration 2020-12-24 10:00:29 +01:00
Vassyli 42e3f22726 Adjusted typehints in LotGD\Core\ComposerManager 2020-12-24 10:00:27 +01:00
Vassyli 3c64492cde Adjusted typehints in LotGD\Core\BuffList 2020-12-23 16:38:28 +01:00
Vassyli 18ec30ede5 Added default values to Character and TimeKeeper in LotGD\Core\Game to prevent access of uninitialized properties. 2020-12-23 16:29:48 +01:00
Vassyli 4e35fbd6b3 Adjusted typehints in LotGD\Core\Battle 2020-12-23 16:09:48 +01:00
Vassyli 32305238d9 Added typehints to LotGD\Core\Attachment 2020-12-23 15:59:11 +01:00
Vassyli 13082f7b9d Added typehints to LotGD\Core\ActionGroup 2020-12-23 15:58:05 +01:00
Vassyli 272910c3a3 Updated typehints in LotGD\Core\Action 2020-12-23 15:55:58 +01:00
Vassyli 33a7812abe Adds constructor property promotion to Game.php 2020-12-23 15:55:58 +01:00
Vassyli a086fe4b9b Adds property TypeHints to Game 2020-12-23 15:55:51 +01:00
Vassyli 557215bba3 Temporarily removes php-cs-fixer as it does not support PHP8 yet 2020-12-23 13:32:49 +01:00
Vassyli 81a6edc074 Merge branch 'master' of https://github.com/lotgd/core into php8-compability 2020-12-23 12:44:41 +01:00
Vassyli 1cfe1cacba Changed travis to only check for php 8.0 2020-12-23 12:41:52 +01:00
Vassyli f3fee8cc7d Adds a to each console command->execute() 2020-12-21 20:30:05 +01:00
Vassyli 90f187dbf2 Doctrine: Fixed class reference to ClassMetadata 2020-12-21 19:21:25 +01:00
Vassyli 1a6079b270 Monolog: Fixed logging calls from addDebug() to debug() 2020-12-21 18:40:31 +01:00
Vassyli 17b704bec4 Updated packages to newest versions. 2020-12-21 18:12:16 +01:00
Basilius Sauter f6df590cd6 Sets SceneTemplate->userAssignable to true by default. 2019-06-29 12:41:56 +02:00
Basilius Sauter 3338a75f71 Removed PHP 7.4 snapshot. 2019-06-29 12:40:10 +02:00
Basilius Sauter 7fe81a9c5b Fixed a bug in SceneTemplate::__construct that prevented any legal classes to be used. 2019-06-29 12:24:16 +02:00
Basilius Sauter 3ecb7f8e95 Changed a few typehints. 2019-06-27 15:05:34 +02:00
Basilius Sauter 0fcc6dc8b7 Automatic PHP CS Fixer run. 2019-06-27 14:39:03 +02:00
Basilius Sauter 2376696101 Fixed errors related to rebasing. 2019-06-27 14:39:03 +02:00
Basilius Sauter a52de33ed7 Added snapshot back in. 2019-06-27 14:39:03 +02:00
Basilius Sauter cf99395ed3 Removed php 7.4.snapshot from travis due to issues with ReflectionType. 2019-06-27 14:39:03 +02:00
Basilius Sauter 1c53604244 Adjusted tests. 2019-06-27 14:39:03 +02:00
Basilius Sauter c6ecab6ce9 Adds SceneTemplate model and connects it to SceneBasics trait. 2019-06-27 14:39:03 +02:00
Basilius Sauter ff154ccbd9 Merge pull request #129 from lotgd/rebase/phpunit8
Rebase/phpunit8
2019-06-27 14:36:56 +02:00
Basilius Sauter fc73f86ffa Changed PHPUnit dependency to use either 7 or 8. 2019-06-27 14:29:01 +02:00
Basilius Sauter a375cb98cd Removed all Warnings from PHPUnit 8 2019-06-27 09:52:18 +02:00
Basilius Sauter e18cdd21cc Added void return type to setUp/tearDown test methods. 2019-06-27 09:40:12 +02:00
Basilius Sauter c4ff330928 Adds in PHP 7.3 to travis 2019-06-25 20:44:24 +02:00
Basilius Sauter e74dcbdfde Automatic changes from 'php cs fixer' 2019-06-25 20:42:44 +02:00
Basilius Sauter 8612a53538 Replaces style checker with PHP CS Fix 2019-06-25 20:23:22 +02:00
Basilius Sauter e8f2ea56bc Upgrades successfully to PHPUnit 7, removed DBUnit support and integrated custom testing. 2019-06-16 17:54:00 +02:00
Basilius Sauter d0b2d48cc1 Upgrades successfully to PHPUnit 7, removed DBUnit support and integrated custom testing. 2019-06-16 17:50:58 +02:00
Basilius Sauter 14d1c9d582 Adds method getConnectionGroup to Scene model 2019-04-23 14:40:54 +02:00
Basilius Sauter 7725c7faed Adds methods setTitle and setName to SceneConnectionGroup 2019-04-23 14:35:04 +02:00
Basilius Sauter 4ca81a3789 Fixed some class documentation. 2019-04-05 13:19:33 +02:00
Basilius Sauter 353c4251c4 Allows the modulemanager to return null instead of the ModuleModel 2019-04-03 17:25:21 +02:00
Basilius Sauter 248258a8fe Adds character stats and tests. 2019-04-03 16:17:36 +02:00
Basilius Sauter 4d1ab9e763 Changed quote strategy to BasicQuoteStrategy as AnsiQuoteStrategy seems to mess up the column names. 2019-04-02 21:12:02 +02:00
Basilius Sauter d7229858cc Adds command to list all characters, and to reset a given characters viewpoint. 2019-04-01 16:50:18 +02:00
Basilius Sauter 14b1db8b82 Changes command module:register to resolve dependencies first. 2019-04-01 16:50:14 +02:00
Basilius Sauter d7100deb0f Merge branch 'master' of https://github.com/lotgd/core into feature/symfony4 2019-03-22 16:26:53 +01:00
Basilius Sauter e260a1d661 Added missing doc block 2019-03-22 16:23:50 +01:00
Basilius Sauter 2ff8d647e8 Changed a small bug causing actions to not have any titles. 2019-03-22 16:23:50 +01:00
Basilius Sauter 8d9a186b46 Small changed to documentation. 2019-03-22 16:23:50 +01:00
Basilius Sauter 8f69527764 Character model: added default parameters for name and displayName. 2019-03-22 16:23:50 +01:00
Basilius Sauter 684f86aed6 Added missing use statements in character model for doctrine annotations. 2019-03-22 16:23:50 +01:00
Basilius Sauter 55481c30b1 Adds schema-update daenerys command. 2019-03-22 16:23:50 +01:00
Basilius Sauter 6b383be1cd Added config to disable automatic database schema update. 2019-03-22 16:23:50 +01:00
Basilius Sauter d609f981aa Changed doctrine dependency 2019-03-22 16:23:50 +01:00
Basilius Sauter 4ec84dfec3 Changed dependency to symfony 4 2019-03-22 16:23:50 +01:00
Basilius Sauter 049d116028 Updated version identifier for 0.5 2018-10-10 10:12:59 +02:00
Basilius Sauter aa63259992 Updated version identifier for 0.5 2018-10-10 10:10:38 +02:00
Basilius Sauter a097c29d67 Added tests to ensure ModuleManager::register does not change anything upon error. 2018-10-08 17:29:44 +02:00
Basilius Sauter bab3e0f236 Changed viewpoint to use uuid-based primary key 2018-10-08 17:29:44 +02:00
Basilius Sauter 2f89bbc7e3 Changed buff to use uuid. 2018-10-08 17:29:44 +02:00
Basilius Sauter 6e2ba248c4 Small changes. 2018-10-08 17:29:44 +02:00
Basilius Sauter b398ffae14 Changed motd id to uuid 2018-10-08 17:29:44 +02:00
Basilius Sauter 90971d152a Changed motd id to uuid 2018-10-08 17:29:44 +02:00
Basilius Sauter 04b3b6aaf9 Changed character id to uuid 2018-10-08 17:29:44 +02:00
Vassyli 0aaba1b94b Fixes inheritance issue with CharacterRepository 2018-10-08 17:28:16 +02:00
Vassyli 2affd4803f Added missing DocBlocks 2018-10-08 17:26:44 +02:00
Vassyli c07f7b3342 Removed auto-flushing for every event from EventManager. 2018-10-08 17:26:44 +02:00
Vassyli 8a75e81431 Updated failing tests 2018-10-08 17:26:44 +02:00
Vassyli ccfb432e4d ActionGroup now implements Countable interface. 2018-04-12 19:49:10 +02:00
Vassyli 2a0bf5f038 Fixes inheritance issue with CharacterRepository 2018-04-12 19:40:57 +02:00
Vassyli 4248dad033 Hotfix for re-enabling tests. Forces to use phpunit dependency. 2018-04-12 14:43:30 +02:00
Katrina Swales b89f6d7b0c Merge branch 'master' of github.com:nekosune/core 2018-04-09 17:02:18 +01:00
nekosune 6c4b1e15f4 Made changes requested, and fixed tests 2018-04-09 16:01:37 +00:00
nekosune ddb7ae08a6 Made requested changes 2018-04-09 15:39:36 +00:00
nekosune 8ba04dade4 Added missing docblock 2018-04-07 19:24:16 +00:00
nekosune 70243f4662 Moved Message sending to Message Manager as per issue #28 2018-04-07 19:12:13 +00:00
Austen McDonald 77b33d7385 Correct typo in comment (#113 from Cousjava/typofix)
Correct typo in comment
2018-03-15 12:52:31 -07:00
Cousjava db49e63c99 Merge branch 'master' into typofix 2018-03-15 19:46:37 +00:00
Cousjava d90e4d0ba9 Correct typo in comment
Closes lotgd/core#108
2018-03-15 19:45:45 +00:00
Vassyli ccbfa0553c Replaced diceBag()->normal with diceBag()->pseudoBell, added a few fight fixes. 2018-01-27 19:33:59 +01:00
Vassyli 04a5b59ea2 Fixed an issue with PropertyManager. 2018-01-26 16:21:53 +01:00
Vassyli 846ab6018e LibraryConfiguration now also knows crate packages. 2018-01-19 17:42:33 +01:00
Vassyli efb333d08a Adds missing doc block 2018-01-10 08:27:56 +01:00
Vassyli 92c0f71ed6 Small clean-ups 2018-01-10 08:22:28 +01:00
Vassyli c9e6f679c4 Adds events to character->getAttack and getDefense to modify the value. Tests included. 2018-01-09 17:16:54 +01:00
Vassyli 17ebdbdbe5 Redesign EventData creation 2018-01-09 09:43:08 +01:00
Vassyli 55b821c8e8 Removed game dependency from FighterInterface->getAttack and getDefense 2018-01-09 09:25:40 +01:00
Vassyli 45a785a8f5 Adds documentation. 2018-01-09 09:04:04 +01:00
Vassyli ff713ac333 Adds possibility to extend certain models using annotations. 2018-01-09 09:04:04 +01:00
Vassyli aba0d53a68 postLoad eventListener adds Game object to GameAware entities. 2018-01-09 09:04:04 +01:00
Vassyli 9ddd16b4e8 Adds suggestion plus a few additional tests 2018-01-09 08:59:58 +01:00
Vassyli c0edd3ac67 Adds php 7.2 to travis 2018-01-05 10:30:59 +01:00
Vassyli 81d773720a Removes master 2018-01-05 10:30:21 +01:00
Vassyli 5ac7098f35 Adds documentation to PropertyManager trait. 2018-01-04 14:13:56 +01:00
Vassyli 41db0ddfda Adds a CharacterEventData class for events that are only character related. 2018-01-04 13:02:57 +01:00
Vassyli 003a6517ba Updated dependencies 2017-12-25 14:59:55 +01:00
Vassyli af6a6cbff0 Adds a ViewpointDecorationEventData class for hooks that want to provide a specific hook for scene manipulation. 2017-12-25 14:46:39 +01:00
Vassyli 56c80e3f8d fixup! ViewpointDescription now ignores empty lines 2017-09-27 13:22:00 +02:00
Vassyli 4b82ee4b89 ViewpointDescription now ignores empty lines 2017-09-27 13:17:26 +02:00
Vassyli 829d63d7f6 Fixes BattleEvents to accept correct game 2017-09-27 12:20:58 +02:00
Vassyli 94763f8d6e Adds the possibility to serialize a battle state for saving it 2017-09-22 13:37:54 +02:00
Vassyli a287313f6f Adds a normal dice to the dice bag 2017-09-22 12:10:11 +02:00
Vassyli 51a102f981 Changed BasicEnemy properties from private to protected in order to allow inheritance 2017-09-19 12:48:02 +02:00
Vassyli 9842fa9ace Added additional api calls to manage action groups 2017-09-18 11:36:27 +02:00
Vassyli adf4eeac5e Adds documentation 2017-09-13 18:32:14 +02:00
Vassyli 533378d006 Adds the foundation for viewpoint to be able to modify there description more easily 2017-09-13 18:24:37 +02:00
Vassyli 867843dddd Adds a GameBuilder to allow better dependency injection 2017-06-23 14:43:24 +02:00
Vassyli 329430c547 Fixed test 2017-06-12 16:00:44 +02:00
Vassyli d20a59e68a Updated composer; Added optional action title 2017-06-12 15:58:51 +02:00
Vassyli 29e474b9c1 Adds possibility to add parameters to actions. 2017-06-12 14:58:40 +02:00
Vassyli bbc960fd3d Added suggested change
Fixes #95
2017-04-25 22:31:37 +02:00
Vassyli 7e58c72526 Changes implemented to pass test. 2017-04-24 20:52:34 +02:00
Vassyli 1eeca4ef9e Adds failing test 2017-04-18 23:55:52 +02:00
Vassyli 201a3a032f Implemented suggested changes.
Fixes #94
2017-04-10 09:42:46 +02:00
Vassyli a790eab5ee Added documentation. 2017-04-10 09:42:46 +02:00
Vassyli e6e9e6e102 Added EventContextData containers. 2017-04-10 09:42:46 +02:00
Vassyli 214b1de95f Adds EventContextDataContainer 2017-04-10 09:42:46 +02:00
Vassyli f5380de501 Adds EventContext 2017-04-10 09:42:46 +02:00
Vassyli 70d29f67b8 Moved TimeKeeper.now to constructor.
Breaks BC intentionally.
2017-04-10 09:24:18 +02:00
Vassyli a33473d435 Add possibility to give gameOffsetSeconds also as a negative number 2017-04-10 09:24:18 +02:00
Vassyli d126b0207f Add tests for isNewDay 2017-04-10 09:24:18 +02:00
Vassyli f201784291 Changed TimeKeeper to keep a permanent "now" state. 2017-04-10 09:24:18 +02:00
Vassyli 39b9ec318a Changed isNewDay to accept null instead of DateTime
Fixes #93
2017-04-10 09:24:18 +02:00
Vassyli 5668c08f45 Renamed ViewpointRestorationPoint to ViewpointSnapshot 2017-03-31 08:55:00 +02:00
Vassyli a739aed94a Added simple test and fixed a bug uncovered by it. 2017-03-27 09:52:09 +02:00
Vassyli d408aa0755 Fixed Typo 2017-03-24 16:32:38 +01:00
Vassyli af98ab0f36 Added viewpoint restoration points
Added an API for model viewpoint to create a restoration point that can be saved. Changing the scene from the restoration point can replay a scene without doing the calculations done to render it.
2017-03-18 16:13:23 +01:00
Vassyli 03fc114775 Revert "Fixs a weird bug causing the deletion of scens to NOT cascade, despite passing tests."
This reverts commit 848e6b022c.
2017-03-13 14:13:50 +01:00
Vassyli 848e6b022c Fixs a weird bug causing the deletion of scens to NOT cascade, despite passing tests. 2017-03-12 19:39:44 +01:00
Vassyli 5c3fd4714d Adds fixes and tests for cascade=persist, remove for scene entities.
It still looks like doctrine doesn't "know" about the column names in a cascade=remove relationship and assumes the property name to be also the column name - which is usually not true (by default, it's propertyname_id).

This update changes the column name so that doctrine's assumptions are correct again and adds tests so any changes which invalidates this relationship can be gecocnized easily.
2017-03-11 12:51:25 +01:00
Vassyli 498f4965e6 Adds tests and support for Unidirectional connections.
Also fixes travis config.
2017-03-02 20:04:27 +01:00
Vassyli 2970bd09d7 Changed the scene parent<=>child relationship to connections.
The parent<=>child relationship of scenes was removed. Instead, this commit introduces the concept of a connection.

A connection is used to connect two scenes. Depending on which entity the connect-method is called, one is defined as the "outgoing" scene, the other as the ingoing scene:

```
$a->connect($b);
```

In this case, $a will be the outgoing part of the connection, $b the incoming.

Furthermore, in order to support action groups, this commit introduces SceneConnectionGroups which can be created in scenes and checked wether they exist or not. Using these, it is possible to specifiy to which part of the scenes are connected to each other.

```
$a->getConnectionGroup("scene-A/marketsquare")->connect($b);
```

In this case, $a will have the action to access $b under the ActionGroup of scene-A/marketsquare. On the other hand, $b, which doesn't have a connection group specified, will have the connection back to $a in the default group.

Connect also accepts the return value of getConnectionGroup as the argument, thus allowing the connection _to_ a certain part of $b as well:

```
$a->connect($b->getConnectionGroup("scene-B/back"));
```

The tests for scenes were updates in order to reflect this change.
2017-03-02 17:19:46 +01:00
Vassyli e82e72a183 Abstract actor model has more straightforward API
The implicit API requirements via class properties has been changed to relay now on abstract methods that the extending class must implement.
2017-01-19 10:18:27 +01:00
Vassyli 9ecd0ddc58 Applied suggested changes 2017-01-19 10:02:31 +01:00
Vassyli 64cb22d3c0 Replaced Permissionable/-Interface with an abstract Actor class. 2017-01-19 10:02:31 +01:00
Vassyli f8057077bc Adds logging to permission manager and requires an actor to return an actor name. 2017-01-19 10:02:31 +01:00
Vassyli 1c89d8f204 Adds removal of exceptions as well as error handling. 2017-01-19 10:02:31 +01:00
Vassyli 3b8537fab6 Adds methods to read and check permissions. 2017-01-19 10:02:31 +01:00
Vassyli 3bf23f3ac7 Adds Permission model and framework for testing permission manager. 2017-01-19 10:02:31 +01:00
Basilius Sauter 94e18b8d11 Increases windows compability by removing microtime from tests 2017-01-04 08:26:10 +01:00
Basilius Sauter b8f47c6d53 Fixed composer creation to account completely for cwd
Also added tests that fail if not.
2017-01-04 08:23:32 +01:00
264 changed files with 18288 additions and 2757 deletions
+39
View File
@@ -0,0 +1,39 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run test suite
run: vendor/bin/phpunit --stop-on-failure
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
# - name: Run test suite
# run: composer run-script test
@@ -0,0 +1,39 @@
name: Package push
on:
push:
branches: [ master ]
release:
types: [ created ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup PHP with PECL extension
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
- name: Set git credentials
run: git config --global user.name "Vassyli" && git config --global user.email "basilius.sauter+automated@gmail.com" && git config --global user.password "${{ secrets.GH_TOKEN }}"
- name: Set composer token
run: composer config -g github-oauth.github.com ${{ secrets.GH_TOKEN }}
- name: "Checkouts satis and packages"
run: git clone https://github.com/composer/satis && git clone https://github.com/lotgd/packages
- name: "Install satis"
run: cd satis && composer install && cd ..
- name: "Build satis"
run: satis/bin/satis build packages/satis.json packages/build
- name: Set git url with credentials for pushing
run: cd packages && git config remote.origin.url 'https://Vassyli:${{ secrets.GH_TOKEN }}@github.com/lotgd/packages.git' && cd ..
- name: "Commit changes and push to origin"
run: cd packages && git add build/* && git commit -a -m "Updated packages" && git push origin master && cd ..
+6
View File
@@ -1,5 +1,6 @@
### Project related
vendor/
.idea/
logs/*
@@ -227,3 +228,8 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
dbconfig.php
composer.phar
clover.xml
.phpunit.result.cache
.php_cs.cache
+142
View File
@@ -0,0 +1,142 @@
<?php
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
'align_multiline_comment' => true,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => true,
'blank_line_after_namespace' => true,
'braces' => [
'position_after_control_structures' => 'same',
'position_after_functions_and_oop_constructs' => 'next',
],
'cast_spaces' => ['space' => 'none'],
'class_definition' => true,
'class_keyword_remove' => false,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'compact_nullable_typehint' => true,
'declare_strict_types' => true,
'declare_equal_normalize' => true,
'dir_constant' => true,
'doctrine_annotation_array_assignment' => true,
'doctrine_annotation_braces' => true,
'doctrine_annotation_indentation' => true,
'doctrine_annotation_spaces' => [
'after_array_assignments_colon' => false,
'after_array_assignments_equals' => false,
'before_argument_assignments' => false,
'before_array_assignments_colon' => false,
'before_array_assignments_equals' => false,
],
'elseif' => true,
'encoding' => true,
'ereg_to_preg' => true,
'escape_implicit_backslashes' => true,
'explicit_indirect_variable' => true,
'explicit_string_variable' => true,
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => true,
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => true,
'indentation_type' => true,
'line_ending' => true,
'linebreak_after_opening_tag' => true,
'list_syntax' => ['syntax' => 'short'],
'lowercase_cast' => true,
'lowercase_constants' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'method_argument_space' => true,
'method_chaining_indentation' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'],
'native_constant_invocation' => true,
'native_function_invocation' => true,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => true,
'no_alternative_syntax' => true,
'no_blank_lines_after_phpdoc' => true,
'no_break_comment' => true,
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ["use" => "print"],
'no_multiline_whitespace_around_double_arrow' => true,
'no_php4_constructor' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => true,
'no_spaces_inside_parenthesis' => true,
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true,
'no_unreachable_default_argument_value' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'non_printable_character' => true,
'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
'ordered_imports' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_indent' => true,
'phpdoc_no_access' => true,
'phpdoc_no_alias_tag' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => true,
'phpdoc_to_return_type' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
'phpdoc_types' => true,
'phpdoc_types_order' => ['null_adjustment' => 'always_last'],
'phpdoc_var_annotation_correct_order' => true,
'pow_to_exponentiation' => true,
'psr4' => true,
'random_api_migration' => true,
'return_type_declaration' => true,
'self_accessor' => true,
'semicolon_after_instruction' => true,
'set_type_to_cast' => true,
'short_scalar_cast' => true,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_import_per_statement' => true,
'single_line_after_imports' => true,
'standardize_not_equals' => true,
'standardize_increment' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => false,
'unary_operator_spaces' => true,
'visibility_required' => true,
'whitespace_after_comma_in_array' => true,
'yoda_style' => false,
])
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__."/src")
);
-8
View File
@@ -1,8 +0,0 @@
sudo: false
language: php
php:
- '7.0'
install:
- composer install
script:
- ./t
+1 -1
View File
@@ -1,6 +1,6 @@
# Legend of the Green Dragon (Core)
[![Build Status](https://travis-ci.org/lotgd/core.svg?branch=master)](https://travis-ci.org/lotgd/core)
![Tests](https://github.com/lotgd/core/workflows/Tests/badge.svg)
Legend of the Green Dragon is a text-based RPG originally developed by Eric Stevens and JT Traub as a remake of and homage to the classic BBS Door game,
Legend of the Red Dragon, by Seth Able Robinson. You can play it at numerous sites, including http://www.lotgd.net/.
+15 -10
View File
@@ -1,7 +1,7 @@
{
"name": "lotgd/core",
"description": "Core functionality for Legend of the Green Dragon, a text-based RPG game.",
"license": "AGPL-3.0",
"license": "AGPL-3.0-or-later",
"autoload": {
"psr-4": {
"LotGD\\Core\\": "src/",
@@ -12,13 +12,18 @@
"bin/daenerys"
],
"require": {
"php": "^7.0.0",
"composer/composer": "*",
"gedmo/doctrine-extensions": "*",
"doctrine/orm": "^2.5",
"monolog/monolog": "^1.12",
"symfony/console": "^3.0",
"symfony/yaml": "^3.0",
"php": "^8.0",
"ext-pdo": "*",
"composer/composer": "^1.10|^2.0",
"gedmo/doctrine-extensions": "^2.3|^3.0",
"doctrine/orm": "^2.8",
"doctrine/common": "^3.0",
"monolog/monolog": "^2.0",
"symfony/console": "^5.0",
"symfony/yaml": "^5.0",
"twig/twig": "^3.0",
"ramsey/uuid-doctrine": "^1.5",
"jetbrains/phpstorm-attributes": "^1.0",
"d11wtq/boris": "^1.0"
},
"repositories": [
@@ -30,7 +35,7 @@
],
"require-dev": {
"phpunit/phpunit": "*",
"phpunit/dbunit": "*",
"block8/php-docblock-checker": "2.0.0"
"phpunit/php-code-coverage": "*",
"friendsofphp/php-cs-fixer": "*"
}
}
Generated
+4043 -892
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -3,6 +3,7 @@ database:
name: daenerys
user: root
password:
disableAutoSchemaUpdate: false
game:
epoch: 2016-07-01 00:00:00.0 -8
offsetSeconds: 0
+1
View File
@@ -1,6 +1,7 @@
<phpunit bootstrap="bootstrap/bootstrap.php">
<php>
<env name="LOTGD_TESTS_CONFIG_PATH" value="./config/lotgd.yml"/>
<env name="COLUMNS" value="120" />
</php>
<testsuites>
<testsuite name="All">
+45 -8
View File
@@ -9,17 +9,20 @@ namespace LotGD\Core;
*/
class Action
{
protected $id;
protected $destinationSceneId;
protected string $id;
/**
* Construct a new action with the specified Scene as its destination.
* @param int $destinationSceneId
* @param string $destinationSceneId
* @param string|null $title
* @param array $parameters
*/
public function __construct(int $destinationSceneId)
{
$this->id = bin2hex(random_bytes(8));
$this->destinationSceneId = $destinationSceneId;
public function __construct(
protected string $destinationSceneId,
protected ?string $title = null,
protected array $parameters = []
) {
$this->id = \bin2hex(\random_bytes(8));
}
/**
@@ -37,8 +40,42 @@ class Action
* go if they take this action.
* @return string
*/
public function getDestinationSceneId(): int
public function getDestinationSceneId(): string
{
return $this->destinationSceneId;
}
/**
* @return string|null
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* @param string|null $title
*/
public function setTitle(?string $title): void
{
$this->title = $title;
}
/**
* Returns all parameters for this action.
* @return array
*/
public function getParameters(): array
{
return $this->parameters;
}
/**
* Sets all parameters for this action.
* @param array $parameters
*/
public function setParameters(array $parameters): void
{
$this->parameters = $parameters;
}
}
+27 -12
View File
@@ -1,19 +1,16 @@
<?php
declare (strict_types=1);
declare(strict_types=1);
namespace LotGD\Core;
/**
* A grouping of navigation actions, like a submenu.
*/
class ActionGroup
class ActionGroup implements \Countable
{
const DefaultGroup = 'lotgd/core/default';
const HiddenGroup = 'lotgd/core/hidden';
private $id;
private $title;
private $sortKey;
private $actions;
/**
@@ -22,14 +19,23 @@ class ActionGroup
* @param string $title Title to display to the end user. Empty string means no title.
* @param int $sortKey Navigation menus are displayed in the order sorted by this integer.
*/
public function __construct(string $id, string $title, int $sortKey)
{
$this->id = $id;
$this->title = $title;
$this->sortKey = $sortKey;
public function __construct(
private string $id,
private string $title,
private int $sortKey
) {
$this->actions = [];
}
/**
* Returns the number of registered Actions for this group.
* @return int
*/
public function count(): int
{
return \count($this->actions);
}
/**
* Returns the unique identifier for this group, in the vendor/module/group format.
* @return string
@@ -61,7 +67,7 @@ class ActionGroup
/**
* Returns the ordered array of actions.
* @return array<Action>
* @return Action[]
*/
public function getActions(): array
{
@@ -70,10 +76,19 @@ class ActionGroup
/**
* Sets the ordered array of actions.
* @param array<Action> $actions
* @param Action[] $actions
*/
public function setActions(array $actions)
{
$this->actions = $actions;
}
/**
* Adds a single action to the list of actions.
* @param Action $action
*/
public function addAction(Action $action)
{
$this->actions[] = $action;
}
}
+15 -17
View File
@@ -3,24 +3,31 @@ declare(strict_types=1);
namespace LotGD\Core;
use Exception;
use LotGD\Core\Models\Scene;
/**
* An attachment to a scene. This is desigend to be subclasses by modules to
* provide functinoality like forms or maybe image attachments to go along with a scene.
*/
abstract class Attachment
abstract class Attachment implements AttachmentInterface
{
protected $id;
protected $type;
protected string $id;
/**
* Construct a new attachment of the given type. Randomly assigns it an ID.
* @param string $type Type of this attachment, in the vendor/module/type format.
* @return Attachment
* @param Game $game
* @param Scene $scene
* @throws Exception
*/
public function __construct(string $type)
public function __construct(Game $game, Scene $scene)
{
$this->id = bin2hex(random_bytes(8));
$this->type = $type;
$this->id = \bin2hex(\random_bytes(8));
}
public function __toString(): string
{
return "<Attachment#{$this->id} '{". static::class . "}'>";
}
/**
@@ -32,13 +39,4 @@ abstract class Attachment
{
return $this->id;
}
/**
* Returns the type of this attachment, in vendor/module/type format.
* @return string
*/
public function getType(): string
{
return $this->type;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Models\Scene;
interface AttachmentInterface
{
/**
* AttachmentInterface constructor.
* @param Game $g Should not be saved internally.
* @param Scene $scene Should not be saved internally.
*/
public function __construct(Game $g, Scene $scene);
public function __toString(): string;
public function getId(): string;
/**
* Returns an array with attachment-specific fields.
* @return array
*/
public function getData(): array;
}
+111 -103
View File
@@ -3,25 +3,17 @@ declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\Common\Collections\{
ArrayCollection,
Collection
};
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use LotGD\Core\{
DiceBag,
Exceptions\ArgumentException,
Exceptions\BattleIsOverException,
Exceptions\BattleNotOverException,
Models\FighterInterface
};
use LotGD\Core\Models\{
Buff,
BattleEvents\BuffMessageEvent,
BattleEvents\CriticalHitEvent,
BattleEvents\DamageEvent,
BattleEvents\DeathEvent
};
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Exceptions\BattleIsOverException;
use LotGD\Core\Exceptions\BattleNotOverException;
use LotGD\Core\Models\BattleEvents\CriticalHitEvent;
use LotGD\Core\Models\BattleEvents\DamageEvent;
use LotGD\Core\Models\BattleEvents\DeathEvent;
use LotGD\Core\Models\Buff;
use LotGD\Core\Models\FighterInterface;
/**
* Class for managing and running battles between 2 participants.
@@ -38,18 +30,10 @@ class Battle
const RESULT_PLAYERDEATH = 1;
const RESULT_MONSTERDEATH = 2;
protected $player;
protected $monster;
protected $game;
protected $events;
protected $result = 0;
protected $round = 0;
/**
* Battle Configuration
* @var type
*/
protected $configuration = [
protected ArrayCollection $events;
protected int $result = 0;
protected int $round = 0;
protected array $configuration = [
"riposteEnabled" => true,
"levelAdjustementEnabled" => true,
"criticalHitEnabled" => true,
@@ -57,35 +41,54 @@ class Battle
/**
* Takes a game object and two participants (Player and Monster) to fight a battle.
* @param \LotGD\Core\Game $game
* @param Game $game
* @param FighterInterface $player
* @param FighterInterface $monster
* @param FighterInterface|null $monster
*/
public function __construct(Game $game, FighterInterface $player, FighterInterface $monster)
{
$this->game = $game;
$this->player = $player;
$this->monster = $monster;
public function __construct(
protected Game $game,
protected FighterInterface $player,
protected ?FighterInterface $monster
) {
$this->events = new ArrayCollection();
}
/**
* @ToDo Returns at some point battle actions
* Returns a string which contains the important fields that must be serialized.
* @return string
*/
public function getActions()
public function serialize(): string
{
return \serialize([
"monster" => $this->monster,
"result" => $this->result,
"round" => $this->round,
"configuration" => $this->configuration,
]);
}
/**
* @param Game $game
* @param FighterInterface $player
* @param string $serialized
* @return self
*/
public static function unserialize(Game $game, FighterInterface $player, string $serialized): self
{
$battle = new self($game, $player, null);
$unserialized = \unserialize($serialized);
$battle->monster = $unserialized["monster"];
$battle->result = $unserialized["result"];
$battle->round = $unserialized["round"];
$battle->configuration = $unserialized["configuration"];
return $battle;
}
/**
* @ToDo Do some action
*/
public function selectAction()
{
}
/**
* Returns a list of all battle events
* @return \LotGD\Core\Collection
* Returns a list of all battle events.
* @return Collection
*/
public function getEvents(): Collection
{
@@ -93,23 +96,23 @@ class Battle
}
/**
* Disables ripostes
* Disables ripostes.
*/
public function disableRiposte()
public function disableRiposte(): void
{
$this->configuration["riposteEnabled"] = false;
}
/**
* Enables ripostes
* Enables ripostes.
*/
public function enableRiposte()
public function enableRiposte(): void
{
$this->configuration["riposteEnabled"] = true;
}
/**
* Returns true if ripostes are enabled
* Returns true if ripostes are enabled.
* @return bool
*/
public function isRiposteEnabled(): bool
@@ -118,23 +121,23 @@ class Battle
}
/**
* Enables level adjustement
* Enables level adjustement.
*/
public function enableLevelAdjustement()
public function enableLevelAdjustement(): void
{
$this->configuration["levelAdjustementEnabled"] = true;
}
/**
* Disables level adjustement
* Disables level adjustement.
*/
public function disableLevelAdjustement()
public function disableLevelAdjustement(): void
{
$this->configuration["levelAdjustementEnabled"] = false;
}
/**
* Returns true if level adjustements are enabled
* Returns true if level adjustements are enabled.
* @return bool
*/
public function isLevelAdjustementEnabled(): bool
@@ -143,7 +146,7 @@ class Battle
}
/**
* Returns true if critical hit events are enabled
* Returns true if critical hit events are enabled.
* @return bool
*/
public function isCriticalHitEnabled(): bool
@@ -152,17 +155,17 @@ class Battle
}
/**
* Disable critical hits
* Disable critical hits.
*/
public function disableCriticalHit()
public function disableCriticalHit(): void
{
$this->configuration["criticalHitEnabled"] = false;
}
/**
* enables critical hits
* enables critical hits.
*/
public function enableCriticalHit()
public function enableCriticalHit(): void
{
$this->configuration["criticalHitEnabled"] = true;
}
@@ -177,7 +180,7 @@ class Battle
}
/**
* Returns the player instance
* Returns the player instance.
* @return FighterInterface
*/
public function getPlayer(): FighterInterface
@@ -186,7 +189,7 @@ class Battle
}
/**
* Returns the montser instance
* Returns the montser instance.
* @return FighterInterface
*/
public function getMonster(): FighterInterface
@@ -195,7 +198,8 @@ class Battle
}
/**
* Returns the winner of this fight
* Returns the winner of this fight.
* @throws BattleNotOverException if battle is not over.
* @return FighterInterface
*/
public function getWinner(): FighterInterface
@@ -208,7 +212,8 @@ class Battle
}
/**
* Returns the loser of this fight
* Returns the loser of this fight.
* @throws BattleNotOverException if battle is not over.
* @return FighterInterface
*/
public function getLoser(): FighterInterface
@@ -224,7 +229,9 @@ class Battle
* Fights the number of rounds given by the parameter $n and returns the number
* of actual rounds fought.
* @param int $n
* @param bool $firstDamageRound Which damage rounds are calculated. Cannot be 0.
* @param int $firstDamageRound Which damage rounds are calculated. Cannot be 0.
* @throws ArgumentException if firstDamageRound is 0.
* @throws BattleIsOverException
* @return int Number of fights fought.
*/
public function fightNRounds(int $n = 1, int $firstDamageRound = self::DAMAGEROUND_BOTH): int
@@ -246,14 +253,14 @@ class Battle
}
}
return $count+1;
return $count + 1;
}
/**
* Fights exactly 1 round
* Fights exactly 1 round.
* @param int $firstDamageRound
*/
protected function fightOneRound(int $firstDamageRound)
protected function fightOneRound(int $firstDamageRound): void
{
$damageHasBeenDone = false;
@@ -266,7 +273,7 @@ class Battle
$offenseTurnEvents = $firstDamageRound & self::DAMAGEROUND_PLAYER ? $this->turn($this->player, $this->monster) : new ArrayCollection();
$defenseTurnEvents = $firstDamageRound & self::DAMAGEROUND_MONSTER ? $this->turn($this->monster, $this->player) : new ArrayCollection();
$events = new ArrayCollection(array_merge($offenseTurnEvents->toArray(), $defenseTurnEvents->toArray()));
$events = new ArrayCollection([...$offenseTurnEvents->toArray(), ...$defenseTurnEvents->toArray()]);
$eventsToAdd = new ArrayCollection();
foreach ($events as $event) {
@@ -296,17 +303,17 @@ class Battle
$monsterBuffExpiringEvents = $this->monster->getBuffs()->expireOneRound();
$this->events = new ArrayCollection(
array_merge(
$this->events->toArray(),
$playerBuffStartEvents->toArray(),
$monsterBuffStartEvents->toArray(),
$eventsToAdd->toArray(),
$playerBuffEndEvents->toArray(),
$monsterBuffEndEvents->toArray(),
$playerBuffExpiringEvents->toArray(),
$monsterBuffExpiringEvents->toArray(),
isset($deathEvent) ? [$deathEvent] : []
)
[
...$this->events->toArray(),
...$playerBuffStartEvents->toArray(),
...$monsterBuffStartEvents->toArray(),
...$eventsToAdd->toArray(),
...$playerBuffEndEvents->toArray(),
...$monsterBuffEndEvents->toArray(),
...$playerBuffExpiringEvents->toArray(),
...$monsterBuffExpiringEvents->toArray(),
...isset($deathEvent) ? [$deathEvent] : [],
]
);
}
@@ -314,6 +321,7 @@ class Battle
* Runs one turn.
* @param FighterInterface $attacker
* @param FighterInterface $defender
* @return ArrayCollection
*/
protected function turn(FighterInterface $attacker, FighterInterface $defender): ArrayCollection
{
@@ -346,12 +354,12 @@ class Battle
// Apply buff scaling for the attacker's attack - this needs to take into
// account the attacker's goodguyAttackModifier and the defenders badguyAttackModifier
$attackersAttack = $attacker->getAttack($this->game)
$attackersAttack = $attacker->getAttack()
* $attackersBuffs->getGoodguyAttackModifier()
* $defendersBuffs->getBadguyAttackModifier();
// It's the opposite for the defender's defense - it needs to take into account the
// defender's goodguyDefenseModifier as well as the attacker's badguyDefenseModifier.
$defendersDefense = $defender->getDefense($this->game)
$defendersDefense = $defender->getDefense()
* $defendersBuffs->getGoodguyDefenseModifier()
* $attackersBuffs->getBadguyDefenseModifier()
* $defenseAdjustement;
@@ -365,17 +373,17 @@ class Battle
}
// Conversion from float to int, since the random number generator takes int values.
$attackersAttack = (int) round($attackersAttack, 0);
$defendersDefense = (int) round($defendersDefense, 0);
$attackersAttack = (int)\round($attackersAttack, 0);
$defendersDefense = (int)\round($defendersDefense, 0);
// Lets roll the
$attackersAtkRoll = $this->game->getDiceBag()->normal(0, $attackersAttack);
$defendersDefRoll = $this->game->getDiceBag()->normal(0, $defendersDefense);
$attackersAtkRoll = $this->game->getDiceBag()->pseudoBell(0, $attackersAttack);
$defendersDefRoll = $this->game->getDiceBag()->pseudoBell(0, $defendersDefense);
$damage = $attackersAtkRoll - $defendersDefRoll;
// If the attacker's attack after modification is bigger than before,
// we call it a critical hit and apply the CriticalHitEvent.
if ($attackersAttack > $attacker->getAttack($this->game) && $this->isCriticalHitEnabled()) {
if ($attackersAttack > $attacker->getAttack() && $this->isCriticalHitEnabled()) {
$events->add(new CriticalHitEvent($attacker, $attackersAttack));
}
@@ -394,10 +402,10 @@ class Battle
$damage = 0;
} elseif ($attackerIsInvulnurable) {
// Attaker is invulnurable, damage is always > 0 (there is no riposte)
$damage = abs($damage);
$damage = \abs($damage);
} elseif ($defenderIsInvulnurable) {
// Defender is invulnurable, damage is always < 0 (defender always ripostes)
$damage = - abs($damage);
$damage = -\abs($damage);
}
if ($damage < 0) {
@@ -419,7 +427,7 @@ class Battle
}
// Round the damage value and convert to int.
$damage = (int)round($damage, 0);
$damage = (int)\round($damage, 0);
// Add the damage event
$events->add(new DamageEvent($attacker, $defender, $damage));
@@ -435,15 +443,15 @@ class Battle
$defendersDamageDependentBuffEvents = $defendersBuffs->processDamageDependentBuffs(Buff::ACTIVATE_DEFENSE, -$damage, $this->game, $defender, $attacker);
return new ArrayCollection(
array_merge(
$attackersBuffStartEvents->toArray(),
$attackersDirectBuffEvents->toArray(),
$defendersBuffStartEvents->toArray(),
$defendersDirectBuffEvents->toArray(),
$events->toArray(),
$attackersDamageDependentBuffEvents->toArray(),
$defendersDamageDependentBuffEvents->toArray()
)
[
...$attackersBuffStartEvents->toArray(),
...$attackersDirectBuffEvents->toArray(),
...$defendersBuffStartEvents->toArray(),
...$defendersDirectBuffEvents->toArray(),
...$events->toArray(),
...$attackersDamageDependentBuffEvents->toArray(),
...$defendersDamageDependentBuffEvents->toArray(),
]
);
}
}
+82 -46
View File
@@ -3,26 +3,22 @@ declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\ORM\ {
EntityManager,
EntityManagerInterface,
Mapping\AnsiQuoteStrategy,
Tools\Setup,
Tools\SchemaTool
};
use Monolog\ {
Logger,
Handler\RotatingFileHandler
};
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events as DoctrineEvents;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\Setup;
use LotGD\Core\Doctrine\EntityPostLoadEventListener;
use LotGD\Core\Exceptions\InvalidConfigurationException;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Application;
use LotGD\Core\ {
ComposerManager,
BootstrapInterface,
Exceptions\InvalidConfigurationException
};
/**
* The entry point for constructing a properly configured LotGD Game object.
*/
@@ -30,23 +26,24 @@ class Bootstrap
{
private $logger;
private $game;
private $libraryConfigurationManager = [];
/** @var LibraryConfigurationManager */
private $libraryConfigurationManager;
private $annotationDirectories = [];
/**
* Create a new Game object, with all the necessary configuration.
* @param string $cwd
* @param string|null $cwd
* @return Game The newly created Game object.
*/
public static function createGame(string $cwd = null): Game
{
$game = new self();
$cwd = $cwd ?? getcwd();
$cwd = $cwd ?? \getcwd();
return $game->getGame($cwd);
}
/**
* Starts the game kernel with the most important classes and returns the object
* Starts the game kernel with the most important classes and returns the object.
* @param string $cwd
* @return Game
*/
@@ -60,20 +57,33 @@ class Bootstrap
$composer = $this->createComposerManager($cwd);
$this->libraryConfigurationManager = $this->createLibraryConfigurationManager($composer, $cwd);
list($dsn, $user, $password) = $config->getDatabaseConnectionDetails($cwd);
[$dsn, $user, $password] = $config->getDatabaseConnectionDetails($cwd);
$pdo = $this->connectToDatabase($dsn, $user, $password);
$entityManager = $this->createEntityManager($pdo);
$entityManager = $this->createEntityManager($pdo, $config);
$this->game = new Game($config, $this->logger, $entityManager, $cwd);
$this->game = (new GameBuilder())
->withConfiguration($config)
->withLogger($this->logger)
->withEntityManager($entityManager)
->withCwd($cwd)
->create()
;
// Add Event listener to entity manager
$dem = $entityManager->getEventManager();
$dem->addEventListener([DoctrineEvents::postLoad], new EntityPostLoadEventListener($this->game));
// Run model extender
$this->extendModels();
return $this->game;
}
/**
* Creates a library configuration manager
* Creates a library configuration manager.
* @param ComposerManager $composerManager
* @param string $cwd
* @return \LotGD\Core\LibraryConfigurationManager
* @return LibraryConfigurationManager
*/
protected function createLibraryConfigurationManager(
ComposerManager $composerManager,
@@ -83,8 +93,10 @@ class Bootstrap
}
/**
* Connects to a database using pdo
* @param \LotGD\Core\Configuration $config
* Connects to a database using pdo.
* @param string $dsn
* @param string $user
* @param string $password
* @return \PDO
*/
protected function connectToDatabase(string $dsn, string $user, string $password): \PDO
@@ -93,7 +105,7 @@ class Bootstrap
}
/**
* Creates and returns an instance of ComposerManager
* Creates and returns an instance of ComposerManager.
* @param string $cwd
* @return ComposerManager
*/
@@ -106,16 +118,16 @@ class Bootstrap
/**
* Returns a configuration object reading from the file located at the path stored in $cwd/config/lotgd.yml.
* @param string $cwd
* @return \LotGD\Core\Configuration
* @throws InvalidConfigurationException
* @return Configuration
*/
protected function createConfiguration(string $cwd): Configuration
{
if (empty($configFilePath)) {
$configFilePath = implode(DIRECTORY_SEPARATOR, [$cwd, "config", "lotgd.yml"]);
$configFilePath = \implode(\DIRECTORY_SEPARATOR, [$cwd, "config", "lotgd.yml"]);
}
if ($configFilePath === false || strlen($configFilePath) == 0 || is_file($configFilePath) === false) {
if ($configFilePath === false || \strlen($configFilePath) == 0 || \is_file($configFilePath) === false) {
throw new InvalidConfigurationException("Invalid or missing configuration file: {$configFilePath}.");
}
@@ -124,7 +136,7 @@ class Bootstrap
}
/**
* Returns a logger instance
* Returns a logger instance.
* @param Configuration $config
* @param string $name
* @return LoggerInterface
@@ -133,35 +145,41 @@ class Bootstrap
{
$logger = new Logger($name);
// Add lotgd as the prefix for the log filenames.
$logger->pushHandler(new RotatingFileHandler($config->getLogPath() . DIRECTORY_SEPARATOR . $name, 14));
$logger->pushHandler(new RotatingFileHandler($config->getLogPath() . \DIRECTORY_SEPARATOR . $name, 14));
return $logger;
}
/**
* Creates the EntityManager using the pdo connection given in it's argument
* Creates the EntityManager using the pdo connection given in it's argument.
* @param \PDO $pdo
* @param Configuration
* @return EntityManagerInterface
*/
protected function createEntityManager(\PDO $pdo): EntityManagerInterface
protected function createEntityManager(\PDO $pdo, Configuration $config): EntityManagerInterface
{
$this->annotationDirectories = $this->generateAnnotationDirectories();
$this->logger->addDebug("Adding annotation directories:");
$this->logger->debug("Adding annotation directories:");
foreach ($this->annotationDirectories as $d) {
$this->logger->addDebug(" {$d}");
$this->logger->debug(" {$d}");
}
$configuration = Setup::createAnnotationMetadataConfiguration($this->annotationDirectories, true);
// Set a quote
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());
// Create entity manager
$entityManager = EntityManager::create(["pdo" => $pdo], $configuration);
// Register uuid type
try {
Type::addType('uuid', 'Ramsey\Uuid\Doctrine\UuidType');
} catch (DBALException $e) {
}
// Create Schema and update database if needed
$metaData = $entityManager->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool($entityManager);
$schemaTool->updateSchema($metaData);
if ($config->getDatabaseAutoSchemaUpdate()) {
$metaData = $entityManager->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool($entityManager);
$schemaTool->updateSchema($metaData);
}
return $entityManager;
}
@@ -173,12 +191,12 @@ class Bootstrap
protected function generateAnnotationDirectories(): array
{
// Read db annotations from our own model files.
$directories = [__DIR__ . DIRECTORY_SEPARATOR . 'Models'];
$directories = [__DIR__ . \DIRECTORY_SEPARATOR . 'Models'];
// Get additional annotation directories from library configs.
$libraryDirectories = $this->libraryConfigurationManager->getEntityDirectories();
return array_merge($directories, $libraryDirectories);
return \array_merge($directories, $libraryDirectories);
}
/**
@@ -194,4 +212,22 @@ class Bootstrap
}
}
}
/**
* Runs the code to extend models.
*/
public function extendModels()
{
AnnotationRegistry::registerLoader("class_exists");
$modelExtender = new ModelExtender();
foreach ($this->libraryConfigurationManager->getConfigurations() as $config) {
$modelExtensions = $config->getSubKeyIfItExists(["modelExtensions"]);
if ($modelExtensions) {
$modelExtender->addMore($modelExtensions);
}
}
}
}
+66 -82
View File
@@ -3,73 +3,58 @@ declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\Common\Collections\{
ArrayCollection,
Collection
};
use LotGD\Core\Exceptions\{
ArgumentException,
BuffListAlreadyActivatedException
};
use LotGD\Core\Models\{
Buff,
Character,
FighterInterface,
BattleEvents\BuffMessageEvent,
BattleEvents\DamageLifetapEvent,
BattleEvents\DamageReflectionEvent,
BattleEvents\RegenerationBuffEvent,
BattleEvents\MinionDamageEvent
};
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Exceptions\BuffListAlreadyActivatedException;
use LotGD\Core\Exceptions\BuffSlotOccupiedException;
use LotGD\Core\Models\BattleEvents\BuffMessageEvent;
use LotGD\Core\Models\BattleEvents\DamageLifetapEvent;
use LotGD\Core\Models\BattleEvents\DamageReflectionEvent;
use LotGD\Core\Models\BattleEvents\MinionDamageEvent;
use LotGD\Core\Models\BattleEvents\RegenerationBuffEvent;
use LotGD\Core\Models\Buff;
use LotGD\Core\Models\FighterInterface;
/**
* Description of BuffList
* Description of BuffList.
*/
class BuffList
{
protected $buffs;
protected $buffsBySlot;
protected $activeBuffs = [];
/** @var Doctrine\Common\Collections\ArrayCollection */
protected $usedBuffs;
protected array $buffsBySlot;
protected array $activeBuffs = [];
protected ArrayCollection $usedBuffs;
/** @var boolean True of the modifiers have already been calculated */
protected $modifiersCalculated = false;
/** @var boolean True if the badguy is invulnurable */
protected $badguyInvulnurable = false;
/** @var float */
protected $badguyDamageModifier = 1.;
/** @var float */
protected $badguyAttackModifier = 1.;
/** @var float */
protected $badguyDefenseModifier = 1.;
/** @var boolean True if the goodguy is invulnurable */
protected $goodguyInvulnurable = false;
/** @var float */
protected $goodguyDamageModifier = 1.;
/** @var float */
protected $goodguyAttackModifier = 1.;
/** @var float */
protected $goodguyDefenseModifier = 1.;
/** True of the modifiers have already been calculated */
protected bool $modifiersCalculated = false;
/** True if the badguy is invulnurable */
protected bool $badguyInvulnurable = false;
protected $events;
protected $loaded = false;
protected float $badguyDamageModifier = 1.;
protected float $badguyAttackModifier = 1.;
protected float $badguyDefenseModifier = 1.;
protected bool $goodguyInvulnurable = false;
protected float $goodguyDamageModifier = 1.;
protected float $goodguyAttackModifier = 1.;
protected float $goodguyDefenseModifier = 1.;
protected ArrayCollection $events;
protected bool $loaded = false;
/**
* Initiates some variables
* Initiates some variables.
* @param Collection $buffs
*/
public function __construct(Collection $buffs)
{
$this->buffs = $buffs;
public function __construct(
protected Collection $buffs
) {
$this->events = new ArrayCollection();
$this->usedBuffs = new ArrayCollection();
}
/**
* Loads all buffs (since it's a lazy correlation)
* Loads all buffs (since it's a lazy correlation).
*/
public function loadBuffs()
{
@@ -97,7 +82,7 @@ class BuffList
}
/**
* Marks the given buff as used
* Marks the given buff as used.
* @param Buff $buff
*/
protected function useBuff(Buff $buff)
@@ -106,7 +91,7 @@ class BuffList
}
/**
* Returns the buff's start or round message
* Returns the buff's start or round message.
* @param Buff $buff
* @return string
*/
@@ -125,7 +110,7 @@ class BuffList
}
/**
* Resets the buff usage for a new round
* Resets the buff usage for a new round.
*/
public function resetBuffUsage()
{
@@ -136,22 +121,23 @@ class BuffList
/**
* Returns whether any buffs are in use.
* @return bool
*/
public function hasBuffsInUse(): bool
{
return count($this->usedBuffs) > 0 ? true : false;
return \count($this->usedBuffs) > 0;
}
/**
* Activates all buffs that activate upon the given activation parameter.
* @param int $activation
* @return Collection
* @throws ArgumentException
* @throws BuffListAlreadyActivatedException
* @return Collection
*/
public function activate(int $activation): Collection
{
if ($activation%2 !== 0 && $activation !== 1) {
if ($activation % 2 !== 0 && $activation !== 1) {
throw new ArgumentException("You can only activate one activation type at a time.");
}
@@ -186,7 +172,7 @@ class BuffList
}
/**
* Decreases the rounds left on all used buffs
* Decreases the rounds left on all used buffs.
* @return Collection A Collection containing expire messages (if there are any)
*/
public function expireOneRound(): Collection
@@ -261,10 +247,9 @@ class BuffList
}
/**
* Calculates all total modifiers
* @return type
* Calculates all total modifiers.
*/
protected function calculateModifiers()
protected function calculateModifiers(): void
{
if ($this->modifiersCalculated === true) {
return;
@@ -302,14 +287,13 @@ class BuffList
// Only look at buffs that are activated in battle.
if ($buff->getsActivatedAt(Buff::ACTIVATE_NONE)) {
continue;
} else {
yield $buff;
}
yield $buff;
}
}
/**
* Returns the badguy attack modifier calculated over the whole bufflist
* Returns the badguy attack modifier calculated over the whole bufflist.
* @return float
*/
public function getBadguyAttackModifier(): float
@@ -318,8 +302,8 @@ class BuffList
return $this->badguyAttackModifier;
}
/**
* Returns the badguy defense modifier calculated over the whole bufflist
/**
* Returns the badguy defense modifier calculated over the whole bufflist.
* @return float
*/
public function getBadguyDefenseModifier(): float
@@ -329,7 +313,7 @@ class BuffList
}
/**
* Returns the badguy damage modifier calculated over the whole bufflist
* Returns the badguy damage modifier calculated over the whole bufflist.
* @return float
*/
public function getBadguyDamageModifier(): float
@@ -339,7 +323,7 @@ class BuffList
}
/**
* Returns true if the badguy is invulnurable
* Returns true if the badguy is invulnurable.
* @return bool
*/
public function badguyIsInvulnurable(): bool
@@ -349,7 +333,7 @@ class BuffList
}
/**
* Returns the badguy attack modifier calculated over the whole bufflist
* Returns the badguy attack modifier calculated over the whole bufflist.
* @return float
*/
public function getGoodguyAttackModifier(): float
@@ -358,8 +342,8 @@ class BuffList
return $this->goodguyAttackModifier;
}
/**
* Returns the badguy defense modifier calculated over the whole bufflist
/**
* Returns the badguy defense modifier calculated over the whole bufflist.
* @return float
*/
public function getGoodguyDefenseModifier(): float
@@ -368,8 +352,8 @@ class BuffList
return $this->goodguyDefenseModifier;
}
/**
* Returns the badguy damage modifier calculated over the whole bufflist
/**
* Returns the badguy damage modifier calculated over the whole bufflist.
* @return float
*/
public function getGoodguyDamageModifier(): float
@@ -379,7 +363,7 @@ class BuffList
}
/**
* Returns true if the goodguy is invulnurable
* Returns true if the goodguy is invulnurable.
* @return bool
*/
public function goodguyIsInvulnurable(): bool
@@ -389,9 +373,9 @@ class BuffList
}
/**
* Processes buffs that do direct damage or regeneration
* Processes buffs that do direct damage or regeneration.
* @param int $activation
* @param \LotGD\Core\Game $game
* @param Game $game
* @param FighterInterface $goodguy
* @param FighterInterface $badguy
* @return Collection
@@ -477,7 +461,7 @@ class BuffList
$events[] = new MinionDamageEvent(
$target,
(int)round($damage, 0),
(int)\round($damage, 0),
$message
);
}
@@ -490,10 +474,10 @@ class BuffList
}
/**
* Processes buffs that are dependant on the damage done in one round
* Processes buffs that are dependant on the damage done in one round.
* @param int $activation
* @param int $damage Positive damage is applied to the badguy, negative damage is applied to the goodguy
* @param \LotGD\Core\Game $game
* @param Game $game
* @param FighterInterface $goodguy
* @param FighterInterface $badguy
* @return Collection
@@ -518,7 +502,7 @@ class BuffList
$reflectedDamage = 0;
$message = $buff->getNoEffectMessage();
} else {
$reflectedDamage = (int)round($buff->getGoodguyDamageReflection() * $damage * -1, 0);
$reflectedDamage = (int)\round($buff->getGoodguyDamageReflection() * $damage * -1, 0);
if ($reflectedDamage === 0) {
$message = $buff->getNoEffectMessage();
} else {
@@ -536,7 +520,7 @@ class BuffList
if ($buff->getBadguyDamageReflection() !== 0.) {
if ($damage > 0) {
// Damage is > 0, so badguy takes damage, we can normally reflect
$reflectedDamage = (int)round($buff->getGoodguyDamageReflection() * $damage, 0);
$reflectedDamage = (int)\round($buff->getBadguyDamageReflection() * $damage, 0);
if ($reflectedDamage === 0) {
$message = $buff->getNoEffectMessage();
} else {
@@ -565,7 +549,7 @@ class BuffList
$message = $buff->getEffectFailsMessage();
} elseif ($damage < 0) {
// Damage is < 0, goodguy takes damage. We act upon this.
$healAmount = (int)round($damage * -$buff->getBadguyLifetap(), 0);
$healAmount = (int)\round($damage * -$buff->getBadguyLifetap(), 0);
if ($healAmount === 0) {
$message = $buff->getNoEffectMessage();
} else {
@@ -586,7 +570,7 @@ class BuffList
if ($buff->getBadguyLifetap() !== 0.) {
if ($damage > 0) {
// Damage is > 0, badguy takes damage. We act upon this to heal goodguy.
$healAmount = (int)round($damage * $buff->getBadguyLifetap(), 0);
$healAmount = (int)\round($damage * $buff->getBadguyLifetap(), 0);
if ($healAmount === 0) {
$message = $buff->getNoEffectMessage();
} else {
+42 -46
View File
@@ -3,35 +3,29 @@ declare(strict_types=1);
namespace LotGD\Core;
use Composer\{
Composer,
Factory,
IO\NullIO
};
use Monolog\Logger;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\NullIO;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Exception;
use LotGD\Core\{
Exceptions\InvalidConfigurationException,
Exceptions\LibraryDoesNotExistException
};
use LotGD\Core\Exceptions\InvalidConfigurationException;
use LotGD\Core\Exceptions\LibraryDoesNotExistException;
/**
* Helps perform tasks with the composer configuration.
*/
class ComposerManager
{
private $composer;
private $cwd;
private ?Composer $composer = null;
/**
* Construct a manager with an optional working directory where composer.json
* lives.
* @param string $cwd
*/
public function __construct(string $cwd)
{
$this->cwd = $cwd;
}
public function __construct(private string $cwd) {}
/**
* Returns a Composer instance to perform underlying operations on. Be careful.
@@ -41,13 +35,13 @@ class ComposerManager
{
if ($this->composer === null) {
// Verify location of composer.json.
$path = $this->cwd . DIRECTORY_SEPARATOR . "composer.json";
if (!file_exists($path)) {
throw new InvalidConfigurationException("composer.json cannot be found at {$path}.");
$composerConfigPath = $this->cwd . \DIRECTORY_SEPARATOR . "composer.json";
if (!\file_exists($composerConfigPath)) {
throw new InvalidConfigurationException("composer.json cannot be found at {$composerConfigPath}.");
}
$io = new NullIO();
$this->composer = Factory::create($io, $path);
$factory = new Factory();
$this->composer = $factory->createComposer(new NullIO(), $composerConfigPath, false, $this->cwd);
}
return $this->composer;
@@ -55,10 +49,11 @@ class ComposerManager
/**
* Return the Composer package for the corresponding library, in vendor/module format.
* @return PackageInterface Package corresponding to this library.
* @param string $library
* @throws LibraryDoesNotExistException
* @return CompletePackageInterface Package corresponding to this library.
*/
public function getPackageForLibrary(string $library): PackageInterface
public function getPackageForLibrary(string $library): CompletePackageInterface
{
// TODO: should probably do something better than O(n) here.
$packages = $this->getComposer()->getRepositoryManager()->getLocalRepository()->getPackages();
@@ -72,27 +67,27 @@ class ComposerManager
/**
* Return all the packages installed in the current setup.
* @return array<Composer\PackageInterface>
* @return PackageInterface[]
*/
public function getPackages(): array
{
return array_merge(
[$this->getComposer()->getPackage()],
$this->getComposer()->getRepositoryManager()->getLocalRepository()->getPackages()
);
return [
$this->getComposer()->getPackage(),
...$this->getComposer()->getRepositoryManager()->getLocalRepository()->getPackages(),
];
}
/**
* Return a list of the configured packages which are LotGD modules (type = 'lotgd-module').
* @return array Array of \Composer\PackageInterface.
* @return PackageInterface[]
*/
public function getModulePackages(): array
{
$result = array();
$result = [];
$packages = $this->getComposer()->getRepositoryManager()->getLocalRepository()->getPackages();
foreach ($packages as $p) {
if ($p->getType() === 'lotgd-module') {
array_push($result, $p);
\array_push($result, $p);
}
}
@@ -105,7 +100,7 @@ class ComposerManager
* @return string|null Path representing $namespace or null if $namespace
* cannot be found or if the path does not exist.
*/
public function translateNamespaceToPath(string $namespace)
public function translateNamespaceToPath(string $namespace): ?string
{
// Find the directory for this namespace by using the autoloader's
// classmap.
@@ -115,32 +110,32 @@ class ComposerManager
// Standardize the namespace to remove any leading \ and add a trailing \
$n = $namespace;
if ('\\' == $n[0]) {
$n = substr($n, 1);
$n = \substr($n, 1);
}
if (strlen($n) > 0 && '\\' != $n[strlen($n) - 1]) {
if (\strlen($n) > 0 && '\\' != $n[\strlen($n) - 1]) {
$n .= '\\';
}
$split = explode('\\', $n);
$suffix = array_splice($split, -1, 1); // starts with ['']
$split = \explode('\\', $n);
$suffix = \array_splice($split, -1, 1); // starts with ['']
$path = null;
while (!empty($split)) {
$key = implode('\\', $split) . '\\';
$dir = implode(DIRECTORY_SEPARATOR, $suffix);
$key = \implode('\\', $split) . '\\';
$dir = \implode(\DIRECTORY_SEPARATOR, $suffix);
// Prefix to directory mappings are arrays in Composer's
// ClassLoader object. Not sure why. This might break in
// some unforseen case.
if (isset($prefixes[$key]) && is_dir($prefixes[$key][0] . DIRECTORY_SEPARATOR . $dir)) {
$path = $prefixes[$key][0] . DIRECTORY_SEPARATOR . $dir;
if (isset($prefixes[$key]) && \is_dir($prefixes[$key][0] . \DIRECTORY_SEPARATOR . $dir)) {
$path = $prefixes[$key][0] . \DIRECTORY_SEPARATOR . $dir;
break;
}
$suffix = array_merge($suffix, array_splice($split, -1, 1));
$suffix = \array_merge($suffix, \array_splice($split, -1, 1));
}
if ($path == null) {
return null;
}
$path = realpath($path);
$path = \realpath($path);
if ($path == false) {
return null;
}
@@ -150,19 +145,20 @@ class ComposerManager
/**
* Returns a path (could be relative) to the proper autoload.php file in
* the current setup.
* @return string
*/
public function findAutoloader(): string
{
// Dance to find the autoloader.
// TOOD: change this to open up the Composer config and use $c['config']['vendor-dir'] instead of "vendor"
$order = [
implode(DIRECTORY_SEPARATOR, [$this->cwd, "vendor", "autoload.php"]),
implode(DIRECTORY_SEPARATOR, [__DIR__, "..", "vendor", "autoload.php"]),
implode(DIRECTORY_SEPARATOR, [__DIR__, "..", "autoload.php"]),
\implode(\DIRECTORY_SEPARATOR, [$this->cwd, "vendor", "autoload.php"]),
\implode(\DIRECTORY_SEPARATOR, [__DIR__, "..", "vendor", "autoload.php"]),
\implode(\DIRECTORY_SEPARATOR, [__DIR__, "..", "autoload.php"]),
];
foreach ($order as $path) {
if (file_exists($path)) {
if (\file_exists($path)) {
return $path;
}
}
+35 -18
View File
@@ -5,10 +5,10 @@ namespace LotGD\Core;
use DateTime;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
use LotGD\Core\Exceptions\InvalidConfigurationException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* The configuration information for a LotGD game. Configuration is read from
@@ -16,14 +16,15 @@ use LotGD\Core\Exceptions\InvalidConfigurationException;
*/
class Configuration
{
private $databaseDSN;
private $databaseName;
private $databaseUser;
private $databasePassword;
private $logPath;
private $gameEpoch;
private $gameOffsetSeconds;
private $gameDaysPerDay;
private string $databaseDSN;
private string $databaseName;
private string $databaseUser;
private string $databasePassword;
private bool $databaseAutoSchemaUpdate;
private string $logPath;
private DateTime $gameEpoch;
private int $gameOffsetSeconds;
private int $gameDaysPerDay;
/**
* Create the configuration object, reading from the specified path.
@@ -41,8 +42,8 @@ class Configuration
// Log dir path is relative to config directory.
$logPath = $rawConfig['logs']['path'] ?? '';
$realLogPath = dirname($configFilePath) . DIRECTORY_SEPARATOR . $logPath;
if ($realLogPath === false || strlen($realLogPath) == 0 || is_dir($realLogPath) === false) {
$realLogPath = \dirname($configFilePath) . \DIRECTORY_SEPARATOR . $logPath;
if ($realLogPath === false || \strlen($realLogPath) == 0 || \is_dir($realLogPath) === false) {
throw new InvalidConfigurationException("Invalid or missing log path: {$realLogPath}");
}
$this->logPath = $realLogPath;
@@ -52,10 +53,10 @@ class Configuration
$passwd = $rawConfig['database']['password'] ?? '';
$name = $rawConfig['database']['name'] ?? '';
if ($dsn === false || strlen($dsn) == 0) {
if ($dsn === false || \strlen($dsn) == 0) {
throw new InvalidConfigurationException("Invalid or missing data source name: {$dsn}");
}
if ($user === false || strlen($user) == 0) {
if ($user === false || \strlen($user) == 0) {
throw new InvalidConfigurationException("Invalid or missing database user: {$user}");
}
if ($passwd === false) {
@@ -70,6 +71,12 @@ class Configuration
$this->databasePassword = $passwd;
$this->databaseName = $name;
if (empty($rawConfig['database']['disableAutoSchemaUpdate'])) {
$this->databaseAutoSchemaUpdate = true;
} else {
$this->databaseAutoSchemaUpdate = false;
}
$gameEpoch = $rawConfig['game']['epoch'];
$gameOffsetSeconds = $rawConfig['game']['offsetSeconds'];
$gameDaysPerDay = $rawConfig['game']['daysPerDay'];
@@ -98,12 +105,12 @@ class Configuration
*/
protected function retrieveRawConfig(string $configFilePath): array
{
return Yaml::parse(file_get_contents($configFilePath));
return Yaml::parse(\file_get_contents($configFilePath));
}
/**
* Returns database connection details needed for pdo to establish a connection.
*
*
* This function takes optionally replaces the string %cwd% in the database dsn and
* replaces it with the first parameter. This is important to normalize the database location
* across different working directories. Alternatively, SQLite databse names can also directly
@@ -114,7 +121,7 @@ class Configuration
public function getDatabaseConnectionDetails(string $cwd = ""): array
{
return [
str_replace("%cwd%", $cwd . DIRECTORY_SEPARATOR, $this->getDatabaseDSN()),
\str_replace("%cwd%", $cwd . \DIRECTORY_SEPARATOR, $this->getDatabaseDSN()),
$this->getDatabaseUser(),
$this->getDatabasePassword(),
];
@@ -157,6 +164,15 @@ class Configuration
return $this->databasePassword;
}
/**
* True if doctrine should not auto update.
* @return bool
*/
public function getDatabaseAutoSchemaUpdate(): bool
{
return $this->databaseAutoSchemaUpdate;
}
/**
* Return the path to the directory to store log files.
* @return string The configured log directory path.
@@ -197,6 +213,7 @@ class Configuration
/**
* Generate a textual representation of the configuration, for debugging
* purposes.
* @return string
*/
public function __toString(): string
{
+22 -3
View File
@@ -3,16 +3,17 @@ declare(strict_types=1);
namespace LotGD\Core\Console\Command;
use Symfony\Component\Console\Command\Command;
use LotGD\Core\Game;
use Monolog\Logger;
use Symfony\Component\Console\Command\Command;
/**
* Parent class for daenerys tool commands.
*/
abstract class BaseCommand extends Command
{
protected $game;
protected Game $game;
protected ?string $namespace = null;
/**
* Construct the command, using the provided Game.
@@ -23,4 +24,22 @@ abstract class BaseCommand extends Command
parent::__construct();
$this->game = $game;
}
/**
* Returns a cloned logger with a different context name.
* @return Logger
*/
public function getCliLogger(): Logger
{
return $this->game->getLogger()->withName("daenerys-cli");
}
protected function namespaced(string $command): string
{
if ($this->namespace) {
return "{$this->namespace}:{$command}";
} else {
return $command;
}
}
}
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use Exception;
use LotGD\Core\Models\Character;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class CharacterAddCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("add"))
->setDescription("Add a character.")
->setDefinition(
new InputDefinition([
new InputArgument(
"name",
mode: InputArgument::REQUIRED,
description: "Character name",
),
new InputOption(
"level",
mode: InputOption::VALUE_OPTIONAL,
description: "Character level",
default: 1,
),
new InputOption(
"maxHealth",
mode: InputOption::VALUE_OPTIONAL,
description: "Maximum health of the character. 10*level if not given.",
default: null,
),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument("name");
$level = intval($input->getOption("level"));
$maxHealth = $input->getOption("maxHealth");
if ($level <= 0) {
$io->error("Level must at least be 1.");
return Command::FAILURE;
}
// Set maxHealth in dependence of the level if not given.
if ($maxHealth === null) {
$maxHealth = $level*10;
} else {
$maxHealth = intval($maxHealth);
}
if ($maxHealth < 0) {
$io->error("Maximum health must be at least 0.");
return Command::FAILURE;
} elseif ($maxHealth === 0) {
$io->warning("The character will have 0 max health and will be permanently dead.");
}
$character = Character::createAtFullHealth([
"name" => $name,
"level" => $level,
"maxHealth" => $maxHealth,
]);
try {
$em->persist($character);
// Commit changes
$em->flush();
} catch (Exception $e) {
$io->error("Creating the character was not possible. Reason: {$e->getMessage()}.");
return Command::FAILURE;
}
$io->success("{$character} was successfully created.");
$logger->info("Character was created.", ["character" => $character]);
return Command::SUCCESS;
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\Character;
use Symfony\Component\Console\Input\InputArgument;
class CharacterBaseCommand extends BaseCommand
{
protected ?string $namespace = "character";
/**
* @return InputArgument
*/
protected function getCharacterIdArgumentDefinition(): InputArgument
{
return new InputArgument(
name: "id",
mode: InputArgument::REQUIRED,
description: "Character ID",
);
}
/**
* @param string $id
* @return Character|null
*/
protected function getCharacter(string $id): ?Character
{
/** @var Character|null $character */
$character = $this->game->getEntityManager()->getRepository(Character::class)->find($id);
return $character;
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class CharacterConfigListCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:list"))
->setDescription('List available settings for a character')
->setDefinition([
$this->getCharacterIdArgumentDefinition(),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$character = $this->getCharacter($input->getArgument("id"));
if (!$character) {
$io->error("Character was not found.");
return Command::FAILURE;
}
// Create hook
$context = EventContextData::create([
"character" => $character,
"io" => $io,
"settings" => [],
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/character-config-list",
contextData: $context
);
$settings = $newContext->get("settings");
$io->title("Character ".$character->getDisplayName());
if (count($settings) === 0) {
$io->note("There are no character settings available.");
} else {
$io->table(["setting", "value", "description"], $settings);
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class CharacterConfigResetCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:reset"))
->setDescription('Reset a character setting')
->setDefinition([
$this->getCharacterIdArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$character = $this->getCharacter($input->getArgument("id"));
if (!$character) {
$io->error("Module was not found.");
return Command::FAILURE;
}
$io->title("Character {$character->getDisplayName()}");
// Create hook
$context = EventContextData::create([
"character" => $character,
"io" => $io,
"setting" => $input->getArgument("setting"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/character-config-reset",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class CharacterConfigSetCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:set"))
->setDescription('Change a character setting')
->setDefinition([
$this->getCharacterIdArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
new InputArgument(
"value",
InputArgument::REQUIRED,
description: "New value for the given setting.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$character = $this->getCharacter($input->getArgument("id"));
if (!$character) {
$io->error("Module was not found.");
return Command::FAILURE;
}
$io->title("Character {$character->getDisplayName()}");
// Create hook
$context = EventContextData::create([
"character" => $character,
"io" => $io,
"setting" => $input->getArgument("setting"),
"value" => $input->getArgument("value"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/character-config-set",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use Exception;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class CharacterEditCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("edit"))
->setDescription("Edit a given character.")
->setDefinition(
new InputDefinition([
$this->getCharacterIdArgumentDefinition(),
new InputOption(
"level",
mode: InputOption::VALUE_REQUIRED,
description: "Changes the level"
),
new InputOption(
"maxHealth",
mode: InputOption::VALUE_REQUIRED,
description: "Change maximum amount of health points. Health will be adjusted accordingly."
),
new InputOption(
"heal",
mode: InputOption::VALUE_NONE,
description: "Restores full health"
),
new InputOption(
"revive",
mode: InputOption::VALUE_OPTIONAL,
description: "Revives at full health. Give a number between 0 and 1 to revive fractionally.",
default: false,
),
new InputOption(
"kill",
mode: InputOption::VALUE_NONE,
description: "Kills"
),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$id = $input->getArgument("id");
$level = $input->getOption("level");
$heal = $input->getOption("heal");
$revive = $input->getOption("revive");
$kill = $input->getOption("kill");
$maxHealth = $input->getOption("maxHealth");
$changed = false;
// Find character
$character = $this->getCharacter($id);
if (!$character) {
$io->error("The character with the id {$id} was not found.");
return Command::FAILURE;
}
// Change level
if ($level !== null) {
$level = intval($level);
if ($level <= 0) {
$io->error("Cannot set the level below 1.");
return Command::FAILURE;
}
// Only change level if necessary
if ($character->getLevel() !== $level) {
// Log
$logger->info("Character level changed", [
"for" => $character,
"from" => $character->getLevel(),
"to" => $level
]);
// Change
$character->setLevel($level);
$changed = true;
}
}
// Heal
if ($heal) {
if ($character->getHealth() >= $character->getMaxHealth()) {
$io->note("Character is already at full health.");
} elseif ($character->isAlive()) {
$oldHealth = $character->getHealth();
// Log
$logger->info("Character health changed", [
"for" => $character,
"from" => $oldHealth,
"to" => $character->getMaxHealth()
]);
// Change
$character->setHealth($character->getMaxHealth());
$io->success("Character was restored to full health ({$oldHealth} to {$character->getMaxHealth()}).");
$changed = true;
} else {
$io->error("Cannot heal a dead character. Use --revive instead.");
return Command::FAILURE;
}
}
// Revive the character
if ($revive !== false) {
// Make sure we revive between 0 and 1
if ($revive === null) {
$revive = 1;
} elseif (str_ends_with($revive, "%")) {
$revive = min(floatval($revive)/100, 1);
} else {
$revive = min(floatval($revive), 1);
}
if ($character->isAlive()) {
$io->error("Character is already alive. Use --heal instead.");
return Command::FAILURE;
} else {
// Make sure we heal at least by 1.
$reviveAmount = (int)round(max($revive * $character->getMaxHealth(), 1), 0);
// Log
$logger->info("Character was revived", [
"for" => $character,
"to" => $reviveAmount
]);
// Change
$character->setHealth($reviveAmount);
$io->success("Character was revived with {$reviveAmount} of health points "
."(max: {$character->getMaxHealth()}).");
$changed = true;
}
}
if ($kill) {
if (!$character->isAlive()) {
$io->error("What is dead may never die.");
return Command::FAILURE;
} else {
// Log
$logger->info("Character was killed", ["for" => $character]);
// Change
$character->setHealth(0);
$io->success("Character was killed.");
$changed = true;
}
}
if ($maxHealth) {
$maxHealth = intval($maxHealth);
if ($maxHealth < 0) {
$io->error("Cannot set maximum health below 0.");
return Command::FAILURE;
}
if ($character->getMaxHealth() === 0) {
$healthProportion = 0;
} else {
$healthProportion = $character->getHealth() / $character->getMaxHealth();
}
// Log
$logger->info("Character maxHealth changed", [
"for" => $character,
"from" => $character->getMaxHealth(),
"to" => $maxHealth
]);
// Change
$character->setMaxHealth($maxHealth);
$character->setHealth((int)round($healthProportion*$maxHealth, 0));
$io->success("Character has new maximum health of {$maxHealth} (current health is {$character->getHealth()}).");
$changed = true;
}
// Save changes
if ($changed) {
try {
$em->flush();
$io->success("The character was successfully changed.");
// Log
$logger->info("Changed committed.");
} catch (Exception $e) {
$io->error("Character could not be saved. Reason: {$e}");
$logger->error("Changes rolled back.", ["exception" => $e]);
return Command::FAILURE;
}
} else {
$io->note("Nothing was changed.");
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\Repositories\CharacterRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Command to list all characters.
*/
class CharacterListCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("list"))
->setDescription('Lists all characters')
->setDefinition(
new InputDefinition([
new InputOption(
"includeSoftDeleted",
mode: InputOption::VALUE_NONE,
description: "Includes soft-deleted characters",
),
new InputOption(
"onlySoftDeleted",
mode: InputOption::VALUE_NONE,
description: "Displays only soft-deleted characters",
),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$io = new SymfonyStyle($input, $output);
/** @var CharacterRepository $repository */
$repository = $em->getRepository(Character::class);
$marker = "";
if ($input->getOption("includeSoftDeleted")) {
$io->writeln("* marks soft-deleted characters");
$marker = "*";
$characters = $repository->findAll(CharacterRepository::INCLUDE_SOFTDELETED);
} elseif ($input->getOption("onlySoftDeleted")) {
$io->writeln("Only soft-deleted characters are shown");
$characters = $repository->findAll(CharacterRepository::ONLY_SOFTDELETED);
} else {
$characters = $repository->findAll();
}
$table = [["id", "name", "level"], []];
foreach ($characters as $character) {
$table[1][] = [
$character->getId(),
$marker.$character->getName(),
$character->getLevel(),
];
}
$io->table(...$table);
return Command::SUCCESS;
}
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use Exception;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\Repositories\CharacterRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class CharacterRemoveCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("remove"))
->setDescription("Definitely removes a character (no soft delete).")
->setDefinition(
new InputDefinition([
$this->getCharacterIdArgumentDefinition(),
new InputOption(
name: "soft",
mode: InputOption::VALUE_NONE,
description: "Only removes the character softly (soft delete)."
),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
/** @var CharacterRepository $characterRepository */
$characterRepository = $em->getRepository(Character::class);
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$id = $input->getArgument("id");
// Find character
/** @var Character $character */
$character = $characterRepository->findWithSoftDeleted($id);
if (!$character) {
$io->error("The character with the id {$id} was not found.");
return Command::FAILURE;
}
if ($character->isDeleted()) {
$io->info("Character was marked as soft-deleted.");
}
try {
if ($input->getOption("soft")) {
// Only soft-delete if requested
$character->delete($em);
// Commit changes
$em->flush();
$io->success("{$character} was successfully soft-deleted.");
$logger->info("Character was soft-deleted.", ["character" => $character]);
} else {
$em->remove($character);
// Commit changes
$em->flush();
$io->success("{$character} was successfully removed.");
$logger->info("Character was removed.", ["character" => $character]);
}
} catch (Exception $e) {
$io->error("Removing {$character} was not possible. Reason: {$e}.");
return Command::FAILURE;
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use Exception;
use LotGD\Core\Models\Character;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class CharacterResetViewpointCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("resetViewpoint"))
->setDescription("Resets the viewpoint of a given character.")
->setDefinition(
new InputDefinition([
$this->getCharacterIdArgumentDefinition(),
]),
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$id = $input->getArgument("id");
/* @var $character Character */
$character = $em->getRepository(Character::class)->find($id);
if ($character === null) {
$io->error("Character not found.");
return Command::FAILURE;
}
if ($character->getViewpoint() === null) {
$io->info("Character does not have a viewpoint yet.");
} else {
try {
$em->remove($character->getViewpoint());
$character->setViewpoint(null);
$io->success("Viewpoint was successfully reset.");
# Save
$em->flush();
} catch (Exception $e) {
$io->error("Resetting the viewpoint was not possible. Reason: {$e}");
return Command::FAILURE;
}
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Character;
use Exception;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\Character;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class CharacterShowCommand extends CharacterBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("show"))
->setDescription("Shows details about character.")
->setDefinition(
new InputDefinition([
$this->getCharacterIdArgumentDefinition(),
new InputOption(
"onlyViewpoint",
mode: InputOption::VALUE_NONE,
description: "Set to only display viewpoint",
)
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->game->getLogger();
$io = new SymfonyStyle($input, $output);
$id = $input->getArgument("id");
$onlyViewpoint = $input->getOption("onlyViewpoint");
// Find character
/** @var Character $character */
$character = $em->getRepository(Character::class)->find($id);
if (!$character) {
$io->error("The character with the id {$id} was not found.");
return Command::FAILURE;
}
if (!$onlyViewpoint) {
$io->title("About Character {$character->getName()}");
$io->listing([
"ID: {$character->getId()}",
"Display name: {$character->getDisplayName()}",
"Level: {$character->getLevel()}",
"Health: {$character->getHealth()}/{$character->getMaxHealth()}",
"Alive: ".($character->isAlive()?"yes":"no"),
"Attack: {$character->getAttack()}",
"Defense: {$character->getDefense()}",
]);
$io->section("Viewpoint");
} else {
$io->title("Viewpoint of {$character->getName()}");
}
$viewpoint = $character->getViewpoint();
if (!$viewpoint) {
$io->text("No viewpoint yet");
} else {
$io->text($viewpoint->getTitle() . "\n");
$io->text($viewpoint->getDescription());
$io->section("Viewpoint actions");
$actionGroups = $viewpoint->getActionGroups();
$rows = [];
foreach ($actionGroups as $actionGroup) {
$rows[] = [$actionGroup->getId(), $actionGroup->getTitle(), "", "", ""];
foreach ($actionGroup->getActions() as $action) {
$rows[] = ["", "", $action->getId(), $action->getTitle(), $action->getDestinationSceneId()];
}
if (count($actionGroup->getActions())) {
$rows[] = new TableSeparator();
}
}
$io->table(["Group id", "Group name", "Action id", "Action name", "Destination"], $rows);
}
return Command::SUCCESS;
}
}
+16 -11
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
namespace LotGD\Core\Console\Command;
use LotGD\Core\Game;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -17,26 +19,29 @@ class ConsoleCommand extends BaseCommand
protected function configure()
{
$this->setName('console')
->setDescription('Start a shell to interact with the game');
->setDescription('Start a shell to interact with the game')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
print("Daenerys console, the dragon prompt. lotgd/core " . \LotGD\Core\Game::getVersion() . ".\n");
print("Enter some PHP, but be careful, this is live and attached to your currently configured setup:\n\n");
print($this->game->getConfiguration());
print "Daenerys console, the dragon prompt. lotgd/core " . Game::getVersion() . ".\n";
print "Enter some PHP, but be careful, this is live and attached to your currently configured setup:\n\n";
print $this->game->getConfiguration();
print("\n");
print("Try things like `\$g::getVersion()`. To quit, ^D or `exit();`.\n");
print("\n");
print "\n";
print "Try things like `\$g::getVersion()`. To quit, ^D or `exit();`.\n";
print "\n";
$boris = new \Boris\Boris('🐲 > ');
$boris->setLocal(array(
'g' => $this->game
));
$boris->setLocal([
'g' => $this->game,
]);
$boris->start();
return Command::SUCCESS;
}
}
@@ -1,15 +1,13 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command;
namespace LotGD\Core\Console\Command\Database;
use LotGD\Core\Console\Command\BaseCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use LotGD\Core\Console\Main;
use LotGD\Core\Game;
/**
* Danerys command to initiate the database with default values.
*/
@@ -21,14 +19,17 @@ class DatabaseInitCommand extends BaseCommand
protected function configure()
{
$this->setName('database:init')
->setDescription('Initiates database with default values.');
->setDescription('Initiates database with default values.')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Database;
use LotGD\Core\Console\Command\BaseCommand;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Danerys command to initiate the database with default values.
*/
class DatabaseSchemaUpdateCommand extends BaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName('database:schemaUpdate')
->setDescription('Updates the database schema manually.')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$entityManager = $this->game->getEntityManager();
$metaData = $entityManager->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool($entityManager);
$schemaTool->updateSchema($metaData);
$entityManager->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use Doctrine\Persistence\ObjectRepository;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\Module;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
class ModuleBaseCommand extends BaseCommand
{
protected ?string $namespace = "module";
/**
* @return InputArgument
*/
protected function getModuleNameArgumentDefinition(): InputArgument
{
return new InputArgument(
name: "moduleName",
mode: InputArgument::REQUIRED,
description: "Name of the module, in vendor/package format",
);
}
protected function getModuleRepository(): ObjectRepository
{
return $this->game->getEntityManager()->getRepository(Module::class);
}
protected function getModuleModel(InputInterface $input): ?Module
{
return $this->game->getModuleManager()->getModule($input->getArgument("moduleName"));
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModuleConfigListCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:list"))
->setDescription('List available configuration option for a module')
->setDefinition([
$this->getModuleNameArgumentDefinition(),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$module = $this->getModuleModel($input);
if (!$module) {
$io->error("Module was not found.");
return Command::FAILURE;
}
// Create hook
$context = EventContextData::create([
"module" => $module,
"io" => $io,
"settings" => [],
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/module-config-list/".$module->getLibrary(),
contextData: $context
);
$settings = $newContext->get("settings");
$io->title("Module ".$module->getLibrary());
if (count($settings) === 0) {
$io->note("This module does not provide any settings.");
} else {
$io->table(["setting", "value", "description"], $settings);
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModuleConfigResetCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:reset"))
->setDescription('Reset a module setting')
->setDefinition([
$this->getModuleNameArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$module = $this->getModuleModel($input);
if (!$module) {
$io->error("Module was not found.");
return Command::FAILURE;
}
$io->title("Module {$module->getLibrary()}");
// Create hook
$context = EventContextData::create([
"module" => $module,
"io" => $io,
"setting" => $input->getArgument("setting"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/module-config-reset/{$module->getLibrary()}",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModuleConfigSetCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:set"))
->setDescription('Change a module setting')
->setDefinition([
$this->getModuleNameArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
new InputArgument(
"value",
InputArgument::REQUIRED,
description: "New value for the given setting.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$module = $this->getModuleModel($input);
if (!$module) {
$io->error("Module was not found.");
return Command::FAILURE;
}
$io->title("Module {$module->getLibrary()}");
// Create hook
$context = EventContextData::create([
"module" => $module,
"io" => $io,
"setting" => $input->getArgument("setting"),
"value" => $input->getArgument("value"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/module-config-set/{$module->getLibrary()}",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use LotGD\Core\Models\Module;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ModuleListCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("list"))
->setDescription('List all installed modules.')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
/** @var Module[] $modules */
$modules = $this->game->getModuleManager()->getModules();
$io->title("Installed modules");
if (count($modules) > 0) {
$listing = [];
foreach ($modules as $module) {
$listing[] = [$module->getLibrary() => $module->getCreatedAt()->format("d M Y, H:i")];
}
$io->definitionList(...$listing);
} else {
$io->note("No modules installed.");
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Module;
use Exception;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Exceptions\ClassNotFoundException;
use LotGD\Core\Exceptions\InvalidConfigurationException;
use LotGD\Core\Exceptions\ModuleAlreadyExistsException;
use LotGD\Core\Exceptions\WrongTypeException;
use LotGD\Core\LibraryConfiguration;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Danerys command to register and initiate any newly installed modules.
*/
class ModuleRegisterCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("register"))
->setDescription('Register and initialize any newly installed modules')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$modules = $this->game->getComposerManager()->getModulePackages();
$globalFlawless = true;
$registered = [];
foreach ($modules as $p) {
$flawless = $this->registerModule($p->getName(), $io, $registered);
$globalFlawless &= $flawless;
}
if (!$globalFlawless) {
$io->warning("Some module were not registered properly.");
return Command::FAILURE;
}
return Command::SUCCESS;
}
/**
* Register a given package as a module if it is of type lotdg-module. Resolves dependencies and skips already registered packages.
* @param string $packageName
* @param SymfonyStyle $io
* @param array $registered
* @return bool True if registering was flawless
* @throws InvalidConfigurationException
* @throws WrongTypeException
* @throws Exception
*/
protected function registerModule(
string $packageName,
SymfonyStyle $io,
array &$registered
): bool {
$composerRepository = $this->game->getComposerManager()->getComposer()
->getRepositoryManager()->getLocalRepository();
$package = $composerRepository->findPackage($packageName, "*");
# Skip if not a lotgd-module
if ($package->getType() !== "lotgd-module") {
return true;
}
# Skip if already registered
if (!empty($registered[$packageName])) {
return true;
}
$io->text("Reading module {$packageName} {$package->getPrettyVersion()}");
# Try to load module configuration ($moduleRoot/lotgd.yml)
try {
$library = new LibraryConfiguration($this->game->getComposerManager(), $package, $this->game->getCWD());
} catch (InvalidConfigurationException) {
$io->error("\tModule {$packageName} does not have a valid lotgd.yml in its root.");
return false;
}
# Register dependencies first.
$dependencyFlawless = true;
$dependencies = $package->getRequires();
foreach ($dependencies as $dependency) {
$dependencyFlawless &= $this->registerModule($dependency->getTarget(), $io, $registered);
}
# If $dependencyFlawless is not true anymore (as true & false == 0), we should abort as a dependency was not met.
if (!$dependencyFlawless) {
$io->warning("\t{$packageName} was not completely installed, as one of its dependencies had an "
."error during registration.");
return false;
}
try {
$this->game->getModuleManager()->register($library);
$io->success("\tRegistered new module {$packageName}");
} catch (ModuleAlreadyExistsException $e) {
$io->note("\tSkipping already registered module {$packageName}");
} catch (ClassNotFoundException $e) {
$io->error("\tError installing module {$packageName}: {$e->getMessage()}");
return false;
}
$registered[$packageName] = true;
return true;
}
}
@@ -1,40 +1,44 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command;
namespace LotGD\Core\Console\Command\Module;
use LotGD\Core\Console\Command\BaseCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Danerys command to validate installed modules.
*/
class ModuleValidateCommand extends BaseCommand
class ModuleValidateCommand extends ModuleBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName('module:validate')
->setDescription('Validate installed modules');
$this->setName($this->namespaced("validate"))
->setDescription('Validate installed modules')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$results = $this->game->getModuleManager()->validate();
if (count($results) > 0) {
if (\count($results) > 0) {
foreach ($results as $r) {
$output->writeln($r);
}
return 1;
} else {
$output->writeln("<info>LotGD modules validated</info>");
return 0;
return Command::FAILURE;
}
$output->writeln("<info>LotGD modules validated</info>");
return Command::SUCCESS;
}
}
@@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use LotGD\Core\Exceptions\ClassNotFoundException;
use LotGD\Core\Exceptions\ModuleAlreadyExistsException;
use LotGD\Core\LibraryConfiguration;
/**
* Danerys command to register and initiate any newly installed modules.
*/
class ModuleRegisterCommand extends BaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName('module:register')
->setDescription('Register and initialize any newly installed modules');
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$modules = $this->game->getComposerManager()->getModulePackages();
foreach ($modules as $p) {
$library = new LibraryConfiguration($this->game->getComposerManager(), $p, $this->game->getCWD());
$name = $library->getName();
try {
$this->game->getModuleManager()->register($library);
$output->writeln("<info>Registered new module {$name}</info>");
} catch (ModuleAlreadyExistsException $e) {
$output->writeln("Skipping already registered module {$name}");
} catch (ClassNotFoundException $e) {
$output->writeln("<error>Error installing module {$name}: " . $e->getMessage() . "</error>");
}
}
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use Exception;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\SceneTemplate;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneAddCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("add"))
->setDescription("Add a scene.")
->setDefinition(
new InputDefinition([
new InputArgument(
"title",
mode: InputArgument::REQUIRED,
description: "Scene title",
),
new InputArgument(
"description",
mode: InputArgument::OPTIONAL,
description: "Scene description",
default: "",
),
new InputOption(
"template",
mode: InputOption::VALUE_OPTIONAL,
description: "A valid, user-assignable scene template. Check sceneTemplate:list to get all available scenes.",
default: null,
)
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$title = $input->getArgument("title");
$description = $input->getArgument("description");
$templateClass = $input->getOption("template");
/* @var $template SceneTemplate */
if ($templateClass) {
$template = $em->getRepository(SceneTemplate::class)->find($templateClass);
if (!$template) {
$io->warning("Template '$template' has not been found. Set to NULL instead.");
}
} else {
$template = $templateClass;
}
$scene = new Scene(
title: $title,
description: $description,
template: $template
);
try {
$em->persist($scene);
// Commit changes
$em->flush();
} catch (Exception $e) {
$io->error("Persisting of the scene was not possible. Reason: {$e->getMessage()}.");
return Command::FAILURE;
}
$io->success("Scene was successfully created. ID: {$scene->getId()}.");
$logger->info("{$scene} was created.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use Exception;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\SceneConnectionGroup;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneAddConnectionGroupCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced('addConnectionGroup'))
->setDescription("Add a connection group to an existing scene.")
->setDefinition(
new InputDefinition([
$this->getSceneIdArgumentDefinition(),
new InputArgument("groupName", InputArgument::REQUIRED, "Internal id of the group."),
new InputArgument("groupTitle", InputArgument::REQUIRED, "Title of the group (what the character can see"),
]),
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$sceneId = $input->getArgument("id");
$groupName = $input->getArgument("groupName");
$groupTitle = $input->getArgument("groupTitle");
// Search scene
/** @var ?Scene $scene */
$scene = $em->getRepository(Scene::class)->find($sceneId);
if (!$scene) {
$io->error("The requested scene with the ID {$sceneId} was not found.");
return Command::FAILURE;
}
// Make scene connection group
$connectionGroup = new SceneConnectionGroup($groupName, $groupTitle);
// Add
try {
$scene->addConnectionGroup($connectionGroup);
// Commit changes
$em->flush();
} catch(ArgumentException $e) {
// Catches the error if a group already exists.
$io->error($e->getMessage());
return Command::FAILURE;
} catch (Exception $e) {
$io->error("An unknown error occurred: {$e->getMessage()}");
return Command::FAILURE;
}
$io->success("{$connectionGroup} successfully added.");
$logger->info("{$connectionGroup} was added to {$scene}.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\Scene;
use LotGD\Core\SceneTemplates\SceneTemplateInterface;
use Symfony\Component\Console\Input\InputArgument;
class SceneBaseCommand extends BaseCommand
{
protected ?string $namespace = "scene";
/**
* @return InputArgument
*/
protected function getSceneIdArgumentDefinition(): InputArgument
{
return new InputArgument(
name: "id",
mode: InputArgument::REQUIRED,
description: "Scene ID",
);
}
/**
* @param string $id
* @return Scene|null
*/
protected function getScene(string $id): ?Scene
{
/** @var Scene|null $scene */
$scene = $this->game->getEntityManager()->getRepository(Scene::class)->find($id);
return $scene;
}
/**
* @param Scene $scene
* @return string
*/
protected function getSceneTemplatePath(Scene $scene)
{
$sceneTemplate = "no-template";
if ($scene->getTemplate()) {
/** @var SceneTemplateInterface $templateClass */
$templateClass = $scene->getTemplate()->getClass();
$sceneTemplate = $templateClass::getNavigationEvent();
}
return $sceneTemplate;
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Events\EventContextData;
use LotGD\Core\SceneTemplates\SceneTemplateInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SceneConfigListCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:list"))
->setDescription('List available settings for a scene')
->setDefinition([
$this->getSceneIdArgumentDefinition(),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$scene = $this->getScene($input->getArgument("id"));
if (!$scene) {
$io->error("Scene was not found.");
return Command::FAILURE;
}
$sceneTemplate = $this->getSceneTemplatePath($scene);
// Create hook
$context = EventContextData::create([
"scene" => $scene,
"io" => $io,
"settings" => [],
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/scene-config-list/$sceneTemplate",
contextData: $context
);
$settings = $newContext->get("settings");
$io->title("Scene ".$scene->getTitle());
if (count($settings) === 0) {
$io->note("There are no scene settings available.");
} else {
$io->table(["setting", "value", "description"], $settings);
}
return Command::SUCCESS;
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Events\EventContextData;
use LotGD\Core\SceneTemplates\SceneTemplateInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SceneConfigResetCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:reset"))
->setDescription('Reset a scene setting')
->setDefinition([
$this->getSceneIdArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$scene = $this->getScene($input->getArgument("id"));
if (!$scene) {
$io->error("Scene was not found.");
return Command::FAILURE;
}
$sceneTemplate = $this->getSceneTemplatePath($scene);
$io->title("Scene {$scene->getTitle()}");
// Create hook
$context = EventContextData::create([
"scene" => $scene,
"io" => $io,
"setting" => $input->getArgument("setting"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/character-config-reset/{$sceneTemplate}",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Events\EventContextData;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SceneConfigSetCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("config:set"))
->setDescription('Change a scene setting')
->setDefinition([
$this->getSceneIdArgumentDefinition(),
new InputArgument(
"setting",
mode: InputArgument::REQUIRED,
description: "Name of setting, see {$this->namespaced('config:list')}.",
),
new InputArgument(
"value",
InputArgument::REQUIRED,
description: "New value for the given setting.",
),
])
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$scene = $this->getScene($input->getArgument("id"));
if (!$scene) {
$io->error("Scene was not found.");
return Command::FAILURE;
}
$sceneTemplate = $this->getSceneTemplatePath($scene);
$io->title("Scene {$scene->getTitle()}");
// Create hook
$context = EventContextData::create([
"scene" => $scene,
"io" => $io,
"setting" => $input->getArgument("setting"),
"value" => $input->getArgument("value"),
"return" => Command::FAILURE,
"reason" => "Setting does not exist.",
]);
$newContext = $this->game->getEventManager()->publish(
event: "h/lotgd/core/cli/character-config-set/{$sceneTemplate}",
contextData: $context
);
if ($newContext->get("return") != Command::SUCCESS) {
$io->error($newContext->get("reason"));
return Command::FAILURE;
}
$this->game->getEntityManager()->flush();
return Command::SUCCESS;
}
}
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use Exception;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\SceneConnectable;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneConnectCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("connect"))
->setDescription("Connects two scenes.")
->setDefinition(
new InputDefinition([
new InputArgument(
"outgoing",
mode: InputArgument::REQUIRED,
description: "Outgoing scene ID",
),
new InputArgument(
"incoming",
mode: InputArgument::REQUIRED,
description: "Incoming scene ID",
),
new InputOption(
"outgoingGroupName",
shortcut: "o",
mode: InputOption::VALUE_OPTIONAL,
description: "A valid, user-assignable scene template. Check sceneTemplate:list to get all available scenes.",
default: null,
),
new InputOption(
"incomingGroupName",
shortcut: "i",
mode: InputOption::VALUE_OPTIONAL,
description: "A valid, user-assignable scene template. Check sceneTemplate:list to get all available scenes.",
default: null,
),
new InputOption(
"directionality",
shortcut: "d",
mode: InputOption::VALUE_OPTIONAL,
description: "0 for bidirectional, 1 for unidirectional (outgoing->incoming)",
default: 0,
)
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$sceneRepository = $em->getRepository(Scene::class);
$io = new SymfonyStyle($input, $output);
/** @var ?Scene $outgoingScene */
$outgoingScene = $sceneRepository->find($input->getArgument("outgoing"));
/** @var ?Scene $incomingScene */
$incomingScene = $sceneRepository->find($input->getArgument("incoming"));
// Check of scenes actually exist
if (!$outgoingScene) {
$io->error("The outgoing scene was not found.");
return Command::FAILURE;
}
if (!$incomingScene) {
$io->error("The incoming scene was not found");
return Command::FAILURE;
}
// Get group names
$outgoingGroupName = $input->getOption("outgoingGroupName");
$incomingGroupName = $input->getOption("incomingGroupName");
/** @var SceneConnectable $outgoing */
$outgoing = null;
/** @var SceneConnectable $outgoing */
$incoming = null;
// Determine the outgoing Connectable
if ($outgoingGroupName) {
if (!$outgoingScene->hasConnectionGroup($outgoingGroupName)) {
$io->error("The outgoing scene does not have a connection group with the id {$outgoingGroupName}");
return Command::FAILURE;
} else {
$outgoing = $outgoingScene->getConnectionGroup($outgoingGroupName);
}
} else {
$outgoing = $outgoingScene;
}
// Determine the incoming Connectable
if ($incomingGroupName) {
if (!$incomingScene->hasConnectionGroup($incomingGroupName)) {
$io->error("The incoming scene does not have a connection group with the id {$incomingGroupName}");
return Command::FAILURE;
} else {
$incoming = $incomingScene->getConnectionGroup($incomingGroupName);
}
} else {
$incoming = $incomingScene;
}
// Get directionality
$directionality = intval($input->getOption("directionality"));
if ($directionality < 0 or $directionality > 1) {
$io->warning("Directionality was not either 0 or 1. It was forced to 0.");
$directionality = 0;
}
// Connect the connectables
try {
$outgoing->connect($incoming, $directionality);
// Commit changes
$em->flush();
} catch (ArgumentException $e) {
$io->error("Scenes were not connected. Reason: {$e->getMessage()}.");
return Command::FAILURE;
} catch (Exception $e) {
$io->error("An unknown error occurred: {$e}");
return Command::FAILURE;
}
$io->success("The two scenes were successfully connected.");
$logger->info("Connected {$outgoingScene} to {$incomingScene}.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use Exception;
use LotGD\Core\Models\Scene;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneDisconnectCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("disconnect"))
->setDescription("Disconnects two scenes.")
->setDefinition(
new InputDefinition([
new InputArgument(
"scene1",
mode: InputArgument::REQUIRED,
description: "One scene ID",
),
new InputArgument(
"scene2",
mode: InputArgument::REQUIRED,
description: "The other scene ID",
),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$sceneRepository = $em->getRepository(Scene::class);
$io = new SymfonyStyle($input, $output);
/** @var Scene $scene1 */
$scene1 = $sceneRepository->find($input->getArgument("scene1"));
/** @var Scene $scene2 */
$scene2 = $sceneRepository->find($input->getArgument("scene2"));
if (!$scene1) {
$io->error("Scene with id {$input->getArgument('scene1')} was not found.");
return Command::FAILURE;
}
if (!$scene2) {
$io->error("Scene with id {$input->getArgument('scene2')} was not found.");
return Command::FAILURE;
}
$connection = $scene1->getConnectionTo($scene2);
if (!$connection) {
$io->error("The given scenes do not share a connection.");
return Command::FAILURE;
}
try {
// Commit changes
$em->remove($connection);
$em->flush();
} catch (Exception $e) {
$io->error("An unknown error occurred: {$e->getMessage()}");
return Command::FAILURE;
}
$io->success("The connections between the two given scenes was removed.");
$logger->info("Disconnected {$connection->getOutgoingScene()} and {$connection->getIncomingScene()}.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\Scene;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SceneListCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("list"))
->setDescription("Lists all scenes")
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$io = new SymfonyStyle($input, $output);
/** @var Scene[] $scenes */
$scenes = $em->getRepository(Scene::class)->findAll();
$table = [["id", "title", "connections", "template"], []];
foreach ($scenes as $scene) {
$table[1][] = [
$scene->getId(),
$scene->getTitle(),
count($scene->getConnectedScenes()),
$scene->getTemplate()?->getClass(),
];
}
$io->table(...$table);
return Command::SUCCESS;
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Models\Scene;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneRemoveCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("remove"))
->setDescription("Removes a scene.")
->setDefinition(
new InputDefinition([
$this->getSceneIdArgumentDefinition(),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$io = new SymfonyStyle($input, $output);
$sceneId = $input->getArgument("id");
// Get scene
/** @var Scene $scene */
$scene = $em->getRepository(Scene::class)->find($sceneId);
if (!$scene) {
$io->error("The scene with the ID {$sceneId} was not found.");
return Command::FAILURE;
}
if (!$scene->isRemovable()) {
$io->error("The scene with the ID {$sceneId} was marked as not removable. Please remove the responsible module instead.");
return Command::FAILURE;
}
// Mark for removal and flush
try {
$em->remove($scene);
$em->flush();
} catch (\Exception $e) {
$io->error("Removal of {$sceneId} was not possible: {$e->getMessage()}");
return Command::FAILURE;
}
$io->success("{$scene} was successfully removed.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\SceneConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneRemoveConnectionGroupCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("removeConnectionGroup"))
->setDescription("Removes a connection group from an existing scene.")
->setDefinition(
new InputDefinition([
$this->getSceneIdArgumentDefinition(),
new InputArgument("groupName", InputArgument::REQUIRED, "Internal id of the group."),
]),
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$logger = $this->getCliLogger();
$io = new SymfonyStyle($input, $output);
$sceneId = $input->getArgument("id");
$groupName = $input->getArgument("groupName");
// Search scene
/** @var ?Scene $scene */
$scene = $em->getRepository(Scene::class)->find($sceneId);
if (!$scene) {
$io->error("The scene with the ID {$sceneId} was not found.");
return Command::FAILURE;
}
if (!$scene->hasConnectionGroup($groupName)) {
$io->error("The scene {$sceneId} does not have a connection group with the name {$groupName}");
return Command::FAILURE;
}
$connectionGroup = $scene->getConnectionGroup($groupName);
# Mark for removal
$em->remove($connectionGroup);
# Update outgoing connections if they refer to the deleted connectionGroup
$connections = $scene->getConnections();
/** @var SceneConnection $connection */
foreach ($connections as $connection) {
if ($connection->getIncomingScene() === $scene and $connection->getIncomingConnectionGroupName() === $groupName) {
$connection->setIncomingConnectionGroupName(null);
$io->comment("Updated connection to {$connection->getOutgoingScene()->getTitle()}");
}
if ($connection->getOutgoingScene() === $scene and $connection->getOutgoingConnectionGroupName() === $groupName) {
$connection->setOutgoingConnectionGroupName(null);
$io->comment("Updated connection to {$connection->getIncomingScene()->getTitle()}");
}
}
try {
$em->flush();
} catch (\Exception $e) {
$io->error("An unknown error occurred: {$e->getMessage()}");
return Command::FAILURE;
}
$io->success("{$connectionGroup} was successfully removed");
$logger->info("Removed {$connectionGroup} from {$scene}.");
return Command::SUCCESS;
}
}
@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\Scene;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\SceneConnectable;
use LotGD\Core\Models\SceneConnection;
use LotGD\Core\Models\SceneConnectionGroup;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Resets the viewpoint of a given character.
*/
class SceneShowCommand extends SceneBaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName($this->namespaced("show"))
->setDescription("Show details about a specific scene.")
->setDefinition(
new InputDefinition([
$this->getSceneIdArgumentDefinition(),
])
)
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$io = new SymfonyStyle($input, $output);
$id = $input->getArgument("id");
/* @var $scene Scene */
$scene = $em->getRepository(Scene::class)->find($id);
if ($scene === null) {
$io->error("Scene not found.");
return Command::FAILURE;
}
$io->title("About scene '{$scene->getTitle()}'");
$io->listing([
"ID: {$scene->getId()}",
"Title: {$scene->getTitle()}",
"Template: {$scene->getTemplate()?->getClass()}",
]);
$io->text($scene->getDescription());
$io->section("Connection groups");
/** @var SceneConnectionGroup[] $connectionGroups */
$connectionGroups = $scene->getConnectionGroups();
$list = [];
foreach ($connectionGroups as $connectionGroup) {
$list[] = "{$connectionGroup->getTitle()} (id={$connectionGroup->getName()})";
}
$io->listing($list);
$io->section("Connected Scenes");
/** @var SceneConnection[] $connections */
$connections = $scene->getConnections();
$list = [];
foreach ($connections as $connection) {
# Get formatting for outgoing scene connection group name
$outgoingSceneConnectionGroup = $connection->getOutgoingConnectionGroupName();
if ($outgoingSceneConnectionGroup) {
$outgoingSceneConnectionGroup = " (on $outgoingSceneConnectionGroup)";
} else {
$outgoingSceneConnectionGroup = "";
}
# Get formatting for incoming scene connection group name
$incomingSceneConnectionGroup = $connection->getIncomingConnectionGroupName();
if ($incomingSceneConnectionGroup) {
$incomingSceneConnectionGroup = " (on $incomingSceneConnectionGroup)";
} else {
$incomingSceneConnectionGroup = " ";
}
# Treat outgoing and incoming connections slightly differently
if ($connection->getOutgoingScene() === $scene) {
$other = $connection->getIncomingScene();
# Check if the connection is bidirectional (only out (this)->in)
if ($connection->isDirectionality(SceneConnectable::Bidirectional)) {
$list[] = "this$outgoingSceneConnectionGroup <=> {$other->getTitle()}$incomingSceneConnectionGroup(id={$other->getId()})";
} else {
$list[] = "this$outgoingSceneConnectionGroup => {$other->getTitle()}$incomingSceneConnectionGroup(id={$other->getId()})";
}
} else {
$other = $connection->getOutgoingScene();
# Check if the connection is bidirectional (only out->in (this))
if ($connection->isDirectionality(SceneConnectable::Bidirectional)) {
$list[] = "this$incomingSceneConnectionGroup <=> {$other->getTitle()}$outgoingSceneConnectionGroup (id={$other->getId()})";
} else {
$list[] = "this$incomingSceneConnectionGroup <= {$other->getTitle()}$outgoingSceneConnectionGroup (id={$other->getId()})";
}
}
}
$io->listing($list);
return Command::SUCCESS;
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Console\Command\SceneTemplates;
use LotGD\Core\Console\Command\BaseCommand;
use LotGD\Core\Models\SceneTemplate;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SceneTemplateListCommand extends BaseCommand
{
/**
* @inheritDoc
*/
protected function configure()
{
$this->setName('sceneTemplate:list')
->setDescription('Lists all registered scene templates')
;
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$em = $this->game->getEntityManager();
$io = new SymfonyStyle($input, $output);
/** @var SceneTemplate[] $templates */
$templates = $em->getRepository(SceneTemplate::class)->findAll();
$table = [["class", "module", "assignable", "# scenes", "# viewpoints"], []];
foreach ($templates as $template) {
$table[1][] = [
$template->getClass(),
$template->getModule(),
$template->isUserAssignable()?"X":"-",
count($template->getOwningScenes()),
count($template->getOwningViewpoints()),
];
}
$io->table(...$table);
return Command::SUCCESS;
}
}
+79 -16
View File
@@ -3,25 +3,49 @@ declare(strict_types=1);
namespace LotGD\Core\Console;
use Symfony\Component\Console\Application;
use LotGD\Core\Bootstrap;
use LotGD\Core\Console\Command\Character\CharacterAddCommand;
use LotGD\Core\Console\Command\Character\CharacterConfigListCommand;
use LotGD\Core\Console\Command\Character\CharacterConfigResetCommand;
use LotGD\Core\Console\Command\Character\CharacterConfigSetCommand;
use LotGD\Core\Console\Command\Character\CharacterEditCommand;
use LotGD\Core\Console\Command\Character\CharacterListCommand;
use LotGD\Core\Console\Command\Character\CharacterRemoveCommand;
use LotGD\Core\Console\Command\Character\CharacterResetViewpointCommand;
use LotGD\Core\Console\Command\Character\CharacterShowCommand;
use LotGD\Core\Console\Command\ConsoleCommand;
use LotGD\Core\Console\Command\Database\DatabaseInitCommand;
use LotGD\Core\Console\Command\Database\DatabaseSchemaUpdateCommand;
use LotGD\Core\Console\Command\Module\ModuleConfigListCommand;
use LotGD\Core\Console\Command\Module\ModuleConfigResetCommand;
use LotGD\Core\Console\Command\Module\ModuleConfigSetCommand;
use LotGD\Core\Console\Command\Module\ModuleListCommand;
use LotGD\Core\Console\Command\Module\ModuleRegisterCommand;
use LotGD\Core\Console\Command\Module\ModuleValidateCommand;
use LotGD\Core\Console\Command\Scene\SceneConfigListCommand;
use LotGD\Core\Console\Command\Scene\SceneConfigResetCommand;
use LotGD\Core\Console\Command\Scene\SceneConfigSetCommand;
use LotGD\Core\Console\Command\SceneTemplates\SceneTemplateListCommand;
use LotGD\Core\Console\Command\Scene\SceneAddCommand;
use LotGD\Core\Console\Command\Scene\SceneAddConnectionGroupCommand;
use LotGD\Core\Console\Command\Scene\SceneConnectCommand;
use LotGD\Core\Console\Command\Scene\SceneDisconnectCommand;
use LotGD\Core\Console\Command\Scene\SceneListCommand;
use LotGD\Core\Console\Command\Scene\SceneRemoveCommand;
use LotGD\Core\Console\Command\Scene\SceneRemoveConnectionGroupCommand;
use LotGD\Core\Console\Command\Scene\SceneShowCommand;
use LotGD\Core\Game;
use LotGD\Core\Console\Command\{
DatabaseInitCommand,
ModuleValidateCommand,
ModuleRegisterCommand,
ConsoleCommand
};
use Symfony\Component\Console\Application;
/**
* Main execution class for the daenerys tool.
*/
class Main
{
private $application;
private $bootstrap;
private $game;
private Application $application;
private Bootstrap $bootstrap;
private Game $game;
/**
* Construct a new daenerys tool instance.
@@ -31,7 +55,7 @@ class Main
$this->application = new Application();
$this->application->setName("daenerys 🐲 ");
$this->application->setVersion("0.0.1 (lotgd/core version " . \LotGD\Core\Game::getVersion() . ")");
$this->application->setVersion("lotgd/core version " . Game::getVersion() . "");
}
/**
@@ -39,23 +63,62 @@ class Main
*/
protected function addCommands()
{
$this->application->add(new ModuleValidateCommand($this->game));
$this->application->add(new ModuleRegisterCommand($this->game));
$this->application->add(new DatabaseInitCommand($this->game));
$this->application->add(new DatabaseSchemaUpdateCommand($this->game));
$this->application->add(new ConsoleCommand($this->game));
// Module commands
$this->application->add(new ModuleConfigListCommand($this->game));
$this->application->add(new ModuleConfigResetCommand($this->game));
$this->application->add(new ModuleConfigSetCommand($this->game));
$this->application->add(new ModuleListCommand($this->game));
$this->application->add(new ModuleRegisterCommand($this->game));
$this->application->add(new ModuleValidateCommand($this->game));
// Character commands
$this->application->add(new CharacterAddCommand($this->game));
$this->application->add(new CharacterConfigListCommand($this->game));
$this->application->add(new CharacterConfigResetCommand($this->game));
$this->application->add(new CharacterConfigSetCommand($this->game));
$this->application->add(new CharacterEditCommand($this->game));
$this->application->add(new CharacterListCommand($this->game));
$this->application->add(new CharacterRemoveCommand($this->game));
$this->application->add(new CharacterResetViewpointCommand($this->game));
$this->application->add(new CharacterShowCommand($this->game));
// Scene commands
$this->application->add(new SceneAddCommand($this->game));
$this->application->add(new SceneConfigListCommand($this->game));
$this->application->add(new SceneConfigResetCommand($this->game));
$this->application->add(new SceneConfigSetCommand($this->game));
$this->application->add(new SceneListCommand($this->game));
$this->application->add(new SceneRemoveCommand($this->game));
$this->application->add(new SceneShowCommand($this->game));
// Scene connections
$this->application->add(new SceneConnectCommand($this->game));
$this->application->add(new SceneDisconnectCommand($this->game));
// Scene connection group
$this->application->add(new SceneAddConnectionGroupCommand($this->game));
$this->application->add(new SceneRemoveConnectionGroupCommand($this->game));
// Scene templates
$this->application->add(new SceneTemplateListCommand($this->game));
// Add additional ones
$this->bootstrap->addDaenerysCommands($this->application);
}
/**
* Run the danerys tool.
* Run the Daenerys tool.
*/
public function run()
{
// Bootstrap application
$this->bootstrap = new Bootstrap();
$this->game = $this->bootstrap->getGame(getcwd());
$this->game = $this->bootstrap->getGame(\getcwd());
// Add commands
$this->addCommands();
+39 -17
View File
@@ -1,6 +1,5 @@
<?php
declare (strict_types = 1);
declare(strict_types=1);
namespace LotGD\Core;
@@ -12,6 +11,7 @@ class DiceBag
/**
* Returns true $p percent of the time, where $p is between 0 and 1.
* @param float $p
* @return bool True if you are lucky, False if not.
*/
public function chance(float $p): bool
{
@@ -24,16 +24,39 @@ class DiceBag
* Generates a uniformly randomly number between $min and $max.
* @param float $min
* @param float $max
* @return float random number between $min and $max
*/
public function uniform(float $min, float $max): float
{
return (mt_rand(0, 100) / 100.0) * ($max - $min) + $min;
return (\mt_rand(0, 100) / 100.0) * ($max - $min) + $min;
}
/**
* Generates a uniformly randomly integer between $min and $max.
* @param int $min
* @param int $max
* @return int random number between $min and $max
*/
public function dice(int $min, int $max): int
{
if ($min == $max) {
return $min;
}
if ($min > $max) {
$a = $min;
$min = $max;
$max = $a;
}
return \mt_rand($min, $max);
}
/**
* Generates a normally distributed random number between $min and $max.
* @param float $min
* @param float $max
* @return float normally distributed random number
*/
public function normal(float $min, float $max): float
{
@@ -48,9 +71,9 @@ class DiceBag
$mean = ($max - $min) / 2;
$r = 0;
do {
$u1 = mt_rand() / mt_getrandmax();
$u2 = mt_rand() / mt_getrandmax();
$r = sqrt(-2 * log($u1)) * cos(2 * pi() * $u2) + $mean;
$u1 = \mt_rand() / \mt_getrandmax();
$u2 = \mt_rand() / \mt_getrandmax();
$r = \sqrt(-2 * \log($u1)) * \cos(2 * \pi() * $u2) + $mean;
} while ($r < $min || $r > $max);
return $r;
@@ -59,31 +82,30 @@ class DiceBag
/**
* This function has uniform distribution except for the extreme values, which are
* half as likely to happen.
* The code for this function was taken from LotGD in version 0.9.7
* The code for this function was taken from LotGD in version 0.9.7.
* @author MightyE, JT
* @param int $min
* @param int $max
* @param int|null $min
* @param int|null $max
* @return int
*/
public function pseudoBell(int $min = null, int $max = null): int
{
if (is_null($min)) {
return mt_rand();
if (\is_null($min)) {
return \mt_rand();
}
$min *= 1000;
if (is_null($max)) {
return (int)round(mt_rand($min)/1000, 0);
if (\is_null($max)) {
return (int)\round(\mt_rand($min) / 1000, 0);
}
$max *= 1000;
if ($min === $max) {
return (int)round($min/1000, 0);
return (int)\round($min / 1000, 0);
} elseif ($min < $max) {
return (int)round(mt_rand($min, $max)/1000, 0);
} else {
return (int)round(mt_rand($max, $min)/1000, 0);
return (int)\round(\mt_rand($min, $max) / 1000, 0);
}
return (int)\round(\mt_rand($max, $min) / 1000, 0);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine\Annotations;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\ExtendableModelInterface;
/**
* Annotation that is used to flag which entity a class extends.
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("of", type="string")
* })
*/
class Extension
{
private string $modelClass;
/**
* Extension constructor.
* @param array $attributes
* @throws ArgumentException
*/
public function __construct(array $attributes)
{
$this->modelClass = $attributes["of"];
if (!\class_exists($this->modelClass)) {
throw new ArgumentException("The class given in of must be a valid class.");
}
if (!\in_array(ExtendableModelInterface::class, \class_implements($this->modelClass))) {
throw new ArgumentException("The class given in of must implement the ExtendableModelInterface.");
}
}
/**
* Returns the model class name.
* @return string
*/
public function getModelClass(): string
{
return $this->modelClass;
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine\Annotations;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;
use LotGD\Core\Exceptions\ArgumentException;
/**
* Annotation that is used to link a static method to a model entity.
* @Annotation
* @Target("METHOD")
* @Attributes({
* @Attribute("as", type="string")
* })
*/
class ExtensionMethod
{
private string $methodName = "";
/**
* ExtensionMethod constructor.
* @param array $attributes
* @throws ArgumentException
*/
public function __construct(array $attributes)
{
$this->methodName = $attributes["as"];
if (!\is_string($this->methodName)) {
throw new ArgumentException("Property 'as' must be a string.");
}
if (\strlen($this->methodName) == 0) {
throw new ArgumentException("Property 'as' must not be an empty string.");
}
}
/**
* Returns the method name.
* @return string
*/
public function getMethodName(): string
{
return $this->methodName;
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine;
use Doctrine\ORM\Event\LifecycleEventArgs;
use LotGD\Core\Game;
use LotGD\Core\GameAwareInterface;
/**
* Class EntityPostLoadEventListener.
*/
class EntityPostLoadEventListener
{
/**
* EntityPostLoadEventListener constructor.
* @param Game $game
*/
public function __construct(private Game $game) {}
/**
* Called upon event postLoad.
* @param LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof GameAwareInterface) {
$entity->setGame($this->game);
}
}
}
+6 -4
View File
@@ -1,18 +1,20 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Events\EventContext;
interface EventHandler
{
/**
* Called when an event is published that is handled by this class.
*
* @param Game $g The game.
* @param string $event Name of this event.
* @param array $context Arbitrary dictionary representing context around this event.
* @return array|null Return an array if you want to make changes to the $context before
* @param EventContext $context Arbitrary dictionary representing context around this event.
* @return EventContext Return an array if you want to make changes to the $context before
* the next handler is called. Otherwise, return null. Any changes made will be propogated
* to the event publisher as well.
*/
public static function handleEvent(Game $g, string $event, array &$context);
public static function handleEvent(Game $g, EventContext $context): EventContext;
}
+29 -23
View File
@@ -3,29 +3,23 @@ declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Models\EventSubscription;
use LotGD\Core\EventHandler;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Events\EventContextData;
use LotGD\Core\Exceptions\ClassNotFoundException;
use LotGD\Core\Exceptions\SubscriptionNotFoundException;
use LotGD\Core\Exceptions\WrongTypeException;
use LotGD\Core\Models\EventSubscription;
/**
* Manages a simple publish/subscribe system based on regular expressions
* matching event names and running a fixed
* matching event names and running a fixed.
*/
class EventManager
{
private $g;
/**
* @param Game $g The game.
*/
public function __construct(Game $g)
{
$this->g = $g;
}
public function __construct(private Game $g) {}
/**
* Publish an event. Will immediately cause handleEvent() to be called on all
@@ -33,8 +27,10 @@ class EventManager
* are run.
*
* @param string $event The name of the event to publish.
* @param EventContextData $contextData The Data context
* @return EventContextData The changed data.
*/
public function publish(string $event, array &$context)
public function publish(string $event, EventContextData $contextData): EventContextData
{
// For right now, implement the naive approach of iterating every entry
// in the subscription database, checking the regular expression. We
@@ -42,16 +38,24 @@ class EventManager
// TODO: Add an in-memory cache here. Will likely only be in the 1000s of
// patterns, so no need to go to the remote key-value store.
$this->g->getLogger()->addDebug("Publishing event {$event}.");
$this->g->getLogger()->debug("Publishing event {$event}.");
$subscriptions = $this->getSubscriptions();
foreach ($subscriptions as $s) {
if (preg_match($s->getPattern(), $event)) {
if (\preg_match($s->getPattern(), $event)) {
$class = $s->getClass();
$this->g->getLogger()->addDebug(" Handling with {$class}.");
$c = $class::handleEvent($this->g, $event, $context);
$this->g->getLogger()->debug(" Handling with {$class}.");
$eventContext = new EventContext($event, $s->getPattern(), $contextData);
$returnedEventContext = $class::handleEvent($this->g, $eventContext);
// Overwrite contextData - contextData might be the same if nothing has changed,
// or might reference a completely new object the event handler changed a value.
$contextData = $returnedEventContext->getData();
}
}
return $contextData;
}
/**
@@ -70,7 +74,7 @@ class EventManager
*/
public function subscribe(string $pattern, string $class, string $library)
{
$this->g->getLogger()->addDebug("Subscribing event pattern={$pattern} class={$class} library={$library}");
$this->g->getLogger()->debug("Subscribing event pattern={$pattern} class={$class} library={$library}");
try {
// Can we resolve this class?
@@ -90,16 +94,17 @@ class EventManager
}
// Validate the regular expression.
if (@preg_match($pattern, '') === false) {
if (@\preg_match($pattern, '') === false) {
throw new WrongTypeException("Invalid regular expression: {$pattern}");
}
$e = EventSubscription::create([
'pattern' => $pattern,
'class' => $class,
'library' => $library
'library' => $library,
]);
$e->save($this->g->getEntityManager());
$this->g->getEntityManager()->persist($e);
}
/**
@@ -109,15 +114,16 @@ class EventManager
* @param string $pattern Regular expression, in PHP format, to match against
* published event names.
* @param string $class Fully qualified class name.
* @param string $library
* @throws SubscriptionNotFoundException if the specified subscription does not exist.
*/
public function unsubscribe(string $pattern, string $class, string $library)
{
$e = $this->g->getEntityManager()->getRepository(EventSubscription::class)->find(array(
$e = $this->g->getEntityManager()->getRepository(EventSubscription::class)->find([
'pattern' => $pattern,
'class' => $class,
'library' => $library
));
'library' => $library,
]);
if (!$e) {
throw new SubscriptionNotFoundException("Subscription not found with pattern={$pattern} class={$class} library={$library}.");
}
+28
View File
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use JetBrains\PhpStorm\ArrayShape;
use LotGD\Core\Models\Character;
/**
* Class CharacterEventData.
*/
class CharacterEventData extends EventContextData
{
#[ArrayShape([
"character" => [
"type" => Character::class,
"required" => "bool",
],
"value" => [
"type" => "mixed",
"required" => "bool",
],
])]
protected static ?array $argumentConfig = [
"character" => ["type" => Character::class, "required" => true],
"value" => ["type" => "mixed", "required" => false],
];
}
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
/**
* Class EventContext.
* @immutable
*/
class EventContext
{
/**
* EventContext constructor.
* @param string $event The published event
* @param string $matchingPattern The matching pattern
* @param EventContextData $data
*/
public function __construct(
private string $event,
private string $matchingPattern,
private EventContextData $data
) {
}
/**
* Returns the event of this context.
* @return string
*/
public function getEvent(): string
{
return $this->event;
}
/**
* Returns the matching pattern of this context.
* @return string
*/
public function getMatchingPattern(): string
{
return $this->matchingPattern;
}
/**
* Checks if the data in this event context has a certain subtype.
* @param string $type FQCN to be checked.
* @return bool
*/
public function hasDataType(string $type): bool
{
return $this->data instanceof $type ? true : false;
}
/**
* Returns the immutable data container.
* @return EventContextData
*/
public function getData(): EventContextData
{
return $this->data;
}
/**
* Returns a data field.
* @param $field
* @return mixed
*/
public function getDataField($field)
{
return $this->data->get($field);
}
/**
* Sets a data field.
* @param $field
* @param $value
*/
public function setDataField($field, $value)
{
$this->data = $this->data->set($field, $value);
}
/**
* Sets multiple data fields at once.
* @param $data
*/
public function setDataFields($data)
{
$this->data = $this->data->setFields($data);
}
/**
* Checks if given original data is the same as currently held within this context.
* @param EventContextData $originalData
* @return bool
*/
public function hasDataChanged(EventContextData $originalData): bool
{
return $this->data === $originalData ? false : true;
}
}
+183
View File
@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use LotGD\Core\Exceptions\ArgumentException;
/**
* EventContextData to provide a basic structure for managing contextual data of an event.
*
* This class must be immutable and returns always a new instance of itself for any change.
* @immutable
*/
class EventContextData
{
protected static ?array $argumentConfig = null;
/**
* protected constructor..
* @see self::create
* @param array $data
*/
protected function __construct(private array $data) {}
/**
* Creates a new instance of a data container.
*
* Sub types can change this method to force certain parameters.
* @param array $data
* @return EventContextData
*/
public static function create(array $data): self
{
if (isset(static::$argumentConfig)) {
static::checkConfiguration($data);
}
return new static($data);
}
/**
* Checks a field configuration given in self::$argumentConfig.
* @param array $data
* @throws ArgumentException
*/
public static function checkConfiguration(array $data)
{
$configuration = static::$argumentConfig;
$types = [
"mixed" => function ($x) {
return true;
},
"int" => function ($x) {
return \is_int($x);
},
"float" => function ($x) {
return \is_float($x);
},
"numeric" => function ($x) {
return \is_numeric($x);
},
"string" => function ($x) {
return \is_string($x);
},
];
$keys = \array_keys($data);
foreach ($keys as $key) {
if (!isset($configuration[$key])) {
throw new ArgumentException(\sprintf("%s does not accept a field called %s", static::class, $key));
}
}
foreach ($configuration as $key => $config) {
if ($config["required"] === true and !isset($data[$key])) {
throw new ArgumentException(\sprintf("%s must have a field called %s.", static::class, $key));
}
if (isset($types[$config["type"]])) {
if ($types[$config["type"]]($data[$key]) === false) {
throw new ArgumentException(\sprintf("The field %s of %s must be of type %s.", $key, static::class, $config["type"]));
}
} else {
if (!$data[$key] instanceof $config["type"]) {
throw new ArgumentException(\sprintf("The field %s of %s must be of type %s", $key, static::class, $config["type"]));
}
}
}
}
/**
* Returns true if container has a certain field.
* @param string $field
* @return bool
*/
public function has(string $field): bool
{
return \array_key_exists($field, $this->data);
}
/**
* Returns the value of a field.
* @param string $field
* @return mixed
*/
public function get(string $field)
{
if ($this->has($field)) {
return $this->data[$field];
}
$this->throwException($field);
}
/**
* Sets a field to a new value and returns a new data container.
* @param string $field
* @param mixed $value
* @return EventContextData
*/
public function set(string $field, mixed $value): self
{
if ($this->has($field)) {
$data = $this->data;
$data[$field] = $value;
return new static($data);
}
$this->throwException($field);
}
/**
* Sets multiple fields at once.
* @param array $data array of $field=>$value pairs
* @return EventContextData
*/
public function setFields(array $data): self
{
$data = $this->data;
foreach ($data as $field => $value) {
if ($this->has($field)) {
$data[$field] = $value;
} else {
$this->throwException($field);
}
}
return new static($data);
}
/**
* Returns a list of fields in this context.
* @return array
*/
private function getListOfFields(): array
{
return \array_keys($this->data);
}
/**
* Returns a comma separated string with all allowed fields, for debugging reasons.
* @return string
*/
private function getFormattedListOfFields(): string
{
return \substr(
\implode(", ", $this->getListOfFields()),
0,
-2
);
}
/**
* internal use only - throws an ArgumentException a field is given that's not valid.
* @param $field
* @throws ArgumentException
*/
private function throwException($field)
{
throw new ArgumentException(
"{$field} is not valid in this context, only {$this->getFormattedListOfFields()} are allowed."
);
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use JetBrains\PhpStorm\ArrayShape;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Scene;
use LotGD\Core\Models\Viewpoint;
/**
* NavigateToScene data container which can be used for navigational events.
*
* Fields are:
* referrer Scene|null
* viewpoint Viewpoint
* scene Scene
* parameters array
* redirect Scene|null
*/
class NavigateToSceneData extends EventContextData
{
/**
* NavigateToScene constructor.
* @param array $data Must contain fields referrer, viewpoint, scene, parameters and redirect; none more.
* @throws ArgumentException
*/
protected function __construct(
#[ArrayShape([
"referrer" => Scene::class . "|null",
"viewpoint" => Viewpoint::class,
"scene" => Scene::class,
"parameters" => "array",
"reddirect" => Scene::class . "|null",
])]
array $data,
) {
$mustHaveForm = ["referrer", "viewpoint", "scene", "parameters", "redirect"];
$doesHaveForm = \array_keys($data);
\sort($mustHaveForm);
\sort($doesHaveForm);
if ($doesHaveForm !== $mustHaveForm) {
throw new ArgumentException("A new NavigateToScene event must have referrer, viewpoint, scene, parameters and redirect.");
}
if ($data["referrer"] instanceof Scene === false and $data["referrer"] !== null) {
throw new ArgumentException(\sprintf(
"data[referrer] must be an instance of %s, %s given.",
Scene::class,
\get_class($data["referrer"])
));
}
if ($data["scene"] instanceof Scene === false) {
throw new ArgumentException(\sprintf(
"data[scene] must be an instance of %s, %s given.",
Scene::class,
\get_class($data["scene"])
));
}
if ($data["viewpoint"] instanceof Viewpoint === false) {
throw new ArgumentException(\sprintf(
"data[viewpoint] must be an instance of %s, %s given.",
Viewpoint::class,
\get_class($data["viewpoint"])
));
}
parent::__construct($data);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use JetBrains\PhpStorm\ArrayShape;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\Scene;
/**
* NewViewpoint data container which is used if no scene has ever been visited.
*
* Fields are:
* character Character
* scene Scene|null
*/
class NewViewpointData extends EventContextData
{
/**
* NewViewpoint constructor.
* @param array $data
* @throws ArgumentException In case $data contains invalid data.
*/
protected function __construct(
#[ArrayShape([
"character" => Character::class,
"scene" => Scene::class . "|null",
])]
array $data,
) {
if (\array_keys($data) !== ["character", "scene"]) {
throw new ArgumentException("A NewViewpoint event must have only character and scene.");
}
if (!$data["character"] instanceof Character) {
throw new ArgumentException(\sprintf(
"NewViewpoint data[character] must be an instance of %s, %s given.",
Character::class,
\get_class($data)
));
}
if ($data["scene"] !== null and !$data["scene"] instanceof Scene) {
throw new ArgumentException(\sprintf(
"NewViewpoint data[scene] must be an instance of %s or null, %s given.",
Scene::class,
\get_class($data)
));
}
parent::__construct($data);
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use JetBrains\PhpStorm\ArrayShape;
use LotGD\Core\Models\Viewpoint;
/**
* Class ViewpointDecorationEventData.
*/
class ViewpointDecorationEventData extends EventContextData
{
#[ArrayShape([
"viewpoint" => [
"type" => Viewpoint::class,
"required" => "bool",
],
])]
protected static ?array $argumentConfig = [
"viewpoint" => ["type" => Viewpoint::class, "required" => true],
];
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class ActionNotFoundException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class ArgumentEmptyException extends ArgumentException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class ArgumentException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class AttributeMissingException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BattleEventException extends BattleException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BattleException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BattleIsOverException extends BattleException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BattleNotOverException extends BattleException
{
}
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BuffListAlreadyActivatedException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class BuffSlotOccupiedException extends CoreException
{
}
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a builder is missing an argument.
*/
class BuilderException extends CoreException
{
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class CharacterNotFoundException extends CoreException
{
}
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Class CharacterStatException.
*/
class CharacterStatException extends CoreException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Class CharacterStatExistsException.
*/
class CharacterStatExistsException extends CharacterStatException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Class CharacterStatGroupExistsException.
*/
class CharacterStatGroupExistsException extends CharacterStatException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Class CharacterStatGroupNotFoundException.
*/
class CharacterStatGroupNotFoundException extends CharacterStatException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Class CharacterStatNotFoundException.
*/
class CharacterStatNotFoundException extends CharacterStatException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class ClassNotFoundException extends CoreException
{
}
-1
View File
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class CoreException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if an existing entity is tried to create again.
*/
class EntityAlreadyExistsException extends EntityException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a non-existing entity is requested.
*/
class EntityDoesNotExistException extends EntityException
{
}
+11
View File
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* A basic entity exception.
*/
class EntityException extends CoreException
{
}
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
class InsecureTwigTemplateError extends CoreException
{
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class InvalidConfigurationException extends CoreException
{
}
-1
View File
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class InvalidModelException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class IsNullException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class KeyNotFoundException extends CoreException
{
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class LibraryDoesNotExistException extends CoreException
{
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class ModuleAlreadyExistsException extends CoreException
{
}
@@ -8,5 +8,4 @@ namespace LotGD\Core\Exceptions;
*/
class ModuleDoesNotExistException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class NotImplementedException extends CoreException
{
}
+1 -2
View File
@@ -4,9 +4,8 @@ declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a specific, required argument is missing
* Exception if a specific, required argument is missing.
*/
class ParentAlreadySetException extends CoreException
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if an existing entity is tried to create again.
*/
class PermissionAlreadyExistsException extends EntityAlreadyExistsException
{
}

Some files were not shown because too many files have changed in this diff Show More