85 Commits

Author SHA1 Message Date
Basilius Sauter 049d116028 Updated version identifier for 0.5 2018-10-10 10:12:59 +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
118 changed files with 5756 additions and 1155 deletions
+1
View File
@@ -1,5 +1,6 @@
### Project related
vendor/
.idea/
logs/*
+2 -1
View File
@@ -1,7 +1,8 @@
sudo: false
language: php
php:
- '7.0'
- '7.1'
- '7.2'
install:
- composer install
script:
+7 -5
View File
@@ -2,6 +2,7 @@
"name": "lotgd/core",
"description": "Core functionality for Legend of the Green Dragon, a text-based RPG game.",
"license": "AGPL-3.0",
"version": "0.5.0",
"autoload": {
"psr-4": {
"LotGD\\Core\\": "src/",
@@ -12,14 +13,15 @@
"bin/daenerys"
],
"require": {
"php": "^7.0.0",
"php": "^7.1.0",
"composer/composer": "*",
"gedmo/doctrine-extensions": "*",
"doctrine/orm": "^2.5",
"doctrine/orm": "2.5.*",
"monolog/monolog": "^1.12",
"symfony/console": "^3.0",
"symfony/yaml": "^3.0",
"d11wtq/boris": "^1.0"
"d11wtq/boris": "^1.0",
"ramsey/uuid-doctrine": "^1.5"
},
"repositories": [
{
@@ -29,8 +31,8 @@
],
"require-dev": {
"phpunit/phpunit": "*",
"phpunit/dbunit": "*",
"phpunit/phpunit": "^5.0",
"phpunit/dbunit": "^2.0",
"block8/php-docblock-checker": "2.0.0"
}
}
Generated
+801 -436
View File
File diff suppressed because it is too large Load Diff
+43 -3
View File
@@ -11,15 +11,21 @@ class Action
{
protected $id;
protected $destinationSceneId;
protected $title = null;
protected $parameters = [];
/**
* Construct a new action with the specified Scene as its destination.
* @param int $destinationSceneId
* @param string|null $title
* @param array $parameters
*/
public function __construct(int $destinationSceneId)
public function __construct(string $destinationSceneId, ?string $title = null, array $parameters = [])
{
$this->id = bin2hex(random_bytes(8));
$this->destinationSceneId = $destinationSceneId;
$this->title = $title;
$this->parameters = $parameters;
}
/**
@@ -35,10 +41,44 @@ class Action
/**
* Return the database ID of the destination scene, where the user will
* go if they take this action.
* @return string
* @return int
*/
public function getDestinationSceneId(): int
public function getDestinationSceneId(): string
{
return $this->destinationSceneId;
}
/**
* @return null|string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* @return null|string
*/
public function setTitle(?string $title)
{
$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;
}
}
+19 -1
View File
@@ -6,7 +6,7 @@ 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';
@@ -30,6 +30,15 @@ class ActionGroup
$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
@@ -76,4 +85,13 @@ class ActionGroup
{
$this->actions = $actions;
}
/**
* Adds a single action to the list of actions.
* @param Action $action
*/
public function addAction(Action $action)
{
$this->actions[] = $action;
}
}
+38 -19
View File
@@ -16,11 +16,7 @@ use LotGD\Core\{
Models\FighterInterface
};
use LotGD\Core\Models\{
Buff,
BattleEvents\BuffMessageEvent,
BattleEvents\CriticalHitEvent,
BattleEvents\DamageEvent,
BattleEvents\DeathEvent
Buff, BattleEvents\BuffMessageEvent, BattleEvents\CriticalHitEvent, BattleEvents\DamageEvent, BattleEvents\DeathEvent, Scene
};
/**
@@ -47,7 +43,7 @@ class Battle
/**
* Battle Configuration
* @var type
* @var array
*/
protected $configuration = [
"riposteEnabled" => true,
@@ -61,31 +57,50 @@ class Battle
* @param FighterInterface $player
* @param FighterInterface $monster
*/
public function __construct(Game $game, FighterInterface $player, FighterInterface $monster)
public function __construct(Game $game, FighterInterface $player, ?FighterInterface $monster)
{
$this->game = $game;
$this->player = $player;
$this->monster = $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,
]);
}
/**
* @ToDo Do some action
* @param Game $game
* @param FighterInterface $player
* @param string $serialized
* @return Battle
*/
public function selectAction()
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;
}
/**
* Returns a list of all battle events
* @return \LotGD\Core\Collection
* @return Collection
*/
public function getEvents(): Collection
{
@@ -197,6 +212,7 @@ class Battle
/**
* Returns the winner of this fight
* @return FighterInterface
* @throws BattleNotOverException if battle is not over.
*/
public function getWinner(): FighterInterface
{
@@ -210,6 +226,7 @@ class Battle
/**
* Returns the loser of this fight
* @return FighterInterface
* @throws BattleNotOverException if battle is not over.
*/
public function getLoser(): FighterInterface
{
@@ -226,6 +243,8 @@ class Battle
* @param int $n
* @param bool $firstDamageRound Which damage rounds are calculated. Cannot be 0.
* @return int Number of fights fought.
* @throws ArgumentException if firstDamageRound is 0.
* @throws BattleIsOverException
*/
public function fightNRounds(int $n = 1, int $firstDamageRound = self::DAMAGEROUND_BOTH): int
{
@@ -346,12 +365,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;
@@ -369,13 +388,13 @@ class Battle
$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));
}
+45 -6
View File
@@ -3,6 +3,11 @@ declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\EventManager as DoctrineEventManager;
use Doctrine\Common\Util\Debug;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\Events as DoctrineEvents;
use Doctrine\ORM\ {
EntityManager,
EntityManagerInterface,
@@ -17,10 +22,8 @@ use Monolog\ {
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Application;
use LotGD\Core\ {
ComposerManager,
BootstrapInterface,
Exceptions\InvalidConfigurationException
use LotGD\Core\{
ComposerManager, BootstrapInterface, Doctrine\EntityPostLoadEventListener, Exceptions\InvalidConfigurationException
};
/**
@@ -30,7 +33,8 @@ class Bootstrap
{
private $logger;
private $game;
private $libraryConfigurationManager = [];
/** @var LibraryConfigurationManager */
private $libraryConfigurationManager;
private $annotationDirectories = [];
/**
@@ -64,7 +68,19 @@ class Bootstrap
$pdo = $this->connectToDatabase($dsn, $user, $password);
$entityManager = $this->createEntityManager($pdo);
$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;
}
@@ -158,6 +174,11 @@ class Bootstrap
// Create entity manager
$entityManager = EntityManager::create(["pdo" => $pdo], $configuration);
// Register uuid type
try {
\Doctrine\DBAL\Types\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);
@@ -194,4 +215,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);
}
}
}
}
+1 -1
View File
@@ -536,7 +536,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 {
+8 -7
View File
@@ -6,7 +6,8 @@ namespace LotGD\Core;
use Composer\{
Composer,
Factory,
IO\NullIO
IO\NullIO,
Package\CompletePackageInterface
};
use Monolog\Logger;
@@ -41,13 +42,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;
@@ -58,7 +59,7 @@ class ComposerManager
* @return PackageInterface Package corresponding to this library.
* @throws LibraryDoesNotExistException
*/
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();
+25 -2
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;
}
/**
* 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
{
+51
View File
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine\Annotations;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Doctrine\Common\Annotations\Annotation\Attribute;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\ExtendableModelInterface;
/**
* Annotation that is used to flag which entity a class extends.
* @package LotGD\Core\Doctrine
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("of", type = "string")
* })
*/
class Extension
{
/** @var string */
private $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,50 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine\Annotations;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Doctrine\Common\Annotations\Annotation\Attribute;
use LotGD\Core\Exceptions\ArgumentException;
/**
* Annotation that is used to link a static method to a model entity.
* @package LotGD\Core\Doctrine\Annotations
* @Annotation
* @Target("METHOD")
* @Attributes({
* @Attribute("as", type = "string")
* })
*/
class ExtensionMethod
{
/** @var string */
private $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,40 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Doctrine;
use Doctrine\Common\Util\Debug;
use Doctrine\ORM\Event\LifecycleEventArgs;
use LotGD\Core\Game;
use LotGD\Core\GameAwareInterface;
/**
* Class EntityPostLoadEventListener
* @package LotGD\Core\Doctrine
*/
class EntityPostLoadEventListener
{
/** @var Game $game */
private $game;
/**
* EntityPostLoadEventListener constructor.
* @param Game $g
*/
public function __construct(Game $g)
{
$this->game = $g;
}
/**
* Called upon event postLoad.
* @param LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof GameAwareInterface) {
$entity->setGame($this->game);
}
}
}
+3 -1
View File
@@ -1,6 +1,8 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Events\EventContext;
interface EventHandler
{
@@ -14,5 +16,5 @@ interface EventHandler
* 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;
}
+16 -3
View File
@@ -5,11 +5,13 @@ namespace LotGD\Core;
use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Models\EventSubscription;
use LotGD\Core\EventHandler;
use LotGD\Core\Exceptions\ClassNotFoundException;
use LotGD\Core\Exceptions\SubscriptionNotFoundException;
use LotGD\Core\Exceptions\WrongTypeException;
use LotGD\Core\Events\EventContextData;
/**
* Manages a simple publish/subscribe system based on regular expressions
@@ -33,8 +35,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
@@ -49,9 +53,17 @@ class EventManager
if (preg_match($s->getPattern(), $event)) {
$class = $s->getClass();
$this->g->getLogger()->addDebug(" Handling with {$class}.");
$c = $class::handleEvent($this->g, $event, $context);
$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;
}
/**
@@ -99,7 +111,8 @@ class EventManager
'class' => $class,
'library' => $library
]);
$e->save($this->g->getEntityManager());
$this->g->getEntityManager()->persist($e);
}
/**
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace LotGD\Core\Events;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Character;
/**
* Class CharacterEventData
* @package LotGD\Core\Events
*/
class CharacterEventData extends EventContextData
{
protected static $argumentConfig = [
"character" => ["type" => Character::class, "required" => true],
"value" => ["type" => "mixed", "required" => false]
];
}
+109
View File
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
/**
* Class EventContext
* @package LotGD\Core
* @immutable
*/
class EventContext
{
private $matchingPattern;
private $event;
private $data;
/**
* EventContext constructor.
* @param string $event The published event
* @param string $matchingPattern The matching pattern
* @param EventContextData $data
*/
public function __construct(
string $event,
string $matchingPattern,
EventContextData $data
) {
$this->event = $event;
$this->matchingPattern = $matchingPattern;
$this->data = $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;
}
}
+179
View File
@@ -0,0 +1,179 @@
<?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.
* @package LotGD\Core\Events
* @immutable
*/
class EventContextData
{
private $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 $data
* @throws ArgumentException
*/
public static function checkConfiguration($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"]));
}
}
}
}
/**
* protected constructor..
* @see self::create
* @param array $data
*/
protected function __construct(array $data)
{
$this->data = $data;
}
/**
* 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];
} else {
$this->throwException($field);
}
}
/**
* Sets a field to a new value and returns a new data container
* @param string $field
* @param $value
* @return EventContextData
*/
public function set(string $field, $value): self
{
if ($this->has($field)) {
$data = $this->data;
$data[$field] = $value;
return new static($data);
} else {
$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."
);
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use Doctrine\Common\Util\Debug;
use Doctrine\DBAL\Schema\View;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Character;
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
* @package LotGD\Core\Events
*/
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(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);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
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
* @package LotGD\Core\Events
*/
class NewViewpointData extends EventContextData
{
/**
* NewViewpoint constructor.
* @param array $data
* @throws ArgumentException In case $data contains invalid data.
*/
protected function __construct(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,18 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Events;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Models\Viewpoint;
/**
* Class ViewpointDecorationEventData
* @package LotGD\Core\Events
*/
class ViewpointDecorationEventData extends EventContextData
{
protected static $argumentConfig = [
"viewpoint" => ["type" => Viewpoint::class, "required" => true]
];
}
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a builder is missing an argument
*/
class BuilderException extends CoreException
{
}
@@ -0,0 +1,12 @@
<?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,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a non-existing entity is requested.
*/
class EntityDoesNotExistException extends EntityException
{
}
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* A basic entity exception
*/
class EntityException extends CoreException
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if an existing entity is tried to create again.
*/
class PermissionAlreadyExistsException extends EntityAlreadyExistsException
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if an existing entity is tried to create again.
*/
class PermissionDoesNotExistException extends EntityDoesNotExistException
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Exceptions;
/**
* Exception if a requested permission id has not been found.
*/
class PermissionIdNotFoundException extends EntityDoesNotExistException
{
}
+142 -46
View File
@@ -3,19 +3,18 @@ declare (strict_types=1);
namespace LotGD\Core;
use DateTime;
use Doctrine\Common\Util\Debug;
use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Events\NavigateToSceneData;
use LotGD\Core\Events\NewViewpointData;
use Monolog\Logger;
use LotGD\Core\Models\ {
Character,
Viewpoint,
Scene
use LotGD\Core\Models\{
Character, SceneConnectable, Viewpoint, Scene, SceneConnection
};
use LotGD\Core\Exceptions\ {
ActionNotFoundException,
CharacterNotFoundException,
InvalidConfigurationException,
SceneNotFoundException
ActionNotFoundException, CharacterNotFoundException, InvalidConfigurationException, SceneNotFoundException
};
/**
@@ -27,6 +26,7 @@ class Game
private $eventManager;
private $composerManager;
private $moduleManager;
private $messageManager;
private $logger;
private $configuration;
private $character;
@@ -34,8 +34,13 @@ class Game
private $cwd;
private $timeKeeper;
/**
* Construct a game. You probably want to use Bootstrap to do this.
* @param Configuration $configuration
* @param Logger $logger
* @param EntityManagerInterface $entityManager
* @param string $cwd
*/
public function __construct(
Configuration $configuration,
@@ -55,7 +60,7 @@ class Game
*/
public static function getVersion(): string
{
return '0.1.0';
return '0.5.0';
}
/**
@@ -83,24 +88,36 @@ class Game
*/
public function getModuleManager(): ModuleManager
{
if ($this->moduleManager === null) {
$this->moduleManager = new ModuleManager($this);
}
return $this->moduleManager;
}
/**
* Sets the game's module manager.
* @param ModuleManager $moduleManager
*/
public function setModuleManager(ModuleManager $moduleManager): void
{
$this->moduleManager = $moduleManager;
}
/**
* Returns the game's composer manager.
* @return ComposerManager The game's composer manager.
*/
public function getComposerManager(): ComposerManager
{
if ($this->composerManager === null) {
$this->composerManager = new ComposerManager($this->cwd);
}
return $this->composerManager;
}
/**
* Sets the game's composer manager.
* @param ComposerManager $composerManager
*/
public function setComposerManager(ComposerManager $composerManager): void
{
$this->composerManager = $composerManager;
}
/**
* Returns the game's entity manager.
* @return EntityManagerInterface The game's database entity manager.
@@ -116,29 +133,41 @@ class Game
*/
public function getEventManager(): EventManager
{
if ($this->eventManager === null) {
$this->eventManager = new EventManager($this);
}
return $this->eventManager;
}
/**
* Sets the game's event manager.
* @param EventManager $eventManager
*/
public function setEventManager(EventManager $eventManager): void
{
$this->eventManager = $eventManager;
}
/**
* Returns the game's dice bag.
* @return DiceBag
*/
public function getDiceBag(): DiceBag
{
if ($this->diceBag === null) {
$this->diceBag = new DiceBag();
}
return $this->diceBag;
}
/**
* Returns the logger instance to write logs.
* @return \Monolog\Logger
* Sets the game's dice bag.
* @param DiceBag $diceBag
*/
public function getLogger(): \Monolog\Logger
public function setDiceBag(DiceBag $diceBag): void
{
$this->diceBag = $diceBag;
}
/**
* Returns the logger instance to write logs.
* @return Logger
*/
public function getLogger(): Logger
{
return $this->logger;
}
@@ -153,14 +182,33 @@ class Game
$gameEpoch = $this->getConfiguration()->getGameEpoch();
$gameOffsetSeconds = $this->getConfiguration()->getGameOffsetSeconds();
$gameDaysPerDay = $this->getConfiguration()->getGameDaysPerDay();
$this->timeKeeper = new TimeKeeper($gameEpoch, $gameOffsetSeconds, $gameDaysPerDay);
$this->timeKeeper = new TimeKeeper($gameEpoch, new DateTime(), $gameOffsetSeconds, $gameDaysPerDay);
}
return $this->timeKeeper;
}
/**
* Returns the Message manager
* @return MessageManager
*/
public function getMessageManager(): MessageManager
{
return $this->messageManager;
}
/**
* Sets the Message Manager
* @param MessageManager $messageManager
*/
public function setMessageManager(MessageManager $messageManager): void
{
$this->messageManager = $messageManager;
}
/**
* Returns the currently configured user character.
* @return Character
* @throws CharacterNotFoundException
*/
public function getCharacter(): Character
{
@@ -182,6 +230,7 @@ class Game
/**
* Return the viewpoint for the current user.
* @return Viewpoint
* @throws InvalidConfigurationException
*/
public function getViewpoint(): Viewpoint
{
@@ -190,13 +239,14 @@ class Game
if ($v === null) {
// No viewpoint set up for this user. Run the hook to find the default
// scene.
$context = [
$contextData = NewViewpointData::create([
'character' => $this->getCharacter(),
'scene' => null
];
$this->getEventManager()->publish('h/lotgd/core/default-scene', $context);
]);
$s = $context['scene'];
$contextData = $this->getEventManager()->publish('h/lotgd/core/default-scene', $contextData);
$s = $contextData->get("scene");
if ($s === null) {
throw new InvalidConfigurationException("No subscriber to h/lotgd/core/default-scene returned a scene.");
}
@@ -236,34 +286,72 @@ class Game
// Generate the default set of actions: the default group with
// all children.
$this->getLogger()->addDebug("Building default action group...");
$defaultGroup = new ActionGroup(ActionGroup::DefaultGroup, '', 0);
$as = array_map(function ($c) {
$id = $c->getId();
$this->getLogger()->addDebug(" Adding navigation action for child sceneId={$id}");
return new Action($c->getId());
}, $scene->getChildren()->toArray());
$defaultGroup->setActions($as);
$count = count($as);
$this->getLogger()->addDebug("Total actions: {$count}");
$actionGroups = [
ActionGroup::DefaultGroup => new ActionGroup(ActionGroup::DefaultGroup, '', 0),
];
$hiddenGroup = new ActionGroup(ActionGroup::HiddenGroup, '', 100);
// Iterates through all connections and adds an action to the connected scene to the action group. If the connection
// belongs to a new connection Group, it creates a new ActionGroup.
$scene->getConnections()->map(function(SceneConnection $connection) use ($scene, &$actionGroups) {
if ($connection->getOutgoingScene() === $scene) {
// current scene is outgoing, use incoming.
$connectedScene = $connection->getIncomingScene();
$connectionGroupName = $connection->getOutgoingConnectionGroupName();
} else {
// current scene is not outgoing, thus incoming, use outgoing.
$connectedScene = $connection->getOutgoingScene();
$connectionGroupName = $connection->getIncomingConnectionGroupName();
$viewpoint->setActionGroups([$defaultGroup, $hiddenGroup]);
// Check if the connection is unidirectional - if yes, the current scene (incoming in this branch) cannot
// connect to the outgoing scene.
if ($connection->isDirectionality(SceneConnectable::Unidirectional)) {
return;
}
}
$this->getLogger()->addDebug(" Adding navigation action for child sceneId={$connectedScene->getId()}");
$action = new Action($connectedScene->getId());
if ($connectionGroupName === null) {
$actionGroups[ActionGroup::DefaultGroup]->addAction($action);
} else {
if (isset($actionGroups[$connectionGroupName])) {
$actionGroups[$connectionGroupName]->addAction($action);
} else {
$connectionGroup = $scene->getConnectionGroup($connectionGroupName);
$actionGroup = new ActionGroup($connectionGroupName, $connectionGroup->getTitle(), 0);
$actionGroup->addAction($action);
$actionGroups[$connectionGroupName] = $actionGroup;
}
}
});
// Logging
$counts = implode(", ", array_map(function($k, $v) {
return $k .count($v);
}, array_keys($actionGroups), array_values($actionGroups)));
$this->getLogger()->addDebug("Total actions: {$counts}");
$actionGroups[ActionGroup::HiddenGroup] = new ActionGroup(ActionGroup::HiddenGroup, '', 100);
$viewpoint->setActionGroups(array_values($actionGroups));
// Let and installed listeners (ie modules) make modifications to the
// new viewpoint, including the ability to redirect the user to
// a different scene, by setting $context['redirect'] to a new scene.
$context = [
$contextData = NavigateToSceneData::create([
'referrer' => $referrer,
'viewpoint' => $viewpoint,
'scene' => $scene,
'parameters' => $parameters,
'redirect' => null
];
$hook = 'h/lotgd/core/navigate-to/' . $scene->getTemplate();
$this->getEventManager()->publish($hook, $context);
]);
$scene = $context['redirect'];
$hook = 'h/lotgd/core/navigate-to/' . $scene->getTemplate();
$contextData = $this->getEventManager()->publish($hook, $contextData);
$scene = $contextData->get('redirect');
if ($scene !== null) {
$id = $scene->getId();
$this->getLogger()->debug("Redirecting to sceneId={$id}");
@@ -275,7 +363,9 @@ class Game
* Take the specified navigation action for the currently configured
* user. This action must be present in the current user's viewpoint.
* @param string $actionId The identifier of the action to take.
* @param array $paramters
* @param array $parameters
* @throws ActionNotFoundException
* @throws SceneNotFoundException
*/
public function takeAction(string $actionId, array $parameters = [])
{
@@ -288,6 +378,7 @@ class Game
if ($action === null) {
throw new ActionNotFoundException("Invalid actionId={$actionId} for current viewpoint.");
}
$actionParameters = $action->getParameters();
$sceneId = $action->getDestinationSceneId();
$scene = $this->getEntityManager()->getRepository(Scene::class)->find([
@@ -296,7 +387,12 @@ class Game
if ($scene == null) {
throw new SceneNotFoundException("Cannot find sceneId={$sceneId} specified by actionId={$actionId}.");
}
// action parameters overwrite other parameters since the former cannot be changed by the user
$parameters = array_merge($parameters, $actionParameters);
$this->navigateToScene($scene, $parameters);
$v->save($this->getEntityManager());
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
/**
* Interface for classes that are aware of the game
* @package LotGD\Core
*/
interface GameAwareInterface
{
public function setGame(Game $g);
public function getGame(): Game;
}
+169
View File
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use Doctrine\ORM\EntityManagerInterface;
use Monolog\Logger;
use LotGD\Core\Exceptions\BuilderException;
/**
* The GameBuilder class is used to build a Game object with all dependencies that are needed.
*
* You must provide $cwd, $configuration, $entityManager and a logger instance using the with* methods.
* You can provide additional class *names* for additional dependency injections using the use* methods.
* @package LotGD\Core
*/
class GameBuilder
{
private $cwd;
private $configuration;
private $entityManager;
private $logger;
private $moduleManagerClass;
private $composerManagerClass;
private $eventManagerClass;
private $diceBagClass;
private $messageManagerClass;
/**
* Creates the game instance with the prepared parameters.
* @return Game
* @throws BuilderException if at least one of cwd, configuration, entityManager or logger as not been set.
*/
public function create(): Game
{
if (isset($this->cwd, $this->configuration, $this->entityManager, $this->logger) === false) {
throw new BuilderException(
"For creating a game, you must set at least: cwd, configuration, entityManager and logger."
);
}
// construct the game
$game = new Game(
$this->configuration,
$this->logger,
$this->entityManager,
$this->cwd
);
// add additional managers to it
$moduleManager = $this->moduleManagerClass ?? ModuleManager::class;
$game->setModuleManager(new $moduleManager($game));
$composerManager = $this->composerManagerClass ?? ComposerManager::class;
$game->setComposerManager(new $composerManager($this->cwd));
$eventManager = $this->eventManagerClass ?? EventManager::class;
$game->setEventManager(new $eventManager($game));
$diceBag = $this->diceBagClass ?? DiceBag::class;
$game->setDiceBag(new $diceBag());
$messageManager=$this->messageManagerClass ?? MessageManager::class;
$game->setMessageManager(new $messageManager());
return $game;
}
/**
* Adds current working directory argument
* @param string $cwd
* @return self
*/
public function withCwd(string $cwd): self
{
$this->cwd = $cwd;
return $this;
}
/**
* Configuration
* @param Configuration $conf
* @return self
*/
public function withConfiguration(Configuration $conf): self
{
$this->configuration = $conf;
return $this;
}
/**
* Sets the logger for the game instance.
* @param EntityManagerInterface $em
* @return self
*/
public function withEntityManager(EntityManagerInterface $em): self
{
$this->entityManager = $em;
return $this;
}
/**
* Sets the logger for the game instance.
* @param Logger $logger
* @return self
*/
public function withLogger(Logger $logger): self
{
$this->logger = $logger;
return $this;
}
/**
* Sets the fqcn for the message manager to be used
* @param string $messageManagerFqcn
* @return self
*/
public function withMessageManager(string $messageManagerFqcn): self
{
$this->messageManagerClass=$messageManagerFqcn;
return $this;
}
/**
* Sets the fqcn for the module manager to be used.
* @param string $moduleManagerFqcn
* @return self
*/
public function useModuleManager(string $moduleManagerFqcn): self
{
$this->moduleManagerClass = $moduleManagerFqcn;
return $this;
}
/**
* Sets the fqcn for the composer manager to be used.
* @param string $composerManagerFqcn
* @return self
*/
public function useComposerManager(string $composerManagerFqcn): self
{
$this->composerManagerClass = $composerManagerFqcn;
return $this;
}
/**
* Sets the fqcn for the event manager to be used.
* @param string $eventManagerFqcn
* @return GameBuilder
*/
public function useEventManager(string $eventManagerFqcn): self
{
$this->eventManagerClass = $eventManagerFqcn;
return $this;
}
/**
* Sets the fqcn for the dice bag to be used.
* @param string $diceBagFqcn
* @return GameBuilder
*/
public function useDiceBag(string $diceBagFqcn): self
{
$this->diceBagClass = $diceBagFqcn;
return $this;
}
}
+4 -4
View File
@@ -43,7 +43,7 @@ class LibraryConfiguration
if ($basePackage && $basePackage->getName() === $package->getName()) {
// Whatever the base package is in this repo is at $cwd.
$path = $cwd;
} else if ($package->getType() === "lotgd-module") {
} elseif (in_array($package->getType(), ["lotgd-module", "lotgd-crate"])) {
// lotgd-modules are installed in the vendor directory.
$installationManager = $composerManager->getComposer()->getInstallationManager();
$path = $installationManager->getInstallPath($package);
@@ -124,9 +124,9 @@ class LibraryConfiguration
/**
* Returns a subkey if it exists or null.
* @param array $arguments
* @return type
* @return mixed
*/
protected function getSubKeyIfItExists(array $arguments)
public function getSubKeyIfItExists(array $arguments)
{
$parent = $this->rawConfig;
@@ -145,7 +145,7 @@ class LibraryConfiguration
* Tries to iterate an array element given by the arguments
* @param scalar $argument1,... array keys, by increasing depth
*/
protected function iterateKey(...$arguments)
public function iterateKey(...$arguments)
{
$result = $this->getSubKeyIfItExists($arguments);
+1 -1
View File
@@ -52,7 +52,7 @@ class LibraryConfigurationManager
/**
* Return an array of the library configurations.
* @return array<LibraryConfiguration>
* @return LibraryConfiguration[]
*/
public function getConfigurations(): array {
return $this->configurations;
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\Message;
use LotGD\Core\Models\MessageThread;
use LotGD\Core\Models\SystemCharacter;
/**
* Manages the message system overall
* Class MessageManager
* @package LotGD\Core
*/
class MessageManager
{
/**
* Sends a message to a MessageThread
* @param \LotGD\Core\Models\Character $from
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @param bool $systemMessage
* @return \LotGD\Core\Models\Message
* @throws Exceptions\CoreException
*/
public function send(
Character $from,
string $message,
MessageThread $thread,
bool $systemMessage = false
) {
$message = new Message($from, $message, $thread, $systemMessage);
$thread->addMessage($message);
return $message;
}
/**
* Sends a system message to a MessageThread
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @return \LotGD\Core\Models\Message
* @throws Exceptions\ArgumentException
* @throws Exceptions\CoreException
*/
public function sendSystemMessage(
string $message,
MessageThread $thread
) {
$message = new Message(SystemCharacter::getInstance(), $message, $thread, true);
$thread->addMessage($message);
return $message;
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Doctrine\Annotations\ExtensionMethod;
use LotGD\Core\Exceptions\ArgumentException;
use ReflectionClass;
use Doctrine\Common\Annotations\AnnotationReader;
use LotGD\Core\Doctrine\Annotations\Extension;
/**
* Contains method to help the extension of a model.
* @package LotGD\Core
*/
class ModelExtender
{
/** @var AnnotationReader */
private $reader;
/** @var array */
private static $classes = [];
/**
* ModelExtender constructor.
*/
public function __construct()
{
$this->reader = new AnnotationReader();
}
/**
* @param string[] $classes
*/
public function addMore(array $classes): void {
foreach($classes as $class) {
$this->add($class);
}
}
/**
* @param string $class
* @throws ArgumentException if the given class is not properly annotated.
*/
public function add(string $class): void
{
$reflectionClass = new ReflectionClass($class);
/** @var Extension $extensionAnnotation */
$extensionAnnotation = $this->reader->getClassAnnotation($reflectionClass, Extension::class);
if ($extensionAnnotation === null) {
throw new ArgumentException(sprintf("Class %s must have the class Annotation %s", $class, Extension::class));
}
$modelClass = $extensionAnnotation->getModelClass();
if (empty(self::$classes[$modelClass])) {
self::$classes[$modelClass] = [];
}
// Run through static methods
$reflectionMethods = $reflectionClass->getMethods();
foreach ($reflectionMethods as $method) {
if ($method->isStatic() === false) {
throw new ArgumentException(sprintf("Method %s must be static.", $method->getName()));
}
/** @var ExtensionMethod $extensionMethodAnnotation */
$extensionMethodAnnotation = $this->reader->getMethodAnnotation($method, ExtensionMethod::class);
$methodName = $method->getName();
self::$classes[$modelClass][$extensionMethodAnnotation->getMethodName()] = [$class, $methodName];
}
}
/**
* Returns a callback registered in the model extender globally.
* @param string $modelClassName
* @param string $methodName
* @return callable|null
*/
public static function get(string $modelClassName, string $methodName): ?callable
{
if (empty(self::$classes[$modelClassName])) {
return null;
}
if (empty(self::$classes[$modelClassName][$methodName])) {
return null;
}
return self::$classes[$modelClassName][$methodName];
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Generator;
use LotGD\Core\Exceptions\PermissionAlreadyExistsException;
use LotGD\Core\Exceptions\PermissionDoesNotExistException;
use LotGD\Core\Models\Permission;
use LotGD\Core\Models\PermissionAssociationInterface;
/**
* This abtract class provides functionality for user entities that crates might
* want to implement, such as permissions.
*/
abstract class Actor
{
/** @var array Associations between permission-id and PermissionAssociation entity. */
private $permissionIdToAssociation = [];
/**
* Needs to return a generator which iterates through all permission associations.
* @return Generator List of PermissionAssociations.
*/
abstract protected function getPermissionAssociations(): Generator;
/**
* Returns the class of the permission associations used for the entity
* implementing this class.
* @return string fully qualified class name of the permission association entity.
*/
abstract protected function getPermissionAssociationClass(): string;
/**
* Loads all associated permissions for this actor.
* @throws PermissionAssociationEntityMissingException
*/
protected function loadPermissions()
{
if (class_exists($this->getPermissionAssociationClass()) === false) {
throw new PermissionAssociationEntityMissingException(
"The method getPermissionAssociationClass does not return a valid class name."
);
}
if (empty($this->permissionIdToAssociation)) {
foreach ($this->getPermissionAssociations() as $permission) {
$this->permissionIdToAssociation[$permission->getId()] = $permission;
}
}
}
/**
* Checks if the actor is associated with a given permission. For permission
* checking, use only the PermissionManager class.
* @param string $permissionId
* @return bool
*/
public function hasPermissionSet(string $permissionId): bool
{
$this->loadPermissions();
return isset($this->permissionIdToAssociation[$permissionId]);
}
/**
* Returns the associated permission given by an id. For permission
* checking, use only the PermissionManager class.
* @param string $permissionId
* @return PermissionAssociationInterface
*/
public function getPermission(string $permissionId): PermissionAssociationInterface
{
$this->loadPermissions();
return $this->permissionIdToAssociation[$permissionId];
}
/**
* Returns the raw permission given by the id. For permission
* checking, use only the PermissionManager class.
* @param string $permissionId
* @return Permission
*/
public function getRawPermission(string $permissionId): Permission
{
$this->loadPermissions();
return $this->permissionIdToAssociation[$permissionId]->getPermission();
}
/**
* Associates a permission with this actor in a given state. For permission
* manipulation, use only the PermissionManager class.
* @param Permission $permission
* @param int $state
* @throws PermissionAlreadyExistsException
*/
public function addPermission(Permission $permission, int $state)
{
$this->loadPermissions();
if ($this->hasPermissionSet($permission->getId())) {
$permissionId = $permission->getId();
throw new PermissionAlreadyExistsException("The permission with the id {$permissionId} has already been set on this actor.");
} else {
$associationEntity = $this->getPermissionAssociationClass();
$permissionAssoc = new $associationEntity($this, $permission, $state);
$this->permissions->add($permissionAssoc);
$this->permissionIdToAssociation[$permissionAssoc->getId()] = $permissionAssoc;
}
}
/**
* Removes an associated permission from this actor by a given id. For permission
* manipulation, use only the PermissionManager class.
* @param string $permissionId
* @throws PermissionDoesNotExistException
*/
public function removePermission(string $permissionId)
{
$this->loadPermissions();
if ($this->hasPermissionSet($permissionId)) {
$permissionAssoc = $this->getPermission($permissionId);
$this->permissions->removeElement($permissionAssoc);
unset($this->permissionIdToAssociation[$permissionId]);
} else {
throw new PermissionDoesNotExistException("The permission with the id {$permissionId} has not been set on this actor.");
}
}
}
+16 -6
View File
@@ -4,26 +4,36 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* @MappedSuperclass
*/
abstract class BasicEnemy implements FighterInterface
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Id @Column(type="uuid", unique=True) */
protected $id;
/** @Column(type="string", length=50); */
private $name;
protected $name;
/** @Column(type="integer"); */
private $level;
protected $level;
/** @var int */
private $health;
protected $health;
/**
* BasicEnemy constructor. Sets uuid upon creation.
* @throws \Exception
*/
public function __construct() {
$this->id = Uuid::uuid4();
}
/**
* Returns the enemy's id
* @return int
*/
public function getId(): int
public function getId(): UuidInterface
{
return $this->id;
}
+1
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Exceptions\BattleEventException;
use LotGD\Core\Game;
/**
* A representation of something that happened in battle.
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Exceptions\BattleEventException;
use LotGD\Core\Game;
/**
* A battle event representing a message generated by a buff.
+2 -1
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -31,7 +32,7 @@ class CriticalHitEvent extends BattleEvent
*/
public function decorate(Game $game): string
{
$pureAttackersAttack = $this->attacker->getAttack($game, true);
$pureAttackersAttack = $this->attacker->getAttack(true);
if ($this->criticalAttackValue > $pureAttackersAttack * 4) {
return "You execute a MEGA power move!!!";
+1
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
+1
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Exceptions\BattleEventException;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -39,7 +40,7 @@ class MinionDamageEvent extends BattleEvent
*/
public function decorate(Game $game): string
{
parent::decorate();
parent::decorate($game);
return str_replace(
[
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models\BattleEvents;
use LotGD\Core\Exceptions\BattleEventException;
use LotGD\Core\Game;
use LotGD\Core\Models\FighterInterface;
/**
@@ -44,7 +45,7 @@ class RegenerationBuffEvent extends BattleEvent
*/
public function decorate(Game $game): string
{
parent::decorate();
parent::decorate($game);
if ($this->regeneration === 0) {
return str_replace(
+10 -6
View File
@@ -7,6 +7,8 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Exceptions\ArgumentException;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* A model representing a buff used to modify the flow of the battle.
@@ -23,7 +25,7 @@ class Buff
const ACTIVATE_NONE = 0b0000;
const ACTIVATE_ANY = 0b1111;
/** @Id @Column(type="integer") @GeneratedValue */
/** @Id @Column(type="uuid", unique=True) */
private $id;
/**
* @ManyToOne(targetEntity="Character", inversedBy="buffs")
@@ -231,7 +233,7 @@ class Buff
* Allowed buff values and their type
* @var array
*/
private $buffArrayTemplate = [
private static $buffArrayTemplate = [
"slot" => "string",
"name" => "string",
"startMessage" => "string",
@@ -282,13 +284,15 @@ class Buff
*/
public function __construct(array $buffArray)
{
$this->id = Uuid::uuid4();
foreach ($buffArray as $attribute => $value) {
// Throw exception if an attribute does not exist (to prevent spelling errors)
if (!isset($this->buffArrayTemplate[$attribute])) {
if (!isset(self::$buffArrayTemplate[$attribute])) {
throw new ArgumentException("{$attribute} is not a valid key for a buff.");
}
switch ($this->buffArrayTemplate[$attribute]) {
switch (self::$buffArrayTemplate[$attribute]) {
case "string":
if (is_string($value) === false) {
throw new ArgumentException("{$attribute} needs to be a string.");
@@ -338,7 +342,7 @@ class Buff
{
$buffArray = [];
foreach ($this->buffArrayTemplate as $attribute => $type) {
foreach (self::$buffArrayTemplate as $attribute => $type) {
$buffArray[$attribute] = $buff->$attribute;
}
@@ -349,7 +353,7 @@ class Buff
* Returns the id of the buff
* @return int
*/
public function getId(): int
public function getId(): UuidInterface
{
return $this->id;
}
+48 -15
View File
@@ -11,15 +11,14 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\{
BuffList,
Game
BuffList, Events\CharacterEventData, Game, GameAwareInterface
};
use LotGD\Core\Tools\Exceptions\BuffSlotOccupiedException;
use LotGD\Core\Exceptions\BuffSlotOccupiedException;
use LotGD\Core\Tools\Model\{
Creator,
PropertyManager,
SoftDeletable
Creator, ExtendableModel, GameAware, PropertyManager, SoftDeletable
};
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* Model for a character
@@ -27,13 +26,15 @@ use LotGD\Core\Tools\Model\{
* @Entity(repositoryClass="LotGD\Core\Models\Repositories\CharacterRepository")
* @Table(name="characters")
*/
class Character implements CharacterInterface, CreateableInterface
class Character implements CharacterInterface, CreateableInterface, GameAwareInterface, ExtendableModelInterface
{
use Creator;
use SoftDeletable;
use PropertyManager;
use GameAware;
use ExtendableModel;
/** @Id @Column(type="integer") @GeneratedValue */
/** @Id @Column(type="uuid", unique=True) */
private $id;
/** @Column(type="string", length=50); */
private $name;
@@ -74,6 +75,8 @@ class Character implements CharacterInterface, CreateableInterface
"level",
];
private $propertyClass = CharacterProperty::class;
/**
* Creates a character at full health
*/
@@ -89,6 +92,8 @@ class Character implements CharacterInterface, CreateableInterface
*/
public function __construct()
{
$this->id = Uuid::uuid4();
$this->properties = new ArrayCollection();
$this->buffs = new ArrayCollection();
$this->messageThreads = new ArrayCollection();
@@ -98,7 +103,7 @@ class Character implements CharacterInterface, CreateableInterface
* Returns the entity's id
* @return int The id
*/
public function getId(): int
public function getId(): UuidInterface
{
return $this->id;
}
@@ -142,7 +147,7 @@ class Character implements CharacterInterface, CreateableInterface
/**
* Sets the maximum health of a character to a given value. It also sets the
* health if none has been set yet.
* @param int $maxhealth
* @param int $maxHealth
*/
public function setMaxHealth(int $maxHealth)
{
@@ -192,7 +197,7 @@ class Character implements CharacterInterface, CreateableInterface
/**
* Heals the enemy
* @param int $heal
* @param type $overheal True if healing bigger than maxhealth is desired.
* @param bool $overheal True if healing bigger than maxHealth is desired.
*/
public function heal(int $heal, bool $overheal = false)
{
@@ -223,18 +228,46 @@ class Character implements CharacterInterface, CreateableInterface
/**
* Returns the character's virtual attribute "attack"
* @param bool $ignoreBuffs
* @return int
*/
public function getAttack(Game $game, bool $ignoreBuffs = false): int
public function getAttack(bool $ignoreBuffs = false): int
{
return $this->level * 2;
$baseAttack = $this->level;
$hookData = $this->getGame()->getEventManager()->publish(
"h/lotgd/core/getCharacterAttack",
CharacterEventData::create([
"character" => $this,
"value" => $baseAttack
])
);
$modifiedAttack = $hookData->get("value");
return $modifiedAttack;
}
/**
* Returns the character's virtual attribute "defense"
* @param bool $ignoreBuffs
* @return int
*/
public function getDefense(Game $game, bool $ignoreBuffs = false): int
public function getDefense(bool $ignoreBuffs = false): int
{
return $this->level * 2;
$baseDefense = $this->level;
$hookData = $this->getGame()->getEventManager()->publish(
"h/lotgd/core/getCharacterDefense",
CharacterEventData::create([
"character" => $this,
"value" => $baseDefense
])
);
$modifiedDefense = $hookData->get("value");
return $modifiedDefense;
}
/**
+3 -1
View File
@@ -3,12 +3,14 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use Ramsey\Uuid\UuidInterface;
/**
* Interface for the character model and all objects that mimick such a model.
*/
interface CharacterInterface extends FighterInterface
{
public function getId(): int;
public function getId(): UuidInterface;
public function getName(): string;
public function getDisplayName(): string;
public function getHealth(): int;
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
interface ExtendableModelInterface
{
public function __call($method, $arguments);
}
+2 -2
View File
@@ -18,8 +18,8 @@ interface FighterInterface
public function getMaxHealth(): int;
public function getHealth(): int;
public function isAlive(): bool;
public function getAttack(Game $game, bool $ignoreBuffs = false): int;
public function getDefense(Game $game, bool $ignoreBuffs = false): int;
public function getAttack(bool $ignoreBuffs = false): int;
public function getDefense(bool $ignoreBuffs = false): int;
public function damage(int $damage);
public function heal(int $heal);
public function setHealth(int $amount);
+4 -2
View File
@@ -18,10 +18,12 @@ class GameConfiguration
/** @var ArrayCollection */
private $properties;
/**
* Constructor.
* @param EntityManagerInterface $em
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
@@ -42,7 +44,7 @@ class GameConfiguration
/**
* Sets and overwrites a configuration value saved by the name
* @param string $configurationName
* @param type $configurationValue
* @param mixed $configurationValue
*/
public function set(string $configurationName, $configurationValue)
{
-20
View File
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\AutoScaleFighter;
/**
* The Monster entity
*
* @Entity
* @Table(name="masters")
*/
class Master extends BasicEnemy
{
use AutoScaleFighter;
}
+6 -36
View File
@@ -11,6 +11,7 @@ use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Exceptions\InvalidModelException;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Exceptions\ParentAlreadySetException;
use LotGD\Core\Tools\Model\Deletor;
use LotGD\Core\Tools\Model\Saveable;
@@ -37,48 +38,17 @@ class Message
/** @Column(type="boolean", nullable=false) */
private $systemMessage = false;
/**
* Sends a message to a MessageThread
* @param \LotGD\Core\Models\Character $from
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @param bool $systemMessage
* @return \LotGD\Core\Models\Message
*/
public static function send(
Character $from,
string $message,
MessageThread $thread,
bool $systemMessage = false
) {
$thread->addMessage(new self($from, $message, $thread, $systemMessage));
}
/**
* Sends a system message to a MessageThread
* @param string $message
* @param \LotGD\Core\Models\MessageThread $thread
* @return \LotGD\Core\Models\Message
*/
public static function sendSystemMessage(
string $message,
MessageThread $thread
) {
$thread->addMessage(new self(SystemCharacter::getInstance(), $message, $thread, true));
}
/**
* Constructs the message.
*
* This method has been made protected to prevent from accessing it directly. Use
* the static methods self::send() and self::sendSystemMessage() instead.
* @param \LotGD\Core\Models\Character $from
* Use the Message Manager methods send() and sendSystemMessage() instead.
* @param CharacterInterface $from
* @param string $message
* @param \LotGD\Core\Models\Thread $thread
* @param MessageThread $thread
* @param bool $systemMessage
* @throws ArgumentException
*/
protected function __construct(CharacterInterface $from, string $message, MessageThread $thread, bool $systemMessage)
public function __construct(CharacterInterface $from, string $message, MessageThread $thread, bool $systemMessage)
{
if ($from instanceof Character) {
if ($from->isDeleted() === true) {
+3 -3
View File
@@ -36,10 +36,10 @@ class MessageThread implements SaveableInterface
/**
* Constructor. Sets the (unique) threadKey.
* @param string $threadKey
* @param type $participants
* @param type $readonly
* @param array $participants
* @param bool $readonly
*/
public function __construct(string $threadKey, array $participants, $readonly = false)
public function __construct(string $threadKey, array $participants, bool $readonly = false)
{
$this->threadKey = $threadKey;
$this->readonly = $readonly;
+5 -2
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Models;
use DateTime;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\Common\Collections\ArrayCollection;
@@ -33,13 +34,15 @@ class Module implements SaveableInterface
/** @OneToMany(targetEntity="ModuleProperty", mappedBy="owner", cascade={"persist", "remove"}) */
private $properties;
private $propertyClass = ModuleProperty::class;
/**
* Construct a new module entry.
*/
public function __construct(string $library)
{
$this->properties = new ArrayCollection();
$this->createdAt = new \DateTime();
$this->createdAt = new DateTime();
$this->library = $library;
}
@@ -47,7 +50,7 @@ class Module implements SaveableInterface
* Returns the time this module was added to the system.
* @return DateTime
*/
public function getCreatedAt(): \DateTime
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
+8 -5
View File
@@ -8,6 +8,8 @@ use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* Model for the message of the day
@@ -20,7 +22,7 @@ class MotD implements CreateableInterface
use Creator;
use Deletor;
/** @Id @Column(type="integer") @GeneratedValue */
/** @Id @Column(type="uuid", unique=True) */
private $id;
/**
* @ManyToOne(targetEntity="Character", cascade={"persist"}, fetch="EAGER")
@@ -49,6 +51,7 @@ class MotD implements CreateableInterface
*/
public function __construct()
{
$this->id = Uuid::uuid4();
$this->creationTime = new \DateTime("now");
}
@@ -56,7 +59,7 @@ class MotD implements CreateableInterface
* Returns the entities ID
* @return int
*/
public function getId(): int
public function getId(): UuidInterface
{
return $this->id;
}
@@ -67,7 +70,7 @@ class MotD implements CreateableInterface
* Returns always the real author of the message, even if it is a
* system message. Use $this->getSystemMessage() to check if it is a system
* message or $this->getAppearentAuthor() to get the appearent author.
* @return \LotGD\Core\Models\Character
* @return CharacterInterface
*/
public function getAuthor(): CharacterInterface
{
@@ -76,7 +79,7 @@ class MotD implements CreateableInterface
/**
* Returns the appearent author of this message.
* @return \LotGD\Core\Models\CharacterInterface
* @return CharacterInterface
*/
public function getApparantAuthor(): CharacterInterface
{
@@ -89,7 +92,7 @@ class MotD implements CreateableInterface
/**
* Sets the author of this motd
* @param \LotGD\Core\Models\Character $author
* @param Character $author
*/
public function setAuthor(Character $author)
{
+96
View File
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
/**
* Represents a permission.
*
* @Entity()
* @Table(name="permissions")
*/
class Permission implements CreateableInterface
{
use Creator;
use Deletor;
/** @Id @Column(type="string"); */
private $id;
/** @Column(type="string") */
private $library;
/** @Column(type="string") */
private $name;
static $fillable = [
"id",
"library",
"name"
];
/**
* Returns the id of this entity.
* @return string
*/
public function getId(): string
{
return $this->id;
}
/**
* Sets this entity's id if it's not set yet.
* @param string $id
* @throws ArgumentException
*/
public function setId(string $id)
{
if (empty($this->id)) {
$this->id = $id;
}
else {
throw new ArgumentException("Cannot reset id.");
}
}
/**
* Returns the library this permission belongs to.
* @return string
*/
public function getLibrary(): string
{
return $this->library;
}
/**
* Sets the library this permission belongs to.
* @param string $library
*/
public function setLibrary(string $library)
{
$this->library = $library;
}
/**
* Gets this entity's human readable name.
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Sets this entity's human readable name.
* @param string $name
*/
public function setName(string $name)
{
$this->name = $name;
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
/**
* Extend this class to provide an association between an entity and a permission.
*/
interface PermissionAssociationInterface
{
}
@@ -43,9 +43,9 @@ class CharacterRepository extends EntityRepository
}
/**
* Find a character by ID.
* Find a character by ID, excluding soft deleted ones.
*/
public function find($id, int $deletes = self::SKIP_SOFTDELETED)
public function find($id, $lockMode=null, $lockVersion=null)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select("c")
@@ -53,7 +53,29 @@ class CharacterRepository extends EntityRepository
->where($queryBuilder->expr()->eq("c.id", ":id"))
->setParameter("id", $id);
$this->modifyQuery($queryBuilder, $deletes);
$this->modifyQuery($queryBuilder, self::SKIP_SOFTDELETED);
try {
return $queryBuilder->getQuery()->getSingleResult();
} catch (NoResultException $e) {
return null;
}
}
/**
* Finds a character id ID, including soft deleted ones.
* @param $id
* @return mixed|null
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function findWithSoftDeleted($id) {
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select("c")
->from(Character::class, "c")
->where($queryBuilder->expr()->eq("c.id", ":id"))
->setParameter("id", $id);
$this->modifyQuery($queryBuilder, self::INCLUDE_SOFTDELETED);
try {
return $queryBuilder->getQuery()->getSingleResult();
@@ -4,6 +4,7 @@ declare(strict_types = 1);
namespace LotGD\Core\Models\Repositories;
use Doctrine\Common\Util\Debug;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
+217 -80
View File
@@ -7,11 +7,13 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Column;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
use LotGD\Core\Tools\Model\SceneBasics;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* A scene is a location within the game, such as the Village or the Tavern. Designed
@@ -20,28 +22,29 @@ use LotGD\Core\Tools\Model\SceneBasics;
* @Entity
* @Table(name="scenes")
*/
class Scene implements CreateableInterface
class Scene implements CreateableInterface, SceneConnectable
{
use Creator;
use Deletor;
use SceneBasics;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Id @Column(type="string", length=36, unique=True, name="id", options={"fixed" = true}) */
protected $id;
/**
* @ManyToMany(targetEntity="Scene", mappedBy="children", cascade={"persist"})
* @OneToMany(targetEntity="SceneConnectionGroup", mappedBy="scene", cascade={"persist", "remove"})
*/
private $parents = null;
private $connectionGroups = null;
/**
* @ManyToMany(targetEntity="Scene", inversedBy="parents", cascade={"persist", "remove"})
* @JoinTable(name="paths",
* joinColumns={@JoinColumn(name="scene_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="child_scene_id", referencedColumnName="id")}
* )
* @OneToMany(targetEntity="SceneConnection", mappedBy="outgoingScene", cascade={"persist", "remove"})
*/
private $children = [];
private $outgoingConnections = null;
/**
* @OneToMany(targetEntity="SceneConnection", mappedBy="incomingScene", cascade={"persist", "remove"})
*/
private $incomingConnections = null;
/**
* @var array
@@ -49,111 +52,245 @@ class Scene implements CreateableInterface
private static $fillable = [
"title",
"description",
"parents",
"template"
];
/* @var ?ArrayCollection */
private $connectedScenes = null;
/**
* Constructor for a scene.
*/
public function __construct()
{
$this->children = new ArrayCollection();
$this->parents = new ArrayCollection();
$this->id = Uuid::uuid4()->toString();
$this->connectionGroups = new ArrayCollection();
$this->outgoingConnections = new ArrayCollection();
$this->incomingConnections = new ArrayCollection();
}
/**
* Returns the primary ID for this scene.
* @return int
*/
public function getId(): int
public function getId(): string
{
return $this->id;
}
/**
* Set the parents to the given Collection.
* @param Collection $parents
*/
public function setParents(Collection $parents)
{
// Super slow, but presumably these are short collections :)
// We should probably move to a set collection at some point.
$oldParents = $this->parents;
$additions = $parents->filter(function($element) use ($oldParents) {
return !$oldParents->contains($element);
});
$removals = $this->parents->filter(function($element) use ($parents) {
return !$parents->contains($element);
});
foreach ($additions as $a) {
$this->addParent($a);
}
foreach ($removals as $r) {
$this->removeParent($r);
}
$this->parents = $parents;
}
/**
* Adds a parent to this scene.
* @param \LotGD\Core\Models\Scene $parent
*/
public function addParent(Scene $parent)
{
if (!$this->parents->contains($parent)) {
$this->parents->add($parent);
$parent->addChild($this);
}
}
/**
* Removes a parent from this scene.
* @param Scene $parent
*/
public function removeParent(Scene $parent)
{
$this->parents->removeElement($parent);
$parent->removeChild($this);
}
/**
* Returns all the possible parents of this scene.
* Filters all connection groups for a specific name.
* @param string $name
* @return Collection
*/
public function getParents(): Collection
private function filterConnectionGroupCollectionByName(string $name): Collection
{
return $this->parents;
return $this->connectionGroups->filter(function(SceneConnectionGroup $group) use ($name) {
if ($group->getName() === $name) {
return true;
} else {
return false;
}
});
}
/**
* Returns a list of all children registered for this scene.
* @return Collection
* Returns true if this scene has a connection group with a given name associated.
* @param string $name
* @return bool
*/
public function getChildren(): Collection
public function hasConnectionGroup(string $name): bool
{
return $this->children;
return count($this->filterConnectionGroupCollectionByName($name)) === 1 ? true : false;
}
/**
* Registers a child for this scene.
* @param \LotGD\Core\Models\Scene $child
* Returns a connection group entity associated with this scene by a given name.
* @param string $name
* @return \LotGD\Core\Models\SceneConnectionGroup
*/
protected function addChild(Scene $child)
public function getConnectionGroup(string $name): SceneConnectionGroup
{
if (!$this->children->contains($child)) {
$this->children->add($child);
return $this->filterConnectionGroupCollectionByName($name)->first();
}
/**
* Adds a connection group to this scene.
* @param SceneConnectionGroup $group
* @throws ArgumentException
*/
public function addConnectionGroup(SceneConnectionGroup $group): void
{
if ($this->connectionGroups->contains($group) === true) {
throw new ArgumentException("This entity already owns the given connection group.");
}
if ($group->getScene()) {
throw new ArgumentException("The given connection group is already owned by another scene entity.");
}
$group->setScene($this);
$this->connectionGroups->add($group);
}
/**
* Removes a connection group from this scene.
* @param \LotGD\Core\Models\SceneConnectionGroup $group
* @throws ArgumentException
*/
public function dropConnectionGroup(SceneConnectionGroup $group): void
{
if ($this->connectionGroups->contains($group) === false) {
throw new ArgumentException("This entity does not own the given connection group.");
}
$this->connectionGroups->removeElement($group);
}
/**
* Lazy loading helper function - loads all scenes that are connected to this scene.
*/
private function loadConnectedScenes(): void
{
if ($this->connectedScenes === null) {
$connectedScenes = new ArrayCollection();
foreach ($this->outgoingConnections as $connection) {
$incomingScene = $connection->getIncomingScene();
if ($connectedScenes->contains($incomingScene) === false) {
$connectedScenes->add($incomingScene);
}
}
foreach ($this->incomingConnections as $connection) {
$outgoingScenes = $connection->getOutgoingScene();
if ($connectedScenes->contains($outgoingScenes) === false) {
$connectedScenes->add($outgoingScenes);
}
}
$this->connectedScenes = $connectedScenes;
}
}
/**
* Removes a child from this scene.
* @param \LotGD\Core\Models\Scene $child
* Returns a list of scenes that are connected to this scene.
*
* This procedure can get slow, especially if there are a lot of scenes connected
* to one. Use this method only for the installation and removal of modules,
* or for administrative purposes (like a scene graph).
* @return ArrayCollection
*/
protected function removeChild(Scene $child)
public function getConnectedScenes(): ArrayCollection
{
$this->children->removeElement($child);
$this->loadConnectedScenes();
return $this->connectedScenes;
}
/**
* Checks if the given scene is connected to this entity.
* @param \LotGD\Core\Models\Scene $scene
* @return bool True if yes.
*/
public function isConnectedTo(Scene $scene): bool
{
$this->loadConnectedScenes();
if ($this->connectedScenes->contains($scene)) {
return true;
} else {
return false;
}
}
/**
* Returns all collections of this entity.
* @return Collection
*/
public function getConnections(): Collection
{
return new ArrayCollection(
array_merge(
$this->outgoingConnections->toArray(),
$this->incomingConnections->toArray()
)
);
}
/**
* Adds a connection to the outgoing connections.
* @param \LotGD\Core\Models\SceneConnection $connection
*/
public function addOutgoingConnection(SceneConnection $connection): void
{
$this->outgoingConnections->add($connection);
// If we already have loaded all connected scenes, we need to add the entry manually.
if ($this->connectedScenes !== null) {
$this->connectedScenes->add($connection->getIncomingScene());
}
}
/**
* Adds a connection to the incoming connections.
* @param \LotGD\Core\Models\SceneConnection $connection
*/
public function addIncomingConnection(SceneConnection $connection): void
{
$this->incomingConnections->add($connection);
// If we already have loaded all connected scenes, we need to add the entry manually.
if ($this->connectedScenes !== null) {
$this->connectedScenes->add($connection->getOutgoingScene());
}
}
/**
* @inheritDoc
*/
public function connect(
SceneConnectable $connectable,
int $directionality = self::Bidirectional
): SceneConnection {
if ($connectable instanceof self) {
if ($this === $connectable) {
throw new ArgumentException("Cannot connect a scene to itself.");
}
if ($this->isConnectedTo($connectable)) {
throw new ArgumentException(
"The given scene (ID {$connectable->getId()}) is already connected to this (ID {$this->getId()}) one."
);
}
$connection = new SceneConnection($this, $connectable, $directionality);
$outgoingScene = $this;
$incomingScene = $connectable;
} else {
if ($this === $connectable->getScene()) {
throw new ArgumentException("Cannot connect a scene to itself.");
}
if ($this->isConnectedTo($connectable->getScene())) {
throw new ArgumentException(
"The given scene (ID {$connectable->getId()}) is already connected to this (ID {$this->getId()}) one."
);
}
$connection = new SceneConnection($this, $connectable->getScene(), $directionality);
$connection->setIncomingConnectionGroupName($connectable->getName());
$outgoingScene = $this;
$incomingScene = $connectable->getScene();
}
$outgoingScene->addOutgoingConnection($connection);
$incomingScene->addIncomingConnection($connection);
return $connection;
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
interface SceneConnectable
{
public const Bidirectional = 0;
public const Unidirectional = 1;
public const Xordirectional = 2;
/**
* Creates an outgoing connection for this scene to the given connectable.
* @param SceneConnectable $connectable
* @param int $directionality
* @return SceneConnection
*/
public function connect(SceneConnectable $connectable, int $directionality): SceneConnection;
}
+128
View File
@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
*
* @Entity
* @Table(name="scene_connections")
*/
class SceneConnection
{
/**
* @Id
* @ManyToOne(targetEntity="Scene", inversedBy="outgoingConnections")
* @JoinColumn(name="outgoingScene", referencedColumnName="id")
*/
private $outgoingScene;
/**
* @Id
* @ManyToOne(targetEntity="Scene", inversedBy="incomingConnections")
* @JoinColumn(name="incomingScene", referencedColumnName="id")
*/
private $incomingScene;
/**
* @Column(type="integer", options={"default":0})
*/
private $directionality = 0;
/**
* @Column(type="string", nullable=True)
*/
private $outgoingConnectionGroupName;
/**
* @Column(type="string", nullable=True)
*/
private $incomingConnectionGroupName;
/**
*
* @param \LotGD\Core\Models\Scene $outgoing
* @param \LotGD\Core\Models\Scene $incoming
* @param int $directionality
*/
public function __construct(
Scene $outgoing,
Scene $incoming,
int $directionality
) {
$this->outgoingScene = $outgoing;
$this->incomingScene = $incoming;
$this->directionality = $directionality;
}
/**
* Sets the connection group name identifier of the outgoing connection.
* @param null|string $name The identifier name of the outgoing connection group.
*/
public function setOutgoingConnectionGroupName(?string $name): void
{
$this->outgoingConnectionGroupName = $name;
}
/**
* Returns the connection from name identifier of the outgoing connection.
* @return null|string
*/
public function getOutgoingConnectionGroupName(): ?string
{
return $this->outgoingConnectionGroupName;
}
/**
* Returns the outgoing Scene of this connection.
* @return Scene
*/
public function getOutgoingScene(): Scene
{
return $this->outgoingScene;
}
/**
* Sets the connection group name identifier of the incoming connection.
* @param null|string $name The identifier name of the incoming connection group.
*/
public function setIncomingConnectionGroupName(?string $name)
{
$this->incomingConnectionGroupName = $name;
}
/**
* Returns the connection group name identifier of the incoming connection.
* @return null|string
*/
public function getIncomingConnectionGroupName(): ?string
{
return $this->incomingConnectionGroupName;
}
/**
* Returns the incoming Scene of this connection.
* @return Scene
*/
public function getIncomingScene(): Scene
{
return $this->incomingScene;
}
/**
* Returns if the directionality of this entity is as given as the first parameter.
* @param int $directionality
* @return bool
*/
public function isDirectionality(int $directionality): bool
{
if ($this->directionality === $directionality) {
return true;
} else {
return false;
}
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\Deletor;
/**
*
* @Entity
* @Table(name="scene_connection_groups")
*/
class SceneConnectionGroup implements SceneConnectable
{
/**
* @Id
* @ManyToOne(targetEntity="Scene", inversedBy="outgoingConnections", cascade={"persist"})
* @JoinColumn(name="scene", referencedColumnName="id")
*/
private $scene;
/**
* @Id
* @Column(type="string")
*/
private $name;
/**
* @Column(type="string", length=255)
*/
private $title;
/**
* SceneConnectionGroup constructor.
* @param string $name Soft-identifier of the connection group, e.g. lotgd/core
* @param string $title
*/
public function __construct(string $name, string $title)
{
$this->name = $name;
$this->title = $title;
}
/**
* Returns the scene associated with this connection group.
* @return \LotGD\Core\Models\Scene
*/
public function getScene(): ?Scene
{
return $this->scene;
}
/**
* Sets the scene associated with this connection group.
* @param \LotGD\Core\Models\Scene $scene
*/
public function setScene(Scene $scene): void
{
$this->scene = $scene;
}
/**
* Returns the name-identifier of this connection group.
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns the title of this connection group.
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @inheritDoc
*/
public function connect(SceneConnectable $connectable, int $directionality = null): SceneConnection
{
if ($directionality === null) {
$connection = $this->scene->connect($connectable);
} else {
$connection = $this->scene->connect($connectable, $directionality);
}
$connection->setOutgoingConnectionGroupName($this->name);
return $connection;
}
}
+116 -6
View File
@@ -7,8 +7,11 @@ use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Action;
use LotGD\Core\ActionGroup;
use LotGD\Core\Exceptions\ArgumentException;
use LotGD\Core\Tools\Model\Creator;
use LotGD\Core\Tools\Model\SceneBasics;
use LotGD\Core\Tools\SceneDescription;
/**
* A Viewpoint is the current Scene a character is experiencing with
@@ -32,6 +35,9 @@ class Viewpoint implements CreateableInterface
/** @ManyToOne(targetEntity="Scene") */
private $scene;
/** @var SceneDescription */
private $_description;
/** @var array */
private static $fillable = [
"owner"
@@ -56,7 +62,49 @@ class Viewpoint implements CreateableInterface
}
/**
* Copies the static data from a scene to this Viewpoint entity
* Sets the description of this viewpoint.
* @param string $description
*/
public function setDescription(string $description): void
{
$this->description = $description;
$this->_description = new SceneDescription($description);
}
/**
* Clears the description
*/
public function clearDescription(): void
{
$this->description = "";
$this->_description = new SceneDescription("");
}
/**
* Returns the current description as a string
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Adds a paragraph to the existing description
* @param string $paragraph
*/
public function addDescriptionParagraph(string $paragraph)
{
if ($this->_description === null) {
$this->_description = new SceneDescription($this->description);
}
$this->_description->addParagraph($paragraph);
$this->description = (string)$this->_description;
}
/**
* Copies the static data from a scene to this Viewpoint entity.
* @param \LotGD\Core\Models\Scene $scene
*/
public function changeFromScene(Scene $scene)
@@ -71,6 +119,38 @@ class Viewpoint implements CreateableInterface
$this->setData([]);
}
/**
* Returns a restoration point that can be used to reconstruct the current viewpoint.
* @return ViewpointSnapshot
*/
public function getSnapshot(): ViewpointSnapshot
{
$snapshot = new ViewpointSnapshot(
$this->getTitle(),
$this->getDescription(),
$this->getTemplate(),
$this->getActionGroups(),
$this->getAttachments(),
$this->getData()
);
return $snapshot;
}
/**
* Changes the current viewpoint to the state saved in the given restoration point.
* @param ViewpointSnapshot $snapshot
*/
public function changeFromSnapshot(ViewpointSnapshot $snapshot)
{
$this->setTitle($snapshot->getTitle());
$this->setDescription($snapshot->getDescription());
$this->setTemplate($snapshot->getTemplate());
$this->setActionGroups($snapshot->getActionGroups());
$this->setAttachments($snapshot->getAttachments());
$this->setData($snapshot->getData());
}
/**
* Sets the template scene used to create this viewpoint.
* @param Scene $scene
@@ -108,11 +188,38 @@ class Viewpoint implements CreateableInterface
}
/**
* Finds an action group by id.
* Adds a new action group to a viewpoint
* @param ActionGroup $group The new group to add.
* @param null|string $after, optional group id that comes before.
* @throws ArgumentException
*/
public function addActionGroup(ActionGroup $group, ?string $after = null): void
{
$groupId = $group->getId();
if ($this->findActionGroupById($groupId) == true) {
throw new ArgumentException("Group {$group} is already contained in this viewpoint.");
}
if ($after === null) {
$this->actionGroups[] = $group;
} else {
$groups = [];
foreach ($this->actionGroups as $g) {
if ($g->getId() == $after) {
$groups[] = $group;
}
$groups[] = $g;
}
$this->actionGroups = $groups;
}
}
/**
* Returns an action group by id or fails.
* @param $actionGroupId
* @return ActionGroup|null
*/
public function findActionGroupById(string $actionGroupId)
public function findActionGroupById(string $actionGroupId): ?ActionGroup
{
$groups = $this->getActionGroups();
foreach ($groups as $g) {
@@ -120,6 +227,7 @@ class Viewpoint implements CreateableInterface
return $g;
}
}
return null;
}
@@ -182,7 +290,7 @@ class Viewpoint implements CreateableInterface
/**
* Returns a single data field
* @param string $fieldname Fieldname
* @param type $default default value
* @param mixed $default default value
* @return mixed
*/
public function getDataField(string $fieldname, $default = null)
@@ -201,9 +309,10 @@ class Viewpoint implements CreateableInterface
/**
* Returns the action that corresponds to the given ID, if present.
* @param string $id
* @return Action|null
*/
public function findActionById(string $id)
public function findActionById(string $id): ?Action
{
foreach ($this->getActionGroups() as $group) {
foreach ($group->getActions() as $a) {
@@ -212,6 +321,7 @@ class Viewpoint implements CreateableInterface
}
}
}
return null;
}
@@ -219,7 +329,7 @@ class Viewpoint implements CreateableInterface
* Removes any actions that correspond to a given scene ID, if present.
* @param int $id
*/
public function removeActionsWithSceneId(int $id)
public function removeActionsWithSceneId(string $id)
{
foreach ($this->getActionGroups() as $group) {
$actions = $group->getActions();
+98
View File
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Models;
/**
* Represents a complete set of viewpoint data used to restore a saved viewpoint.
* @package LotGD\Core\Models
*/
class ViewpointSnapshot
{
private $title;
private $description;
private $template;
private $actionGroups;
private $attachments;
private $data;
/**
* ViewpointRestorationPoint constructor.
* @param string $title
* @param string $description
* @param string $template
* @param array $actionGroups
* @param array $attachments
* @param array $data
*/
public function __construct(
string $title,
string $description,
string $template,
array $actionGroups,
array $attachments,
array $data
)
{
$this->title = $title;
$this->description = $description;
$this->template = $template;
$this->actionGroups = $actionGroups;
$this->attachments = $attachments;
$this->data = $data;
}
/**
* Title of the viewpoint.
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Description of the viewpoint.
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Template of the viewpoint.
* @return string
*/
public function getTemplate(): string
{
return $this->template;
}
/**
* Action groups of the viewpoint.
* @return array
*/
public function getActionGroups(): array
{
return $this->actionGroups;
}
/**
* Attachements of the viewpoint.
* @return array
*/
public function getAttachments(): array
{
return $this->attachments;
}
/**
* Date of the viewpoint.
* @return array
*/
public function getData(): array
{
return $this->data;
}
}
-12
View File
@@ -9,18 +9,6 @@ use LotGD\Core\Models\Module as ModuleModel;
*/
interface Module extends 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
* 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);
/**
* Lifecycle method called when this module is initially installed. Use
* this method to perform one-time setup operations like adding tables
+35 -10
View File
@@ -3,13 +3,13 @@ declare (strict_types=1);
namespace LotGD\Core;
use Composer\Package\PackageInterface;
use Doctrine\ORM\EntityManagerInterface;
use Throwable;
use LotGD\Core\PackageConfiguration;
use LotGD\Core\Exceptions\KeyNotFoundException;
use LotGD\Core\Exceptions\ClassNotFoundException;
use LotGD\Core\Exceptions\ModuleAlreadyExistsException;
use LotGD\Core\Exceptions\ModuleDoesNotExistException;
use LotGD\Core\Exceptions\SubscriptionNotFoundException;
use LotGD\Core\Exceptions\WrongTypeException;
use LotGD\Core\Models\Module as ModuleModel;
/**
@@ -42,6 +42,7 @@ class ModuleManager
{
$name = $library->getName();
$package = $library->getComposerPackage();
$em = $this->g->getEntityManager();
$this->g->getLogger()->debug("Registering module {$name}...");
@@ -52,7 +53,8 @@ class ModuleManager
// TODO: handle error cases here.
$this->g->getLogger()->debug("Creating module model for {$name}");
$m = new ModuleModel($name);
$m->save($this->g->getEntityManager());
$em->beginTransaction();
$class = $library->getRootNamespace() . 'Module';
try {
@@ -70,15 +72,38 @@ class ModuleManager
}
// Subscribe to the module's events.
$subscriptions = $library->getSubscriptionPatterns();
foreach ($subscriptions as $s) {
$this->g->getEventManager()->subscribe($s, $class, $name);
try {
$subscriptions = $library->getSubscriptionPatterns();
foreach ($subscriptions as $s) {
$this->g->getEventManager()->subscribe($s, $class, $name);
}
} catch (\Throwable $e) {
$em->rollBack();
$em->clear();
throw $e;
}
// Run the module's onRegister handler.
$this->g->getLogger()->debug("Calling {$class}::onRegister");
$class::onRegister($this->g, $m);
$m->save($this->g->getEntityManager());
try {
$class::onRegister($this->g, $m);
$this->g->getEntityManager()->persist($m);
$this->g->getEntityManager()->flush();
$em->commit();
return;
} catch (Throwable $e) {
$em->rollBack();
$em->clear();
$this->g->getLogger()->error("Calling {$class}::onRegister failed with exception: {$e->getMessage()}");
unset($m);
// Propagate the exception.
throw $e;
}
}
}
+196
View File
@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
use LotGD\Core\Exceptions\PermissionIdNotFoundException;
use LotGD\Core\Models\Actor;
use LotGD\Core\Models\PermissionableInterface;
use LotGD\Core\Models\Permission;
/**
* The PermissionManager manages (checks and manipulates) permissions of actors.
*
* The PermissionManager class provides methods to work with permissions and is
* the only way to check and manipulate permissions. It can be used to create or
* delete permissions, to remove, allow or deny permissions to actors and to
* check whether an actor has a certain permission or if it is explicitly
* denied to him.
*
* The wording used in this class is:
* - allowed, the actor has a certain permission in the allowed state.
* - denied, the actor has a certain permission in the denied state.
*
* To make this more clear, the following table summarizes how different methods
* react.
*
* Method
* State: | Unset | Allowed | Denied
* -------------------+-------+---------+---------
* isAllowed | False | True | False
* isDenied | False | False | True
* hasPermissionSet | False | True | True
*/
class PermissionManager
{
const Allowed = 1;
const Denied = -1;
const Superuser = "lotgd/core/superuser";
const AddScenes = "lotgd/core/scene/add";
const EditScenes = "lotgd/core/scene/edit";
const DeleteScenes = "lotgd/core/scene/delete";
const AddCharacters = "lotgd/core/characters/add";
const EditCharacters = "lotgd/core/characters/edit";
const DeleteCharacters = "lotgd/core/characters/delete";
private $game;
/**
* Construct a permission manager.
* @param Game $g The game.
*/
public function __construct(Game $game)
{
$this->game = $game;
}
/**
* Checks if an actor has a permission set. No assumption can be made if it's allowed or denied.
* @param \LotGD\Core\PermissionableInterface $actor
* @param string $permissionId
* @return bool True if the permission has been set, be it allowed or denied.
*/
public function hasPermissionSet(
Actor $actor,
string $permissionId
): bool {
if ($actor->hasPermissionSet($permissionId)) {
return true;
} else {
return false;
}
}
/**
* Checks if an actor is allowed a given permission.
* @param \LotGD\Core\PermissionableInterface $actor
* @param string $permissionId
* @return bool True if the actor has the permission set and it's state is allowed.
*/
public function isAllowed(
Actor $actor,
string $permissionId
): bool {
if ($actor->hasPermissionSet($permissionId)) {
return $actor->getPermission($permissionId)->checkState(static::Allowed);
} else {
return false;
}
}
/**
* Checks if an actor is denied a given permission.
* @param \LotGD\Core\PermissionableInterface $actor
* @param string $permissionId
* @return bool True if the actor has the permission set and it's state is denied.
*/
public function isDenied(
Actor $actor,
string $permissionId
): bool {
if ($actor->hasPermissionSet($permissionId)) {
return $actor->getPermission($permissionId)->checkState(static::Denied);
} else {
return false;
}
}
/**
* Retrieves a permission entity from the database by a permission id.
* @param string $permissionId
* @return Permission
* @throws PermissionIdNotFoundException
*/
private function findPermission(string $permissionId): Permission
{
$em = $this->game->getEntityManager();
$result = $em->getRepository(Permission::class)->find($permissionId);
if ($result) {
return $result;
} else {
throw new PermissionIdNotFoundException("Permission {$permissionId} was not found.");
}
}
/**
* Allows an actor a permission given by the permission id.
* @param PermissionableInterface $actor
* @param string $permissionId
*/
public function allow(
Actor $actor,
string $permissionId
) {
if ($actor->hasPermissionSet($permissionId)) {
if ($this->isAllowed($actor, $permissionId) == false) {
$permission = $actor->getPermission($permissionId);
$permission->setState(static::Allowed);
$name = $actor->getActorName();
$this->game->getLogger()->debug("Granting permission {$permissionId} to {$name} (from denied).");
}
} else {
$permission = $this->findPermission($permissionId);
$actor->addPermission($permission, static::Allowed);
$name = $actor->getActorName();
$this->game->getLogger()->debug("Granting permission {$permissionId} to {$name} (from nothing).");
}
}
/**
* Denies an actor a permission given by the permission id.
* @param PermissionableInterface $actor
* @param string $permissionId
*/
public function deny(
Actor $actor,
string $permissionId
) {
if ($actor->hasPermissionSet($permissionId)) {
if ($this->isDenied($actor, $permissionId) == false) {
$permission = $actor->getPermission($permissionId);
$permission->setState(static::Denied);
$name = $actor->getActorName();
$this->game->getLogger()->debug("Denying permission {$permissionId} from {$name} (from allowed).");
}
} else {
$permission = $this->findPermission($permissionId);
$actor->addPermission($permission, static::Denied);
$name = $actor->getActorName();
$this->game->getLogger()->debug("Denying permission {$permissionId} from {$name} (from nothing).");
}
}
/**
* Removes a permission from an actor.
* @param PermissionableInterface $actor
* @param string $permissionId
*/
public function remove(
Actor $actor,
string $permissionId
) {
if ($actor->hasPermissionSet($permissionId)) {
$permissionAssoc = $actor->getPermission($permissionId);
$actor->removePermission($permissionId);
$name = $actor->getActorName();
$this->game->getLogger()->debug("Removing permission {$permissionId} from {$name}).");
}
}
}
+32 -16
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LotGD\Core;
use DateTime;
use LotGD\Core\Exceptions\ArgumentException;
/**
* Configurable way to convert back and forth between real time and game time.
@@ -22,18 +23,33 @@ class TimeKeeper
private $secondsPerGameMinute;
private $secondsPerGameSecond;
private $now;
/**
* Construct a TimeKeeper with required configuration.
* @param DateTime $gameEpoch When in real time is game day 0.
* @param DateTime $now The current time.
* @param int $gameOffsetSeconds How many seconds from midnight on the epoch should the first game day start.
* @param int $gameDaysPerDay How many game days are in one real day.
*/
public function __construct(DateTime $gameEpoch, int $gameOffsetSeconds, int $gameDaysPerDay)
{
public function __construct(
DateTime $gameEpoch,
DateTime $now,
int $gameOffsetSeconds,
int $gameDaysPerDay
) {
$gameEpochCopy = clone($gameEpoch);
$this->adjustedEpoch = $gameEpochCopy->add(
$this->interval(0, 0, 0, 0, $gameOffsetSeconds)
);
if ($gameOffsetSeconds < 0) {
$this->adjustedEpoch = $gameEpochCopy->sub(
$this->interval(0, 0, 0, 0, $gameOffsetSeconds*-1)
);
} else {
$this->adjustedEpoch = $gameEpochCopy->add(
$this->interval(0, 0, 0, 0, $gameOffsetSeconds)
);
}
$this->theBeginning = new DateTime("0000-01-01 UTC");
$this->secondsPerGameDay = (float) $this->secondsPerDay / $gameDaysPerDay;
@@ -41,19 +57,23 @@ class TimeKeeper
$this->secondsPerGameHour = $this->secondsPerGameDay / 24;
$this->secondsPerGameMinute = $this->secondsPerGameHour / 60;
$this->secondsPerGameSecond = $this->secondsPerGameMinute / 60;
$this->now = $now;
}
/**
* Returns whether a user who is interating with the game now and last
* interacted at $lastInteractionTime should experience a New Day event.
* @param DateTime $lastInteractionTime
* @param DateTime|null $lastInteractionTime
* @return bool
*/
public function isNewDay(DateTime $lastInteractionTime): bool
public function isNewDay(?DateTime $lastInteractionTime): bool
{
if ($lastInteractionTime == null) {
return true;
}
$t1 = $this->gameTime();
$t1 = $this->getGameTime();
$t2 = $this->convertToGameTime($lastInteractionTime);
$d1 = $t1->format("Y-m-d");
$d2 = $t2->format("Y-m-d");
@@ -65,9 +85,9 @@ class TimeKeeper
* Return the current game time.
* @return DateTime
*/
public function gameTime(): DateTime
public function getGameTime(): DateTime
{
return $this->convertToGameTime(new DateTime());
return $this->convertToGameTime($this->now);
}
/**
@@ -75,9 +95,7 @@ class TimeKeeper
* @param DateTime $time Game time to convert.
* @return DateTime Real time corresponding to game time $time.
*/
public function convertFromGameTime(
DateTime $time
): DateTime {
public function convertFromGameTime(DateTime $time): DateTime {
// Game dates are in the distant past, better not use getTimestamp().
$i = $this->theBeginning->diff($time);
@@ -96,9 +114,7 @@ class TimeKeeper
* @param DateTime $time Real time to convert.
* @return DateTime Game time corresponding to real time $time.
*/
public function convertToGameTime(
DateTime $time
): DateTime {
public function convertToGameTime(DateTime $time): DateTime {
$timeUnix = $time->getTimestamp();
$epochUnix = $this->adjustedEpoch->getTimestamp();
+5 -3
View File
@@ -28,19 +28,21 @@ trait AutoScaleFighter
/**
* Returns the attack value based on the fighter's level
* @param bool $ignoreBuffs
* @return int
*/
public function getAttack(Game $game, bool $ignoreBuffs = false): int
public function getAttack(bool $ignoreBuffs = false): int
{
$level = $this->getLevel();
return (int)$level * 2 - 1;
}
/**
* Returns the defense value based on the fighter's level
* @param bool $ignoreBuffs
* @return int
*/
public function getDefense(Game $game, bool $ignoreBuffs = false): int
public function getDefense(bool $ignoreBuffs = false): int
{
$level = $this->getlevel();
return (int)floor($level*1.45);
+22
View File
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
use LotGD\Core\ModelExtender;
/**
* Trait to add the __call class required for extendable models.
* @package LotGD\Core\Tools\Model
*/
trait ExtendableModel
{
public function __call($method, $arguments)
{
$callback = ModelExtender::get(self::class, $method);
if ($callback) {
return call_user_func_array($callback, array_merge([$this], $arguments));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
use LotGD\Core\Game;
/**
* Helper trait to implement public setGame from GameAwareInterface and private getGame for internal use.
* @package LotGD\Core\Tools\Model
*/
trait GameAware
{
private $game;
public function setGame(Game $game) {
$this->game = $game;
}
public function getGame(): Game {
return $this->game;
}
}
+4 -3
View File
@@ -10,6 +10,7 @@ use LotGD\Core\{
};
use LotGD\Core\Exceptions\IsNullException;
use LotGD\Core\Models\Viewpoint;
use Ramsey\Uuid\UuidInterface;
/**
* Provides basic implementation to mock CharacterInterface.
@@ -21,7 +22,7 @@ trait MockCharacter
throw new IsNullException();
}
public function getId(): int
public function getId(): UuidInterface
{
throw new IsNullException();
}
@@ -71,12 +72,12 @@ trait MockCharacter
throw new IsNullException();
}
public function getAttack(Game $game, bool $ignoreBuffs = false): int
public function getAttack(bool $ignoreBuffs = false): int
{
throw new IsNullException();
}
public function getDefense(Game $game, bool $ignoreBuffs = false): int
public function getDefense(bool $ignoreBuffs = false): int
{
throw new IsNullException();
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools\Model;
use LotGD\Core\Models\Actor;
use LotGD\Core\Models\Permission;
use LotGD\Core\Models\PermissionableInterface;
/**
* Tools to work with a permission type field.
*/
trait PermissionAssociationable
{
/**
* @Id @ManyToOne(targetEntity="LotGD\Core\Models\Permission", inversedBy="permission")
* @JoinColumn(name="permission", referencedColumnName="id")
*/
protected $permission;
/** @Column(type="integer") */
protected $permissionState;
public function __construct(Actor $owner, Permission $permission, int $state) {
$this->owner = $owner;
$this->permission = $permission;
$this->permissionState = $state;
}
/**
* Returns the current state of the permission.
* @return int
*/
public function getState(): int
{
return $this->permissionState;
}
/**
* Sets the current state of the permission.
* @param int $state
*/
public function setState(int $state)
{
$this->permissionState = $state;
}
/**
* Checks if this permission is set to a given state.
* @param int $state
* @return bool
*/
public function checkState(int $state): bool
{
return $this->permissionState == $state ? true : false;
}
/**
* Returns the permission id.
* @see Permission->getId()
* @return string
*/
public function getId(): string
{
return $this->permission->getId();
}
/**
* Returns the permission library.
* @see Permission->getLibrary()
* @return string
*/
public function getLibrary(): string
{
return $this->permission->getLibrary();
}
/**
* Returns the Permission entity.
* @return Permission
*/
public function getPermission(): Permission
{
return $this->permission;
}
}
+26 -4
View File
@@ -10,7 +10,10 @@ trait PropertyManager
{
private $propertyStorage = null;
public function loadProperties()
/**
* Loads properties
*/
public function loadProperties(): void
{
if ($this->propertyStorage !== null) {
return;
@@ -21,6 +24,12 @@ trait PropertyManager
}
}
/**
* Returns a property with its stored type.
* @param string $name
* @param mixed $default
* @return mixed
*/
public function getProperty(string $name, $default = null)
{
$this->loadProperties();
@@ -32,7 +41,11 @@ trait PropertyManager
}
}
public function unsetProperty(string $name)
/**
* Deletes a property.
* @param string $name
*/
public function unsetProperty(string $name): void
{
$this->loadProperties();
@@ -43,14 +56,23 @@ trait PropertyManager
}
}
public function setProperty(string $name, $value)
/**
* Sets a property to a given value
* @param string $name
* @param mixed $value
*/
public function setProperty(string $name, $value): void
{
$this->loadProperties();
if (isset($this->propertyStorage[$name])) {
$this->propertyStorage[$name]->setValue($value);
} else {
$className = $this->properties->getTypeClass()->name;
if (isset($this->propertyClass)) {
$className = $this->propertyClass;
} else {
$className = $this->properties->getTypeClass()->name;
}
$property = new $className();
if (method_exists($property, "setOwner")) {
$property->setOwner($this);
+73
View File
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tools;
/**
* Abstracts a scene description and provides tools to modify the text more easily.
* Class SceneDescription
* @package LotGD\Core\Tools
*/
class SceneDescription
{
private $description = [];
/**
* SceneDescription constructor.
* @param string $description
*/
public function __construct(string $description)
{
$this->description = $this->splitIntoParagraphs($description);
}
/**
* Converts the description to a string
* @return string
*/
public function __toString(): string
{
return $this->getDescriptionBack();
}
/**
* Converts the description to a string.
* @return string
*/
public function getDescriptionBack(): string
{
return implode("\n\n", $this->description);
}
/**
* Adds a paragraph to the description. If the paragraph contains \n\n, it gets broken into multiple paragraphs first.
* @param string $paragraph
*/
public function addParagraph(string $paragraph): void
{
$paragraph = $this->splitIntoParagraphs($paragraph);
$this->description = array_merge($this->description, $paragraph);
}
/**
* Splits a given string into an array ("paragraphs")
*
* This method takes a string, normalizes line ends and then splits it at every double line break (\n\n).
* @param string $input
* @return array
*/
private function splitIntoParagraphs(string $input): array
{
$input = str_replace("\r\n", "\n", $input);
$input = str_replace("\r", "\n", $input);
$parts = explode("\n\n", $input);
foreach ($parts as $key => $part) {
if (strlen($part) === 0) {
unset($parts[$key]);
}
}
return $parts;
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash -ex
phpunit
./vendor/bin/phpunit --stop-on-failure
./vendor/bin/phpdoccheck -d src --no-ansi
+1
View File
@@ -0,0 +1 @@
CMD /C phpunit --stop-on-failure
+77 -32
View File
@@ -5,6 +5,7 @@ namespace LotGD\Core\Tests\Models;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Util\Debug;
use LotGD\Core\{
Battle,
DiceBag,
@@ -25,6 +26,9 @@ use LotGD\Core\Models\BattleEvents\{
};
use LotGD\Core\Tests\CoreModelTestCase;
use Ramsey\Uuid\Codec\OrderedTimeCodec;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactory;
class BattleTest extends CoreModelTestCase
{
@@ -33,6 +37,7 @@ class BattleTest extends CoreModelTestCase
public function getMockGame(Character $character): Game
{
mt_srand(0);
$game = $this->getMockBuilder(Game::class)
->disableOriginalConstructor()
->getMock();
@@ -51,13 +56,13 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$this->assertSame(5, $monster->getLevel());
$this->assertSame(52, $monster->getMaxHealth());
$this->assertSame(9, $monster->getAttack($this->getMockGame($character)));
$this->assertSame(7, $monster->getDefense($this->getMockGame($character)));
$this->assertSame(9, $monster->getAttack());
$this->assertSame(7, $monster->getDefense());
$this->assertSame($monster->getMaxHealth(), $monster->getHealth());
}
@@ -68,11 +73,14 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$battle = new Battle($this->getMockGame($character), $character, $monster);
$this->assertSame($character, $battle->getPlayer());
$this->assertSame($monster, $battle->getMonster());
for ($n = 0; $n < 99; $n++) {
$oldPlayerHealth = $character->getHealth();
$oldMonsterHealth = $monster->getHealth();
@@ -85,12 +93,49 @@ class BattleTest extends CoreModelTestCase
if ($battle->isOver()) {
break;
}
foreach ($battle->getEvents() as $event) {
$this->assertNotNull($event->decorate($this->getMockGame($character)));
}
}
$this->assertTrue($battle->isOver());
$this->assertTrue($character->isAlive() xor $monster->isAlive());
}
/**
* Tests if a fight can happen if it is serialized between each round.
*/
public function testFairBattleWithSerializationBetweenRounds()
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$battle = new Battle($this->getMockGame($character), $character, $monster);
$battle = $battle->serialize();
for ($n = 0; $n < 99; $n++) {
$battle = Battle::unserialize($this->getMockGame($character), $character, $battle);
$battle->fightNRounds(1);
if ($battle->isOver()) {
break;
}
foreach ($battle->getEvents() as $event) {
$this->assertNotNull($event->decorate($this->getMockGame($character)));
}
$battle = $battle->serialize();
}
$this->assertTrue($battle->isOver());
$this->assertTrue($battle->getPlayer()->isAlive() xor $battle->getMonster()->isAlive());
}
/**
* Tests a fight which the player has to win (lvl 100 vs lvl 1)
*/
@@ -98,8 +143,8 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$highLevelPlayer = $em->getRepository(Character::class)->find(2);
$lowLevelMonster = $em->getRepository(Monster::class)->find(3);
$highLevelPlayer = $em->getRepository(Character::class)->find("4d01c29b-d825-4bc7-9e6e-63525155fd37");
$lowLevelMonster = $em->getRepository(Monster::class)->find("c004bcb6-a7c1-4f9a-abc2-1711c64e23a0");
$battle = new Battle($this->getMockGame($highLevelPlayer), $highLevelPlayer, $lowLevelMonster);
@@ -131,8 +176,8 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$lowLevelPlayer = $em->getRepository(Character::class)->find(3);
$highLevelMonster = $em->getRepository(Monster::class)->find(2);
$lowLevelPlayer = $em->getRepository(Character::class)->find("c3792b61-4e34-4710-9871-65a68ac30bb4");
$highLevelMonster = $em->getRepository(Monster::class)->find("b636df29-f72d-4e2d-9850-982e783a9e94");
$battle = new Battle($this->getMockGame($lowLevelPlayer), $lowLevelPlayer, $highLevelMonster);
@@ -164,8 +209,8 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$battle = new Battle($this->getMockGame($character), $character, $monster);
@@ -179,8 +224,8 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$battle = new Battle($this->getMockGame($character), $character, $monster);
@@ -195,8 +240,8 @@ class BattleTest extends CoreModelTestCase
{
$em = $this->getEntityManager();
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
$battle = new Battle($this->getMockGame($character), $character, $monster);
@@ -216,23 +261,23 @@ class BattleTest extends CoreModelTestCase
default:
case 0:
// Fair Battle
$character = $em->getRepository(Character::class)->find(1);
$monster = $em->getRepository(Monster::class)->find(1);
$character = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$monster = $em->getRepository(Monster::class)->find("de84c507-9673-44e7-b665-9e43416b9c2f");
break;
case 1:
// very long battle
$character = $em->getRepository(Character::class)->find(4);
$monster = $em->getRepository(Monster::class)->find(3);
$character = $em->getRepository(Character::class)->find("6565b418-55f5-4a6b-8d92-a9ef81329912");
$monster = $em->getRepository(Monster::class)->find("c004bcb6-a7c1-4f9a-abc2-1711c64e23a0");
break;
case 2:
// player should win battle
$character = $em->getRepository(Character::class)->find(13);
$monster = $em->getRepository(Monster::class)->find(11);
$character = $em->getRepository(Character::class)->find("1a9f63f2-3006-4e12-b272-4fd6be518a93");
$monster = $em->getRepository(Monster::class)->find("7ca9c141-aaf8-44a5-9d04-b6f9923f3c66");
break;
case 3:
// player should lose battle
$character = $em->getRepository(Character::class)->find(11);
$monster = $em->getRepository(Monster::class)->find(13);
$character = $em->getRepository(Character::class)->find("24d71c26-f915-401c-8b3e-1932edf650ce");
$monster = $em->getRepository(Monster::class)->find("09540b93-63c9-4d82-8501-f569f63dfc4c");
break;
}
@@ -1187,8 +1232,8 @@ class BattleTest extends CoreModelTestCase
$battle = $this->provideBuffBattleParticipants(new Buff([
"slot" => "test",
"rounds" => 99,
"goodguyAttackModifier" => 2,
"goodguyDefenseModifier" => 2,
"goodguyAttackModifier" => 10,
"goodguyDefenseModifier" => 10,
"activateAt" => Buff::ACTIVATE_ROUNDSTART,
]), 3);
@@ -1290,7 +1335,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistGoodguyAttackModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
@@ -1319,7 +1364,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistGoodguyDefenseModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
@@ -1348,7 +1393,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistGoodguyDamageModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
@@ -1377,7 +1422,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistBadguyAttackModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
@@ -1406,7 +1451,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistBadguyDefenseModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
@@ -1435,7 +1480,7 @@ class BattleTest extends CoreModelTestCase
public function testBufflistBadguyDamageModifier()
{
$em = $this->getEntityManager();
$player = $em->getRepository(Character::class)->find(1);
$player = $em->getRepository(Character::class)->find("d363c077-234a-433d-834e-f1a1d3b281d8");
$game = $this->getMockGame($player);
$player->addBuff(new Buff([
+63
View File
@@ -89,4 +89,67 @@ class BootstrapTest extends \PHPUnit_Framework_TestCase
$this->assertInternalType("string", $user->getName());
$this->assertSame("Monthy", $user->getName());
}
public function testIfGameAwareEntitiesHaveAGAmeInstanceAssociatedAfterLoading()
{
$installationManager = $this->getMockBuilder(InstallationManager::class)
->disableOriginalConstructor()
->setMethods(["getInstallPath"])
->getMock();
$installationManager->method("getInstallPath")->willReturn(__DIR__ . "/FakeModule");
$composer = $this->getMockBuilder(\Composer\Composer::class)
->disableOriginalConstructor()
->setMethods(["getInstallationManager"])
->getMock();
$composer->method("getInstallationManager")->willReturn($installationManager);
$fakeModulePackage = $this->getMockBuilder(AliasPackage::class)
->disableOriginalConstructor()
->setMethods(["getType", "getAutoload"])
->getMock();
$fakeModulePackage->method("getType")->willReturn("lotgd-module");
$fakeModulePackage->method("getAutoload")->willReturn([
"psr-4" => [
"LotGD\\Core\\Tests\\FakeModule\\" => "FakeModule/"
]
]);
$composerManager = $this->getMockBuilder(ComposerManager::class)
->disableOriginalConstructor()
->setMethods(["getPackages", "getComposer", "translateNamespaceToPath"])
->getMock();
$composerManager->method("getPackages")->willReturn([$fakeModulePackage]);
$composerManager->method("getComposer")->willReturn($composer);
$composerManager
->expects($this->exactly(1))
->method("translateNamespaceToPath")
->with("LotGD\\Core\\Tests\\FakeModule\\Models\\")
->willReturn(__DIR__ . "/FakeModule/Models");
$bootstrap = $this->getMockBuilder(Bootstrap::class)
->setMethods(["createComposerManager"])
->getMock();
$bootstrap->method("createComposerManager")->willReturn($composerManager);
// run tests
$game = $bootstrap->getGame(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
// A freshly created user entity should not rely
$user = new UserEntity();
$user->setName("Testus");
$user->setGame($game);
$this->assertSame($game, $user->returnGame());
$game->getEntityManager()->persist($user);
$game->getEntityManager()->flush();
$id = $user->getId();
$this->assertInternalType("int", $id);
$game->getEntityManager()->clear();
$user = $game->getEntityManager()->getRepository(UserEntity::class)->find($id);
$this->assertSame($game, $user->returnGame());
$this->assertSame([$user->getName()], $user->getNameAsArray());
}
}
+46
View File
@@ -40,4 +40,50 @@ class ComposerManagerTest extends \PHPUnit_Framework_TestCase
$namespace = 'LotGD\\NotFound';
$this->assertNull($manager->translateNamespaceToPath($namespace));
}
public function testListPackageWithRootCwd()
{
$manager = new ComposerManager(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
$packageCount = count($manager->getPackages());
$this->assertGreaterThan(1, $packageCount);
}
public function testListPackageWithDifferentThanRootCwd()
{
$oldcwd = getcwd();
chdir($oldcwd . DIRECTORY_SEPARATOR . "tests");
$manager = new ComposerManager(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
$packageCount = count($manager->getPackages());
$this->assertGreaterThan(1, $packageCount);
chdir($oldcwd);
}
public function testGetPackageByLibraryNameWithRootCwd()
{
$manager = new ComposerManager(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
$package = $manager->getPackageForLibrary("composer/composer");
$this->assertSame("composer/composer", $package->getName());
}
public function testGetPackageByLibraryNameWithDifferentThanRootCwd()
{
$oldcwd = getcwd();
chdir($oldcwd . DIRECTORY_SEPARATOR . "tests");
$manager = new ComposerManager(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
$package = $manager->getPackageForLibrary("composer/composer");
$this->assertSame("composer/composer", $package->getName());
chdir($oldcwd);
}
}
+1 -1
View File
@@ -100,7 +100,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
list($dsn, $user, $password) = $configuration->getDatabaseConnectionDetails("/home/web/sqlite");
$this->assertNotSame($rawDSN, $dsn);
$this->assertSame("sqlite:/home/web/sqlite/db.db3", $dsn);
$this->assertSame("sqlite:/home/web/sqlite" . DIRECTORY_SEPARATOR . "db.db3", $dsn);
}
public function testIfInvalidConfigurationExceptionIsThrownIfDatabaseDSNIsMissing()
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\FakeModule\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
* @Entity
* @Table(name="Users")
*/
class UserEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string", length=50); */
private $name;
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name)
{
$this->name = $name;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace LotGD\Core\Tests\DefectiveModule;
use LotGD\Core\Exceptions\CoreException;
use LotGD\Core\Game;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Models\Character;
use LotGD\Core\Module as ModuleInterface;
use LotGD\Core\Models\Module as ModuleModel;
class DefectiveModuleException extends CoreException {}
class Module implements ModuleInterface {
public static function handleEvent(Game $g, EventContext $context): EventContext
{
return $context;
}
public static function onRegister(Game $g, ModuleModel $module)
{
$character = Character::create(["name" => "Test"]);
$character->save($g->getEntityManager());
throw new DefectiveModuleException("Exception");
}
public static function onUnregister(Game $g, ModuleModel $module)
{
}
}
+4
View File
@@ -0,0 +1,4 @@
entityNamespace: "LotGD\\Core\\Tests\\FakeModule\\Models\\"
subscriptionPatterns:
- "e/lotgd/core/tests/defective-event"
- "e/lotgd/core/tests/gom-event"
+19
View File
@@ -51,4 +51,23 @@ class DiceBagTests extends \PHPUnit_Framework_TestCase
$this->assertGreaterThanOrEqual(1, $value);
$this->assertLessThanOrEqual(3, $value);
}
public function testDice()
{
$db = new DiceBag();
$value = $db->dice(1, 6);
$this->assertGreaterThanOrEqual(1, $value);
$this->assertLessThanOrEqual(6, $value);
$value = $db->dice(5, 5);
$this->assertSame(5, $value);
$value = $db->dice(1, 3);
$this->assertGreaterThanOrEqual(1, $value);
$this->assertLessThanOrEqual(3, $value);
$value = $db->dice(3, 1);
$this->assertGreaterThanOrEqual(1, $value);
$this->assertLessThanOrEqual(3, $value);
}
}
+11 -4
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
namespace LotGD\Core\Tests;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Events\EventContextData;
use LotGD\Core\Game;
use LotGD\Core\EventManager;
use LotGD\Core\EventHandler;
@@ -20,7 +22,9 @@ class EventManagerTestInvalidSubscriber
class EventManagerTestSubscriber implements EventHandler
{
public static function handleEvent(Game $g, string $event, array &$context) {}
public static function handleEvent(Game $g, EventContext $context): EventContext {
return $context;
}
}
class EventManagerTest extends CoreModelTestCase
@@ -92,6 +96,7 @@ class EventManagerTest extends CoreModelTestCase
$library = 'lotgd/tests';
$em->subscribe($pattern, $class, $library);
$this->getEntityManager()->flush();
$sub = EventSubscription::create([
'pattern' => $pattern,
@@ -129,9 +134,11 @@ class EventManagerTest extends CoreModelTestCase
$em = new EventManager($this->g);
$event = 'test.foo.something_here';
$context = array('foo' => 'bar');
$contextData = EventContextData::create(["foo" => "bar"]);
$em->publish($event, $context);
$this->assertEquals($context['foo'], 'baz');
// The event is expected to change foo from bar to baz.
$contextDataModified = $em->publish($event, $contextData);
$this->assertNotSame($contextData, $contextDataModified);
$this->assertEquals("baz", $contextDataModified->get("foo"));
}
}
+14 -1
View File
@@ -5,13 +5,21 @@ namespace LotGD\Core\Tests\FakeModule\Models;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use LotGD\Core\Game;
use LotGD\Core\GameAwareInterface;
use LotGD\Core\Models\ExtendableModelInterface;
use LotGD\Core\Tools\Model\ExtendableModel;
use LotGD\Core\Tools\Model\GameAware;
/**
* @Entity
* @Table(name="Users")
*/
class UserEntity
class UserEntity implements GameAwareInterface, ExtendableModelInterface
{
use GameAware;
use ExtendableModel;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string", length=50); */
@@ -31,4 +39,9 @@ class UserEntity
{
$this->name = $name;
}
public function returnGame(): Game
{
return $this->getGame();
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\FakeModule\Models;
use LotGD\Core\Doctrine\Annotations\Extension;
use LotGD\Core\Doctrine\Annotations\ExtensionMethod;
use LotGD\Core\Models\Character;
/**
* Class CharacterTestExtension
* @package LotGD\Core\Tests\FakeModule\Models
* @Extension(of="LotGD\Core\Tests\FakeModule\Models\UserEntity")
*/
class UserTestExtension
{
/**
* @param UserEntity $user
* @return array
* @ExtensionMethod(as="getNameAsArray")
*/
public static function returnNameAsArrayForUser(UserEntity $user): array
{
$g = $user->getGame();
if ($g !== null) {
return [$user->getName()];
} else {
return [];
}
}
}
+4 -2
View File
@@ -3,12 +3,14 @@
namespace LotGD\Core\Tests\FakeModule;
use LotGD\Core\Game;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Module as ModuleInterface;
use LotGD\Core\Models\Module as ModuleModel;
class Module implements ModuleInterface {
public static function handleEvent(Game $g, string $event, array &$context) {
$context['foo'] = 'baz';
public static function handleEvent(Game $g, EventContext $context): EventContext
{
$context->setDataField("foo", "baz");
return $context;
}
public static function onRegister(Game $g, ModuleModel $module) {}
+2
View File
@@ -1,3 +1,5 @@
entityNamespace: "LotGD\\Core\\Tests\\FakeModule\\Models\\"
subscriptionPatterns:
- "e/lotgd/core/tests/event"
modelExtensions:
- "LotGD\\Core\\Tests\\FakeModule\\Models\\UserTestExtension"
+175 -36
View File
@@ -8,26 +8,16 @@ use Doctrine\ORM\EntityManager;
use Monolog\Logger;
use Monolog\Handler\NullHandler;
use LotGD\Core\Action;
use LotGD\Core\ActionGroup;
use LotGD\Core\Bootstrap;
use LotGD\Core\Configuration;
use LotGD\Core\ComposerManager;
use LotGD\Core\DiceBag;
use LotGD\Core\EventHandler;
use LotGD\Core\EventManager;
use LotGD\Core\Game;
use LotGD\Core\TimeKeeper;
use LotGD\Core\ModuleManager;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\Viewpoint;
use LotGD\Core\Models\Scene;
use LotGD\Core\Exceptions\ {
ActionNotFoundException,
CharacterNotFoundException,
InvalidConfigurationException
use LotGD\Core\{
Action, ActionGroup, Bootstrap, Configuration, ComposerManager, DiceBag, EventHandler, EventManager, Events\NewViewpointData, Game, GameBuilder, TimeKeeper, ModuleManager
};
use LotGD\Core\Tests\CoreModelTestCase;
use LotGD\Core\Models\{
Character, Viewpoint, Scene
};
use LotGD\Core\Exceptions\ {
ActionNotFoundException, CharacterNotFoundException, InvalidConfigurationException
};
use LotGD\Core\Events\EventContext;
class DefaultSceneProvider implements EventHandler
{
@@ -35,29 +25,50 @@ class DefaultSceneProvider implements EventHandler
public static $attachments = ['actions'];
public static $data = ['data'];
public static function handleEvent(Game $g, string $event, array &$context)
public static function handleEvent(Game $g, EventContext $context): EventContext
{
switch ($event) {
switch ($context->getEvent()) {
case 'h/lotgd/core/default-scene':
if (!isset($context['character'])) {
throw new \Exception("Key 'character' was expected on event h/lotgd/core/default-scene.");
if (!$context->hasDataType(NewViewpointData::class)) {
throw new \Exception(sprintf(
"Context was expected to be %s, %s instead.",
NewViewpointData::class,
get_class($context->getData())
));
}
$context['scene'] = $g->getEntityManager()->getRepository(Scene::class)->find(1);
$context->setDataField("scene", $g->getEntityManager()->getRepository(Scene::class)
->find("30000000-0000-0000-0000-000000000001"));
break;
case 'h/lotgd/core/navigate-to/lotgd/tests/village':
$v = $context['viewpoint'];
$v = $context->getDataField('viewpoint');
self::$actionGroups = [new ActionGroup('default', 'Title', 0)];
self::$actionGroups[0]->setActions([
new Action(2), // This is a real sceneId in game.yml
new Action(101),
new Action("30000000-0000-0000-0000-000000000002"), // This is a real sceneId in game.yml
new Action("30000000-0000-0000-0000-000000000101"),
]);
$v->setActionGroups(self::$actionGroups);
$v->setAttachments(self::$attachments);
$v->setData(self::$data);
break;
case 'h/lotgd/core/navigate-to/lotgd/tests/paramaters':
/* @var Viewpoint $v //*/
$v = $context->getDataField('viewpoint');
/* @var array //*/
$p = $context->getDataField('parameters');
if ($p["foo"] === "baz") {
$v->setDescription("Parameter is baz.");
} else {
$v->setDescription("Parameter is NOT baz.");
}
break;
}
return $context;
}
}
@@ -66,7 +77,7 @@ class GameTest extends CoreModelTestCase
/** @var string default data set */
protected $dataset = "game";
private $g;
public $g;
public function setUp()
{
@@ -75,7 +86,12 @@ class GameTest extends CoreModelTestCase
$logger = new Logger('test');
$logger->pushHandler(new NullHandler());
$this->g = new Game(new Configuration(getenv('LOTGD_TESTS_CONFIG_PATH')), $logger, $this->getEntityManager(), implode(DIRECTORY_SEPARATOR, [__DIR__, '..']));
$this->g = (new GameBuilder())
->withConfiguration(new Configuration(getenv('LOTGD_TESTS_CONFIG_PATH')))
->withLogger($logger)
->withEntityManager($this->getEntityManager())
->withCwd(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']))
->create();
}
public function testBasicInjection()
@@ -113,7 +129,7 @@ class GameTest extends CoreModelTestCase
public function testSetGetCharacter()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(1);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$this->g->setCharacter($c);
$this->assertEquals($c, $this->g->getCharacter());
@@ -121,17 +137,17 @@ class GameTest extends CoreModelTestCase
public function testGetViewpointException()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(1);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$this->g->setCharacter($c);
// There shouldnt be any listeners to provide a default scene.
// There should'nt be any listeners to provide a default scene.
$this->expectException(InvalidConfigurationException::class);
$this->g->getViewpoint();
}
public function testGetViewpointStored()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(2);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$this->g->setCharacter($c);
$this->assertNotNull($this->g->getViewpoint());
@@ -139,11 +155,12 @@ class GameTest extends CoreModelTestCase
public function testGetViewpointDefault()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(1);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$this->g->setCharacter($c);
$this->g->getEventManager()->subscribe('/h\/lotgd\/core\/default-scene/', DefaultSceneProvider::class, 'lotgd/core/tests');
$this->g->getEventManager()->subscribe('/h\/lotgd\/core\/navigate-to\/.*/', DefaultSceneProvider::class, 'lotgd/core/tests');
$this->getEntityManager()->flush();
$v = $this->g->getViewpoint();
// Run it twice to make sure no additional DB operations happen.
@@ -160,7 +177,7 @@ class GameTest extends CoreModelTestCase
public function testTakeActionNonExistant()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(1);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$this->g->setCharacter($c);
// For now, I cant seem to serialize a proper ActionGroup to store in
@@ -173,7 +190,7 @@ class GameTest extends CoreModelTestCase
public function testTakeActionNavigate()
{
$c = $this->getEntityManager()->getRepository(Character::class)->find(3);
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000003");
$this->g->setCharacter($c);
// For now, I cant seem to serialize a proper ActionGroup to store in
@@ -190,4 +207,126 @@ class GameTest extends CoreModelTestCase
$v = $this->g->getViewpoint();
$this->assertSame($s->getTemplate(), $v->getTemplate());
}
public function testIfActionParametersAreRelayedToEvent()
{
/* @var $c Character */
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$this->g->setCharacter($c);
// subscribe event
$this->g->getEventManager()->subscribe('#h/lotgd/core/navigate-to/lotgd/tests/paramaters#', DefaultSceneProvider::class, 'lotgd/core/tests');
$this->getEntityManager()->flush();
$action = new Action("30000000-0000-0000-0000-000000000007", null, ["foo" => "baz"]);
$actionId = $action->getId();
$ag = new ActionGroup("group1", "Group 1", 5);
$ag->addAction($action);
$v = $c->getViewpoint();
$v->setDescription("Test");
$v->setActionGroups([$ag]);
$c->setViewpoint($v);
$this->g->takeAction($actionId);
$v = $c->getViewpoint();
$this->assertSame("Parameter is baz.", $v->getDescription());
// unsubscribe event
$this->g->getEventManager()->unsubscribe('#h/lotgd/core/navigate-to/lotgd/tests/paramaters#', DefaultSceneProvider::class, 'lotgd/core/tests');
}
public function testIfActionParametersTakePriorityToOtherParameters()
{
/* @var $c Character */
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$this->g->setCharacter($c);
// subscribe event
$this->g->getEventManager()->subscribe('#h/lotgd/core/navigate-to/lotgd/tests/paramaters#', DefaultSceneProvider::class, 'lotgd/core/tests');
$this->getEntityManager()->flush();
$action = new Action("30000000-0000-0000-0000-000000000007", null, ["foo" => "baz"]);
$actionId = $action->getId();
$ag = new ActionGroup("group1", "Group 1", 5);
$ag->addAction($action);
$v = $c->getViewpoint();
$v->setDescription("Test");
$v->setActionGroups([$ag]);
$c->setViewpoint($v);
$this->g->takeAction($actionId, ["foo" => "nobaz"]);
$v = $c->getViewpoint();
$this->assertSame("Parameter is baz.", $v->getDescription());
// unsubscribe event
$this->g->getEventManager()->unsubscribe('#h/lotgd/core/navigate-to/lotgd/tests/paramaters#', DefaultSceneProvider::class, 'lotgd/core/tests');
}
public function testIfActionsAreAddedAsExpected()
{
$viewpointToArray = function(Viewpoint $v) {
$returnTree = [];
foreach ($v->getActionGroups() as $actionGroup) {
$returnTree[$actionGroup->getId()] = [];
foreach ($actionGroup->getActions() as $action) {
$returnTree[$actionGroup->getId()][] = $action->getDestinationSceneId();
}
}
return [$v->getTitle(), $returnTree];
};
$sortedValues = function(array $array) {
$values = array_values($array);
sort($values);
return $values;
};
$c = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000003");
$this->g->setCharacter($c);
$v0 = $this->g->getViewpoint();
$this->g->takeAction($v0->getActionGroups()[0]->getActions()[2]->getId());
$v1 = $this->g->getViewpoint();
$this->assertSame([
"Parent Scene",
[
ActionGroup::DefaultGroup => ["30000000-0000-0000-0000-000000000001"],
"lotgd/tests/none/child1" => ["30000000-0000-0000-0000-000000000005"],
"lotgd/tests/none/child2" => ["30000000-0000-0000-0000-000000000006"],
ActionGroup::HiddenGroup => [],
]
], $viewpointToArray($v1));
$this->g->takeAction($v1->getActionGroups()[1]->getActions()[0]->getId());
$v2 = $this->g->getviewpoint();
$this->assertSame([
"Child Scene 1",
[
ActionGroup::DefaultGroup => [
"30000000-0000-0000-0000-000000000006",
"30000000-0000-0000-0000-000000000004"
],
ActionGroup::HiddenGroup => [],
]
], $viewpointToArray($v2));
$this->g->takeAction($v1->getActionGroups()[0]->getActions()[0]->getId());
$v3 = $this->g->getviewpoint();
$this->assertSame([
"Child Scene 2",
[
ActionGroup::DefaultGroup => ["30000000-0000-0000-0000-000000000004"],
ActionGroup::HiddenGroup => [],
]
], $viewpointToArray($v3));
}
}
+290
View File
@@ -0,0 +1,290 @@
<?php
declare(strict_types=1);
namespace LotGD\Core\Tests\Managers;
use Doctrine\ORM\EntityManagerInterface;
use LotGD\Core\Game;
use LotGD\Core\PermissionManager;
use LotGD\Core\Exceptions\PermissionAlreadyExistsException;
use LotGD\Core\Exceptions\PermissionDoesNotExistException;
use LotGD\Core\Exceptions\PermissionIdNotFoundException;
use LotGD\Core\Models\Permission;
use LotGD\Core\Models\PermissionableInterface;
use LotGD\Core\Models\PermissionAssociationInterface;
use LotGD\Core\Tools\Model\Permissionable;
use LotGD\Core\Tests\CoreModelTestCase;
use LotGD\Core\Tests\Ressources\TestModels\User;
use LotGD\Core\Tests\Ressources\TestModels\UserPermissionAssociation;
/**
* Description of PermissionManagerTest
*/
class PermissionManagerTest extends CoreModelTestCase
{
protected $dataset = "permission-manager";
public function getPermissionManager(EntityManagerInterface $em): PermissionManager
{
$this->game = $this->getMockBuilder(Game::class)
->disableOriginalConstructor()
->getMock();
$this->game->method('getEntityManager')->willReturn($em);
return new PermissionManager($this->game);
}
public function testUserHasPermission()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($user->hasPermissionSet("test/permission_one"));
$this->assertTrue($user->hasPermissionSet("test/permission_two"));
}
public function testUserReturnsPermission()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permission = $user->getRawPermission("test/permission_one");
$this->assertInstanceOf(Permission::class, $permission);
}
public function testUserReturnsPermissionAssociation()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permission = $user->getPermission("test/permission_one");
$this->assertInstanceOf(UserPermissionAssociation::class, $permission);
}
public function testIfAddingAnAlreadySetPermissionToAnUserResultsInException()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permission = $em->getRepository(Permission::class)->find("test/permission_two");
$this->expectException(PermissionAlreadyExistsException::class);
$user->addPermission($permission, PermissionManager::Denied);
}
public function testIfRemovingANotSetPermissionFromAnUserResultsInException()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permission = $em->getRepository(Permission::class)->find("test/permission_tri");
$this->expectException(PermissionDoesNotExistException::class);
$user->removePermission("test/permission_tri");
}
public function testIfHasPermissionSetWorksAsExpected()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->hasPermissionSet($user, "test/permission_one"));
$this->assertTrue($permissionManager->hasPermissionSet($user, "test/permission_two"));
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_none"));
}
public function testIfIsAllowedSetWorksAsExpected()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_one"));
$this->assertFalse($permissionManager->isAllowed($user, "test/permission_two"));
$this->assertFalse($permissionManager->isAllowed($user, "test/permission_tri"));
$this->assertFalse($permissionManager->isAllowed($user, "test/permission_none"));
}
public function testIfIsDeniedSetWorksAsExpected()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertFalse($permissionManager->isDenied($user, "test/permission_one"));
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
$this->assertFalse($permissionManager->isDenied($user, "test/permission_tri"));
$this->assertFalse($permissionManager->isDenied($user, "test/permission_none"));
}
public function testIfAllowingAnAllowedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$permissionManager->allow($user, "test/permission_one");
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_one"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_one"));
}
public function testIfAllowingAnDeniedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
$permissionManager->allow($user, "test/permission_two");
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_two"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_two"));
}
public function testIfAllowingANonExistingPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$permissionManager->allow($user, "test/permission_tri");
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_tri"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_tri"));
}
public function testIfDenyingAnAllowedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_one"));
$permissionManager->deny($user, "test/permission_one");
$this->assertTrue($permissionManager->isDenied($user, "test/permission_one"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_one"));
}
public function testIfDenyingAnDeniedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
$permissionManager->deny($user, "test/permission_two");
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
}
public function testIfDenyingANonExistingPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$permissionManager->deny($user, "test/permission_tri");
$this->assertTrue($permissionManager->isDenied($user, "test/permission_tri"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_tri"));
}
public function testIfRemovingAnAllowedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isAllowed($user, "test/permission_one"));
$permissionManager->remove($user, "test/permission_one");
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_one"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_one"));
}
public function testIfRemovingADeniedPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertTrue($permissionManager->isDenied($user, "test/permission_two"));
$permissionManager->remove($user, "test/permission_two");
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_two"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_two"));
}
public function testIfRemovingANonExistingPermissionWorks()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$permissionManager->remove($user, "test/permission_tri");
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
$em->flush();
$em->clear();
$user = $em->getRepository(User::class)->find(1);
$this->assertFalse($permissionManager->hasPermissionSet($user, "test/permission_tri"));
}
public function testIfRequestingANonExistingPermissionThrowsAnException()
{
$em = $this->getEntityManager();
$user = $em->getRepository(User::class)->find(1);
$permissionManager = $this->getPermissionManager($em);
$this->expectException(PermissionIdNotFoundException::class);
$permissionManager->allow($user, "test/non_existing_permission");
}
public function testSomething()
{
$this->assertTrue(True);
}
}
+57
View File
@@ -3,16 +3,23 @@ declare(strict_types=1);
namespace LotGD\Core\Tests;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events as DoctrineEvents;
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\Tools\SchemaTool;
use LotGD\Core\Configuration;
use LotGD\Core\ComposerManager;
use LotGD\Core\Doctrine\EntityPostLoadEventListener;
use LotGD\Core\GameBuilder;
use LotGD\Core\LibraryConfigurationManager;
use LotGD\Core\Exceptions\InvalidConfigurationException;
use LotGD\Core\ModelExtender;
use Monolog\Handler\NullHandler;
use Monolog\Logger;
/**
* Description of ModelTestCase
@@ -25,6 +32,7 @@ abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase
static private $em = null;
/** @var \PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection */
private $connection = null;
public $g;
/**
* Returns a connection to test models
@@ -47,6 +55,7 @@ abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase
$libraryConfigurationManager = new LibraryConfigurationManager($composerManager, getcwd());
$directories = $libraryConfigurationManager->getEntityDirectories();
$directories[] = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'src', 'Models']);
$directories[] = implode(DIRECTORY_SEPARATOR, [__DIR__, 'Resources', 'TestModels']);
// Read db annotations from model files
$configuration = Setup::createAnnotationMetadataConfiguration($directories, true);
@@ -54,6 +63,9 @@ abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase
self::$em = EntityManager::create(["pdo" => self::$pdo], $configuration);
// Register uuid type
\Doctrine\DBAL\Types\Type::addType('uuid', 'Ramsey\Uuid\Doctrine\UuidType');
// Create Schema
$metaData = self::$em->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool(self::$em);
@@ -75,10 +87,55 @@ abstract class ModelTestCase extends \PHPUnit_Extensions_Database_TestCase
return self::$em;
}
protected function setUp()
{
parent::setUp();
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
// Make an empty logger for these tests. Feel free to change this
// to place log messages somewhere you can easily find them.
$logger = new Logger('test');
$logger->pushHandler(new NullHandler());
// Create a Game object for use in these tests.
$this->g = (new GameBuilder())
->withConfiguration(new Configuration(getenv('LOTGD_TESTS_CONFIG_PATH')))
->withLogger($logger)
->withEntityManager($this->getEntityManager())
->withCwd(implode(DIRECTORY_SEPARATOR, [__DIR__, '..']))
->create();
// Add Event listener to entity manager
$dem = $this->getEntityManager()->getEventManager();
$dem->addEventListener([DoctrineEvents::postLoad], new EntityPostLoadEventListener($this->g));
// Run model extender
AnnotationRegistry::registerLoader("class_exists");
$modelExtender = new ModelExtender();
$libraryConfigurationManager = new LibraryConfigurationManager($this->g->getComposerManager(), getcwd());
foreach ($libraryConfigurationManager->getConfigurations() as $config) {
$modelExtensions = $config->getSubKeyIfItExists(["modelExtensions"]);
if ($modelExtensions) {
$modelExtender->addMore($modelExtensions);
}
}
}
protected function tearDown() {
parent::tearDown();
// Clear out the cache so tests don't get confused.
$this->getEntityManager()->clear();
}
protected function flushAndClear()
{
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
}
}
+51 -5
View File
@@ -3,10 +3,16 @@ declare(strict_types=1);
namespace LotGD\Core\Tests\Models;
use LotGD\Core\EventHandler;
use LotGD\Core\EventManager;
use LotGD\Core\Events\EventContext;
use LotGD\Core\Game;
use LotGD\Core\GameBuilder;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\CharacterProperty;
use LotGD\Core\Tests\CoreModelTestCase;
use LotGD\Core\Models\Repositories\CharacterRepository;
use Ramsey\Uuid\UuidInterface;
/**
* Tests the management of Characters
@@ -21,13 +27,13 @@ class CharacterModelTest extends CoreModelTestCase
*/
public function testSoftDeletion()
{
$chars = $this->getEntityManager()->getRepository(Character::class)->find(3);
$chars = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000003");
$this->assertSame(null, $chars);
$allChars = $this->getEntityManager()->getRepository(Character::class)->findAll();
$this->assertSame(2, count($allChars));
$char = $this->getEntityManager()->getRepository(Character::class)->find(1);
$char = $this->getEntityManager()->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$char->delete($this->getEntityManager());
$this->getEntityManager()->flush();
$this->getEntityManager()->clear();
@@ -90,11 +96,14 @@ class CharacterModelTest extends CoreModelTestCase
$em = $this->getEntityManager();
$characterEntity = Character::create($characterData);
$characterEntity->setProperty("a property", 16);
$this->assertSame(16, $characterEntity->getProperty("a property"));
$characterEntity->save($em);
$em->flush();
$this->assertInternalType("int", $characterEntity->getId());
$this->assertInstanceOf(UuidInterface::class, $characterEntity->getId());
$this->assertSame(16, $characterEntity->getProperty("a property"));
$em->flush();
}
@@ -134,7 +143,7 @@ class CharacterModelTest extends CoreModelTestCase
$rowsBefore = count($em->getRepository(Character::class)->findAll());
// Delete one row
$character = $em->getRepository(Character::class)->find(1);
$character = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character->delete($em);
$em->clear();
@@ -155,7 +164,7 @@ class CharacterModelTest extends CoreModelTestCase
$em = $this->getEntityManager();
// test default values
$firstCharacter = $em->getRepository(Character::class)->find(1);
$firstCharacter = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$this->assertSame(5, $firstCharacter->getProperty("dragonkills", 5));
$this->assertNotSame(5, $firstCharacter->getProperty("dragonkills", "5"));
$this->assertSame("hanniball", $firstCharacter->getProperty("petname", "hanniball"));
@@ -189,4 +198,41 @@ class CharacterModelTest extends CoreModelTestCase
$this->assertSame(6, $total);
}
public function testIfAttackPublishesEvent()
{
$level = mt_rand(0, 100);
$character1 = Character::create(["name" => "Test", "maxHealth" => 10, "level" => $level]);
$character1->setGame($this->g);
$character2 = Character::create(["name" => "Test", "maxHealth" => 10, "level" => $level*2]);
$character2->setGame($this->g);
$detectionClass = new class implements EventHandler {
static $events_called = [];
public static function handleEvent(Game $g, EventContext $context): EventContext
{
$event = $context->getEvent();
$value = $context->getDataField("value");
self::$events_called[$event] = $value;
$context->setDataField("value", $value*2);
return $context;
}
};
/** @var EventManager $eventManager */
$eventManager = $this->g->getEventManager();
$eventManager->subscribe("#h/lotgd/core/getCharacterAttack#", get_class($detectionClass), "test");
$eventManager->subscribe("#h/lotgd/core/getCharacterDefense#", get_class($detectionClass), "test");
$this->getEntityManager()->flush();
$this->assertSame($level*2, $character1->getAttack());
$this->assertSame($level*4, $character2->getDefense());
$this->assertSame($level, $detectionClass::$events_called["h/lotgd/core/getCharacterAttack"]);
$this->assertSame($level*2, $detectionClass::$events_called["h/lotgd/core/getCharacterDefense"]);
}
}
+49 -43
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace LotGD\Core\Tests\Models;
use LotGD\Core\MessageManager;
use LotGD\Core\Models\Character;
use LotGD\Core\Models\MessageThread;
use LotGD\Core\Models\Message;
@@ -14,24 +15,26 @@ use LotGD\Core\Tests\CoreModelTestCase;
*/
class MessageModelTest extends CoreModelTestCase
{
/** @var string default data set */
protected $dataset = "messages";
public function testSendMessageToSingleCharacter()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(4);
$mm=new MessageManager();
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000004");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character2, $character1]);
$this->assertSame($thread1, $thread2);
Message::send($character1, "Hi, how are you?", $thread1);
Message::send($character2, "I'm fine, and you?", $thread1);
Message::send($character1, "Sorry, I need to leave~", $thread1);
$mm->send($character1, "Hi, how are you?", $thread1);
$mm->send($character2, "I'm fine, and you?", $thread1);
$mm->send($character1, "Sorry, I need to leave~", $thread1);
$this->assertSame(3, count($thread1->getMessages()));
@@ -39,8 +42,8 @@ class MessageModelTest extends CoreModelTestCase
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(4);
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000004");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
@@ -57,25 +60,26 @@ class MessageModelTest extends CoreModelTestCase
public function testSendMessageToMultipleCharacters()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character3 = $em->getRepository(Character::class)->find(3, CharacterRepository::INCLUDE_SOFTDELETED);
$character4 = $em->getRepository(Character::class)->find(4);
$mm=new MessageManager();
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$character3 = $em->getRepository(Character::class)->findWithSoftDeleted("10000000-0000-0000-0000-000000000003");
$character4 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000004");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2, $character3, $character4]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character4, $character2, $character1, $character3]);
$this->assertSame($thread1, $thread2);
Message::send($character1, "Multi-User-Message", $thread1);
Message::send($character2, "Multi-User-Message", $thread1);
$mm->send($character1, "Multi-User-Message", $thread1);
$mm->send($character2, "Multi-User-Message", $thread1);
try {
$exception = false;
Message::send($character3, "Multi-User-Message", $thread1);
$mm->send($character3, "Multi-User-Message", $thread1);
} catch(\LotGD\Core\Exceptions\ArgumentException $e) {
$exception = true;
}
Message::send($character4, "Multi-User-Message", $thread1);
$mm->send($character4, "Multi-User-Message", $thread1);
$this->assertTrue($exception);
$this->assertSame(3, count($thread1->getMessages()));
@@ -85,11 +89,11 @@ class MessageModelTest extends CoreModelTestCase
$this->assertSame(1, count($character4->getMessageThreads()));
$em->flush();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character3 = $em->getRepository(Character::class)->find(3, CharacterRepository::INCLUDE_SOFTDELETED);
$character4 = $em->getRepository(Character::class)->find(4);
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$character3 = $em->getRepository(Character::class)->findWithSoftDeleted("10000000-0000-0000-0000-000000000003");
$character4 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000004");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2, $character3, $character4]);
@@ -105,23 +109,24 @@ class MessageModelTest extends CoreModelTestCase
public function testSendSystemMessageToSingleCharacter()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$mm=new MessageManager();
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character2]);
$this->assertNotSame($thread1, $thread2);
Message::sendSystemMessage("This is a Systemmessage for Character 1.", $thread1);
Message::sendSystemMessage("This is a Systemmessage for Character 2.", $thread2);
$mm->sendSystemMessage("This is a Systemmessage for Character 1.", $thread1);
$mm->sendSystemMessage("This is a Systemmessage for Character 2.", $thread2);
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character2]);
@@ -133,12 +138,12 @@ class MessageModelTest extends CoreModelTestCase
// needs to be able to get attached
try {
$exception = false;
Message::send($character1, "A normal message", $thread1);
$mm->send($character1, "A normal message", $thread1);
} catch (\LotGD\Core\Exceptions\CoreException $ex) {
$exception = true;
}
Message::sendSystemMessage("A second system Message", $thread1);
$mm->sendSystemMessage("A second system Message", $thread1);
$this->assertTrue($exception);
$this->assertSame(2, count($thread1->getMessages()));
@@ -147,22 +152,23 @@ class MessageModelTest extends CoreModelTestCase
public function testSendSystemMessageToMultipleCharacters()
{
$em = $this->getEntityManager();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$mm=new MessageManager();
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1, $character2]);
$thread2 = $em->getRepository(MessageThread::class)->findOrCreateFor([$character1, $character2]);
$this->assertNotSame($thread1, $thread2);
Message::sendSystemMessage("A system message to 2 recipients", $thread1);
$mm->sendSystemMessage("A system message to 2 recipients", $thread1);
$em->flush();
$em->clear();
$character1 = $em->getRepository(Character::class)->find(1);
$character2 = $em->getRepository(Character::class)->find(2);
$character1 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000001");
$character2 = $em->getRepository(Character::class)->find("10000000-0000-0000-0000-000000000002");
$thread1 = $em->getRepository(MessageThread::class)->findOrCreateReadonlyFor([$character1, $character2]);
+10
View File
@@ -5,6 +5,7 @@ namespace LotGD\Core\Tests\Models;
use LotGD\Core\Models\Module;
use LotGD\Core\Models\ModuleProperty;
use LotGD\Core\ModuleManager;
use LotGD\Core\Tests\CoreModelTestCase;
/**
@@ -29,6 +30,15 @@ class ModuleTest extends CoreModelTestCase
$em->flush();
}
public function testSetter()
{
$em = $this->getEntityManager();
$module = new Module("lotgd/test/blah");
$module->setProperty("test", 15);
$this->assertSame(15, $module->getProperty("test"));
}
public function testProperties()
{
$em = $this->getEntityManager();

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