3 Commits

11 changed files with 543 additions and 253 deletions
+1
View File
@@ -18,6 +18,7 @@
"gedmo/doctrine-extensions": "^2.3|^3.0", "gedmo/doctrine-extensions": "^2.3|^3.0",
"doctrine/orm": "^2.8", "doctrine/orm": "^2.8",
"doctrine/common": "^3.0", "doctrine/common": "^3.0",
"doctrine/dbal": "^2.12 <2.13",
"monolog/monolog": "^2.0", "monolog/monolog": "^2.0",
"symfony/console": "^5.0", "symfony/console": "^5.0",
"symfony/yaml": "^5.0", "symfony/yaml": "^5.0",
Generated
+194 -200
View File
File diff suppressed because it is too large Load Diff
+58 -1
View File
@@ -3,13 +3,16 @@ declare(strict_types=1);
namespace LotGD\Core; namespace LotGD\Core;
use LotGD\Core\Models\Viewpoint;
/** /**
* A representation of an action the user can take to affect the game * A representation of an action the user can take to affect the game
* state. An encapsulation of a navigation menu option. * state. An encapsulation of a navigation menu option.
*/ */
class Action class Action implements \Serializable
{ {
protected string $id; protected string $id;
private ?Viewpoint $viewpoint = null;
/** /**
* Construct a new action with the specified Scene as its destination. * Construct a new action with the specified Scene as its destination.
@@ -25,6 +28,41 @@ class Action
$this->id = \bin2hex(\random_bytes(8)); $this->id = \bin2hex(\random_bytes(8));
} }
public function serialize()
{
return serialize([
"id" => $this->id,
"destinationSceneId" => $this->destinationSceneId,
"title" => $this->title,
"parameters" => $this->parameters,
]);
}
public function unserialize($serialized)
{
$data = unserialize($serialized);
$this->id = $data["id"];
$this->destinationSceneId = $data["destinationSceneId"];
$this->title = $data["title"];
$this->parameters = $data["parameters"];
}
/**
* @param Viewpoint|null $viewpoint
*/
public function setViewpoint(?Viewpoint $viewpoint)
{
$this->viewpoint = $viewpoint;
}
/**
* @return Viewpoint|null
*/
public function getViewpoint(): ?Viewpoint
{
return $this->viewpoint;
}
/** /**
* Returns the unique, automatically generated identifier for this action. * Returns the unique, automatically generated identifier for this action.
* Use this ID to refer to this action when calling Game::takeAction(). * Use this ID to refer to this action when calling Game::takeAction().
@@ -53,6 +91,25 @@ class Action
return $this->title; return $this->title;
} }
/**
* Returns the rendered action title.
* @return string|null
* @throws Exceptions\InsecureTwigTemplateError
*/
public function getRenderedTitle(): ?string
{
$title = $this->getTitle();
$sceneRenderer = $this->getViewpoint()?->getTwigSceneRenderer();
if (!$title) {
return null;
} elseif ($sceneRenderer) {
return $sceneRenderer->render($title, $this->viewpoint, ignoreErrors: true, templateValues: ["Action" => $this]);
} else {
return $title;
}
}
/** /**
* @param string|null $title * @param string|null $title
*/ */
+69 -2
View File
@@ -3,15 +3,21 @@ declare(strict_types=1);
namespace LotGD\Core; namespace LotGD\Core;
use LotGD\Core\Models\Viewpoint;
/** /**
* A grouping of navigation actions, like a submenu. * A grouping of navigation actions, like a submenu.
*/ */
class ActionGroup implements \Countable class ActionGroup implements \Countable, \Serializable
{ {
const DefaultGroup = 'lotgd/core/default'; const DefaultGroup = 'lotgd/core/default';
const HiddenGroup = 'lotgd/core/hidden'; const HiddenGroup = 'lotgd/core/hidden';
private $actions; /**
* @var Action[]
*/
private array $actions;
private ?Viewpoint $viewpoint = null;
/** /**
* Create a new ActionGroup, starting with an empty set of actions. * Create a new ActionGroup, starting with an empty set of actions.
@@ -27,6 +33,45 @@ class ActionGroup implements \Countable
$this->actions = []; $this->actions = [];
} }
public function serialize()
{
return serialize([
"id" => $this->id,
"title" => $this->title,
"actions" => $this->actions,
"sortKey" => $this->sortKey,
]);
}
public function unserialize($serialized)
{
$data = unserialize($serialized);
$this->id = $data["id"];
$this->title = $data["title"];
$this->actions = $data["actions"];
$this->sortKey = $data["sortKey"];
}
/**
* @param Viewpoint|null $viewpoint
*/
public function setViewpoint(?Viewpoint $viewpoint)
{
$this->viewpoint = $viewpoint;
foreach ($this->actions as $action) {
$action->setViewpoint($viewpoint);
}
}
/**
* @return Viewpoint|null
*/
public function getViewpoint(): ?Viewpoint
{
return $this->viewpoint;
}
/** /**
* Returns the number of registered Actions for this group. * Returns the number of registered Actions for this group.
* @return int * @return int
@@ -54,6 +99,23 @@ class ActionGroup implements \Countable
return $this->title; return $this->title;
} }
/**
* Returns the rendered title for this group.
* @return string
* @throws Exceptions\InsecureTwigTemplateError
*/
public function getRenderedTitle(): string
{
$title = $this->getTitle();
$sceneRenderer = $this->viewpoint?->getTwigSceneRenderer();
if ($sceneRenderer) {
return $sceneRenderer->render($title, $this->viewpoint, ignoreErrors: true);
} else {
return $title;
}
}
/** /**
* Returns the sort key for this group. The ordering of the groups in the * Returns the sort key for this group. The ordering of the groups in the
* final menu displayed to users will be sorted by this key. The default * final menu displayed to users will be sorted by this key. The default
@@ -80,6 +142,10 @@ class ActionGroup implements \Countable
*/ */
public function setActions(array $actions) public function setActions(array $actions)
{ {
foreach ($actions as $action) {
$action->setViewpoint($this->viewpoint);
}
$this->actions = $actions; $this->actions = $actions;
} }
@@ -89,6 +155,7 @@ class ActionGroup implements \Countable
*/ */
public function addAction(Action $action) public function addAction(Action $action)
{ {
$action->setViewpoint($this->viewpoint);
$this->actions[] = $action; $this->actions[] = $action;
} }
} }
+3 -1
View File
@@ -288,6 +288,8 @@ class Game
private function navigateToScene(Scene $scene, array $parameters) private function navigateToScene(Scene $scene, array $parameters)
{ {
$viewpoint = $this->getCharacter()->getViewpoint(); $viewpoint = $this->getCharacter()->getViewpoint();
$viewpoint->setTwigSceneRenderer($this->getSceneRenderer());
do { do {
$referrer = $viewpoint->getScene(); $referrer = $viewpoint->getScene();
@@ -296,7 +298,7 @@ class Game
$this->getLogger()->debug("Navigating to sceneId={$id} from referrer sceneId={$referrerId}"); $this->getLogger()->debug("Navigating to sceneId={$id} from referrer sceneId={$referrerId}");
// Copy over the basic structure from the scene database. // Copy over the basic structure from the scene database.
$viewpoint->changeFromScene($scene, $this->getSceneRenderer()); $viewpoint->changeFromScene($scene);
// Generate the default set of actions: the default group with // Generate the default set of actions: the default group with
// all children. // all children.
+72 -12
View File
@@ -41,10 +41,16 @@ class Viewpoint implements CreateableInterface
*/ */
private Character $owner; private Character $owner;
/** @Column(type="array") */ /**
* @var ActionGroup[]
* @Column(type="array")
*/
private array $actionGroups = []; private array $actionGroups = [];
/** @Column(type="array") */ /**
* @var Attachment[]
* @Column(type="array")
*/
private array $attachments = []; private array $attachments = [];
/** @Column(type="array") */ /** @Column(type="array") */
@@ -57,6 +63,7 @@ class Viewpoint implements CreateableInterface
private ?Scene $scene = null; private ?Scene $scene = null;
private ?SceneDescription $_description = null; private ?SceneDescription $_description = null;
private ?TwigSceneRenderer $twigSceneRenderer = null;
private static array $fillable = [ private static array $fillable = [
"owner", "owner",
@@ -80,6 +87,22 @@ class Viewpoint implements CreateableInterface
$this->owner = $owner; $this->owner = $owner;
} }
/**
* @param TwigSceneRenderer $twigSceneRenderer
*/
public function setTwigSceneRenderer(TwigSceneRenderer $twigSceneRenderer)
{
$this->twigSceneRenderer = $twigSceneRenderer;
}
/**
* @return TwigSceneRenderer|null
*/
public function getTwigSceneRenderer(): ?TwigSceneRenderer
{
return $this->twigSceneRenderer;
}
/** /**
* Sets the description of this viewpoint. * Sets the description of this viewpoint.
* @param string $description * @param string $description
@@ -125,15 +148,11 @@ class Viewpoint implements CreateableInterface
/** /**
* Copies the static data from a scene to this Viewpoint entity. * Copies the static data from a scene to this Viewpoint entity.
* @param Scene $scene * @param Scene $scene
* @param TwigSceneRenderer $renderer Required to parse title and description.
*/ */
public function changeFromScene(Scene $scene, TwigSceneRenderer $renderer) public function changeFromScene(Scene $scene)
{ {
$title = $renderer->render($scene->getTitle(), $scene, ignoreErrors: true); $this->setTitle($scene->getTitle());
$description = $renderer->render($scene->getDescription(), $scene, ignoreErrors: true); $this->setDescription($scene->getDescription());
$this->setTitle($title);
$this->setDescription($description);
$this->setTemplate($scene->getTemplate()); $this->setTemplate($scene->getTemplate());
$this->setScene($scene); $this->setScene($scene);
@@ -178,6 +197,10 @@ class Viewpoint implements CreateableInterface
$this->setActionGroups($snapshot->getActionGroups()); $this->setActionGroups($snapshot->getActionGroups());
$this->setAttachments($snapshot->getAttachments()); $this->setAttachments($snapshot->getAttachments());
$this->setData($snapshot->getData()); $this->setData($snapshot->getData());
foreach ($this->actionGroups as $actionGroup) {
$actionGroup->setViewpoint($this);
}
} }
/** /**
@@ -213,6 +236,11 @@ class Viewpoint implements CreateableInterface
*/ */
public function setActionGroups(array $actionGroups) public function setActionGroups(array $actionGroups)
{ {
foreach ($actionGroups as $actionGroup) {
if ($actionGroup instanceof ActionGroup) {
$actionGroup->setViewpoint($this);
}
}
$this->actionGroups = $actionGroups; $this->actionGroups = $actionGroups;
} }
@@ -229,6 +257,10 @@ class Viewpoint implements CreateableInterface
throw new ArgumentException("Group {$group} is already contained in this viewpoint."); throw new ArgumentException("Group {$group} is already contained in this viewpoint.");
} }
foreach ($group->getActions() as $action) {
$action->setViewpoint($this);
}
if ($after === null) { if ($after === null) {
$this->actionGroups[] = $group; $this->actionGroups[] = $group;
} else { } else {
@@ -271,9 +303,7 @@ class Viewpoint implements CreateableInterface
$actionGroups = $this->getActionGroups(); $actionGroups = $this->getActionGroups();
foreach ($actionGroups as $group) { foreach ($actionGroups as $group) {
if ($group->getId() == $actionGroupId) { if ($group->getId() == $actionGroupId) {
$actions = $group->getActions(); $group->addAction($action);
$actions[] = $action;
$group->setActions($actions);
break; break;
} }
} }
@@ -381,4 +411,34 @@ class Viewpoint implements CreateableInterface
$group->setActions($actions); $group->setActions($actions);
} }
} }
/**
* Returns the rendered version of the title
* @return string
*/
public function getRenderedTitle(): string
{
$title = $this->getTitle();
if ($this->twigSceneRenderer) {
return $this->twigSceneRenderer->render($title, $this, ignoreErrors: true);
} else {
return $title;
}
}
/**
* Returns the rendered version of the description
* @return string
*/
public function getRenderedDescription(): string
{
$description = $this->getDescription();
if ($this->twigSceneRenderer) {
return $this->twigSceneRenderer->render($description, $this, ignoreErrors: true);
} else {
return $description;
}
}
} }
+3
View File
@@ -25,6 +25,9 @@ class ViewpointSnapshot
private array $attachments, private array $attachments,
private array $data, private array $data,
) { ) {
foreach ($this->actionGroups as $actionGroup) {
$actionGroup->setViewpoint(null);
}
} }
/** /**
+45 -12
View File
@@ -5,12 +5,17 @@ declare(strict_types=1);
namespace LotGD\Core\Services; namespace LotGD\Core\Services;
use LotGD\Core\Action;
use LotGD\Core\Events\EventContextData; use LotGD\Core\Events\EventContextData;
use LotGD\Core\Exceptions\CharacterNotFoundException;
use LotGD\Core\Exceptions\InsecureTwigTemplateError; use LotGD\Core\Exceptions\InsecureTwigTemplateError;
use LotGD\Core\Game; use LotGD\Core\Game;
use LotGD\Core\Models\Character; use LotGD\Core\Models\Character;
use LotGD\Core\Models\Scene; use LotGD\Core\Models\Scene;
use LotGD\Core\Models\Viewpoint;
use Twig\Environment; use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Twig\Extension\SandboxExtension; use Twig\Extension\SandboxExtension;
use Twig\Sandbox\SecurityError; use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityPolicy; use Twig\Sandbox\SecurityPolicy;
@@ -18,19 +23,40 @@ use Twig\Sandbox\SecurityPolicy;
class TwigSceneRenderer class TwigSceneRenderer
{ {
private Environment $twig; private Environment $twig;
private array $templateValues;
public function __construct( public function __construct(
private Game $game private Game $game
) { ) {
$this->twig = new Environment(new TwigNullLoader()); $this->twig = new Environment(new TwigNullLoader());
$securityPolicy = $this->getSecurityPolicy(); // Add a hook for additional fields
// This is for global changes only. Viewpoint-dependent changes should try to store the important values within
// the viewpoint itself.
$eventManager = $this->game->getEventManager();
$contextData = EventContextData::create(["templateValues" => []]);
$newContextData = $eventManager->publish("h/lotgd/core/scene-renderer/templateValues", $contextData);
$this->templateValues = $newContextData->get("templateValues") ?? [];
# Add Sandbox extension // Add Sandbox extension
$securityPolicy = $this->getSecurityPolicy();
$this->twig->addExtension(new SandboxExtension($securityPolicy, sandboxed: true)); $this->twig->addExtension(new SandboxExtension($securityPolicy, sandboxed: true));
} }
public function render(string $string, Scene $scene, bool $ignoreErrors = false): string /**
* Renders a given string in the context if a given viewpoint.
*
* @param string $string
* @param Viewpoint $viewpoint
* @param bool $ignoreErrors If set to true, errors are ignored and the unparsed string will be returned instead.
* @param array $templateValues
* @return string
* @throws InsecureTwigTemplateError
* @throws CharacterNotFoundException
* @throws LoaderError
* @throws SyntaxError
*/
public function render(string $string, Viewpoint $viewpoint, bool $ignoreErrors = false, array $templateValues = []): string
{ {
// We catch here "Tag" errors. If error, we'll exit either by returning the input ($ignoreError === true) or // We catch here "Tag" errors. If error, we'll exit either by returning the input ($ignoreError === true) or
// throwing an exception. // throwing an exception.
@@ -44,16 +70,14 @@ class TwigSceneRenderer
} }
} }
$templateValues = [ $defaultTemplateValues = [
"Character" => $this->game->getCharacter(), "Character" => $this->game->getCharacter(),
"Scene" => $scene, "Scene" => $viewpoint->getScene(),
"Viewpoint" => $viewpoint,
]; ];
// Publish event to change $templateValues // Merges additional template values with important ones.
$eventManager = $this->game->getEventManager(); $templateValues = array_merge($this->templateValues, $defaultTemplateValues, $templateValues);
$contextData = EventContextData::create(["templateValues" => $templateValues]);
$newContextData = $eventManager->publish("h/lotgd/core/scene-renderer/templateValues", $contextData);
$templateValues = $newContextData->get("templateValues");
// Try to render the template // Try to render the template
try { try {
@@ -70,17 +94,26 @@ class TwigSceneRenderer
return $result; return $result;
} }
/**
* Returns the current security policy.
* This method provides a hook.
* @return SecurityPolicy
*/
public function getSecurityPolicy(): SecurityPolicy public function getSecurityPolicy(): SecurityPolicy
{ {
$tags = ["if"]; $tags = ["if"];
$filters = ["lower", "upper", "escape"]; $filters = ["lower", "upper", "escape", "round"];
$functions = ["range"]; $functions = ["range"];
$methods = [ $methods = [
Character::class => ["getDisplayName", "getLevel", "isAlive", "getHealth", "getMaxHealth", "getProperty"], Character::class => ["getDisplayName", "getLevel", "isAlive", "getHealth", "getMaxHealth", "getProperty"],
Scene::class => ["getProperty"], Scene::class => ["getProperty"],
Viewpoint::class => ["getData"],
Action::class => ["getParameters"],
]; ];
$properties = [ $properties = [
"Character" => ["displayName", "level", "health", "maxHealth"], "Character" => ["displayName", "level", "alive", "health", "maxHealth", "property"],
"Viewpoint" => ["data"],
"Action" => ["parameters"],
]; ];
// Publish event to change $templateValues // Publish event to change $templateValues
+26 -6
View File
@@ -22,11 +22,11 @@ class ViewpointRestorationTest extends CoreModelTestCase
protected function getActionGroup() protected function getActionGroup()
{ {
static $actionGroup = null; static $actionGroup = null;
if ($actionGroup !== null) { if ($actionGroup === null) {
$actionGroup = new ActionGroup("main", "Title", 0); $actionGroup = new ActionGroup("main", "Title", 0);
$actionGroup->addAction(new Action(1)); $actionGroup->addAction(new Action("30000000-0000-0000-0000-000000000001"));
$actionGroup->addAction(new Action(2)); $actionGroup->addAction(new Action("30000000-0000-0000-0000-000000000002"));
$actionGroup->addAction(new Action(3)); $actionGroup->addAction(new Action("30000000-0000-0000-0000-000000000003"));
} }
return $actionGroup; return $actionGroup;
@@ -102,7 +102,17 @@ class ViewpointRestorationTest extends CoreModelTestCase
$this->assertSame($viewpoint->getTitle(), $newViewpoint->getTitle()); $this->assertSame($viewpoint->getTitle(), $newViewpoint->getTitle());
$this->assertSame($viewpoint->getDescription(), $newViewpoint->getDescription()); $this->assertSame($viewpoint->getDescription(), $newViewpoint->getDescription());
$this->assertSame($viewpoint->getTemplate(), $newViewpoint->getTemplate()); $this->assertSame($viewpoint->getTemplate(), $newViewpoint->getTemplate());
$this->assertEquals($viewpoint->getActionGroups(), $newViewpoint->getActionGroups());;
for ($i=0; $i < count($viewpoint->getActionGroups()); $i++) {
$should = $viewpoint->getActionGroups()[$i];
$is = $newViewpoint->getActionGroups()[$i];
$this->assertSame($should->getId(), $is->getId());
$this->assertSame($should->getTitle(), $is->getTitle());
$this->assertSame($should->getSortKey(), $is->getSortKey());
$this->assertSame(count($should->getActions()), count($is->getActions()));
}
$this->assertSame($viewpoint->getData(), $newViewpoint->getData()); $this->assertSame($viewpoint->getData(), $newViewpoint->getData());
$this->assertSame($viewpoint->getAttachments(), $newViewpoint->getAttachments()); $this->assertSame($viewpoint->getAttachments(), $newViewpoint->getAttachments());
} }
@@ -120,7 +130,17 @@ class ViewpointRestorationTest extends CoreModelTestCase
$this->assertSame($viewpoint->getTitle(), $newViewpoint->getTitle()); $this->assertSame($viewpoint->getTitle(), $newViewpoint->getTitle());
$this->assertSame($viewpoint->getDescription(), $newViewpoint->getDescription()); $this->assertSame($viewpoint->getDescription(), $newViewpoint->getDescription());
$this->assertSame($viewpoint->getTemplate(), $newViewpoint->getTemplate()); $this->assertSame($viewpoint->getTemplate(), $newViewpoint->getTemplate());
$this->assertEquals($viewpoint->getActionGroups(), $newViewpoint->getActionGroups());;
for ($i=0; $i < count($viewpoint->getActionGroups()); $i++) {
$should = $viewpoint->getActionGroups()[$i];
$is = $newViewpoint->getActionGroups()[$i];
$this->assertSame($should->getId(), $is->getId());
$this->assertSame($should->getTitle(), $is->getTitle());
$this->assertSame($should->getSortKey(), $is->getSortKey());
$this->assertSame(count($should->getActions()), count($is->getActions()));
}
$this->assertSame($viewpoint->getData(), $newViewpoint->getData()); $this->assertSame($viewpoint->getData(), $newViewpoint->getData());
$this->assertSame($viewpoint->getAttachments(), $newViewpoint->getAttachments()); $this->assertSame($viewpoint->getAttachments(), $newViewpoint->getAttachments());
} }
+31 -4
View File
@@ -117,9 +117,18 @@ class ViewpointTest extends CoreModelTestCase
$em->clear(); $em->clear();
$output = $em->getRepository(Viewpoint::class)->find("10000000-0000-0000-0000-000000000002"); $output = $em->getRepository(Viewpoint::class)->find("10000000-0000-0000-0000-000000000002");
$this->assertEquals($actionGroups, $output->getActionGroups());
$this->assertEquals($ag2, $input->findActionGroupById('id2')); for ($i=0; $i < count($actionGroups); $i++) {
$should = $actionGroups[$i];
$is = $output->getActionGroups()[$i];
$this->assertSame($should->getId(), $is->getId());
$this->assertSame($should->getTitle(), $is->getTitle());
$this->assertSame($should->getSortKey(), $is->getSortKey());
$this->assertSame(count($should->getActions()), count($is->getActions()));
}
$this->assertEquals($ag2->getTitle(), $input->findActionGroupById('id2')->getTitle());
$this->assertNull($input->findActionGroupById('not-there')); $this->assertNull($input->findActionGroupById('not-there'));
$testAction = new Action("30000000-0000-0000-0000-000000000004"); $testAction = new Action("30000000-0000-0000-0000-000000000004");
@@ -186,7 +195,16 @@ class ViewpointTest extends CoreModelTestCase
// Not finding the scene ID should change nothing. // Not finding the scene ID should change nothing.
$output->removeActionsWithSceneId("30000000-0000-0000-0000-000000001000"); $output->removeActionsWithSceneId("30000000-0000-0000-0000-000000001000");
$this->assertEquals($actionGroups, $output->getActionGroups());
for ($i=0; $i < count($actionGroups); $i++) {
$should = $actionGroups[$i];
$is = $output->getActionGroups()[$i];
$this->assertSame($should->getId(), $is->getId());
$this->assertSame($should->getTitle(), $is->getTitle());
$this->assertSame($should->getSortKey(), $is->getSortKey());
$this->assertSame(count($should->getActions()), count($is->getActions()));
}
$ag1_output = new ActionGroup('id1', 'title1', 42); $ag1_output = new ActionGroup('id1', 'title1', 42);
$ag1_output->setActions([ $ag1_output->setActions([
@@ -199,7 +217,16 @@ class ViewpointTest extends CoreModelTestCase
$ag2 $ag2
]; ];
$output->removeActionsWithSceneId("30000000-0000-0000-0000-000000000002"); $output->removeActionsWithSceneId("30000000-0000-0000-0000-000000000002");
$this->assertEquals($actionGroupsWithout2, $output->getActionGroups());
for ($i=0; $i < count($actionGroups); $i++) {
$should = $actionGroupsWithout2[$i];
$is = $output->getActionGroups()[$i];
$this->assertSame($should->getId(), $is->getId());
$this->assertSame($should->getTitle(), $is->getTitle());
$this->assertSame($should->getSortKey(), $is->getSortKey());
$this->assertSame(count($should->getActions()), count($is->getActions()));
}
} }
public function testChangingSceneDescription() public function testChangingSceneDescription()
+41 -15
View File
@@ -9,6 +9,7 @@ use LotGD\Core\Exceptions\InsecureTwigTemplateError;
use LotGD\Core\Game; use LotGD\Core\Game;
use LotGD\Core\Models\Character; use LotGD\Core\Models\Character;
use LotGD\Core\Models\Scene; use LotGD\Core\Models\Scene;
use LotGD\Core\Models\Viewpoint;
use LotGD\Core\PHPUnit\LotGDTestCase; use LotGD\Core\PHPUnit\LotGDTestCase;
use LotGD\Core\Services\TwigSceneRenderer; use LotGD\Core\Services\TwigSceneRenderer;
@@ -43,12 +44,17 @@ class TwigSceneRendererTest extends LotGDTestCase
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
return [$game, $scene, $character, $eventManager]; $viewpoint = $this->getMockBuilder(Viewpoint::class)
->disableOriginalConstructor()
->getMock();
$viewpoint->method("getScene")->willReturn($scene);
return [$game, $viewpoint, $character, $eventManager];
} }
public function testIfSceneRendererCanBeConstructed() public function testIfSceneRendererCanBeConstructed()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1); $eventManager->method("publish")->willReturnArgument(1);
$renderer = new TwigSceneRenderer($game); $renderer = new TwigSceneRenderer($game);
@@ -58,7 +64,7 @@ class TwigSceneRendererTest extends LotGDTestCase
public function testIfTwigSceneRendererReturnsANonTemplateStringUnmodified() public function testIfTwigSceneRendererReturnsANonTemplateStringUnmodified()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1); $eventManager->method("publish")->willReturnArgument(1);
# Get renderer # Get renderer
@@ -68,7 +74,7 @@ class TwigSceneRendererTest extends LotGDTestCase
$template = "You enter a new location.\n\nA new location."; $template = "You enter a new location.\n\nA new location.";
# Create the result # Create the result
$renderResult = $renderer->render($template, $scene); $renderResult = $renderer->render($template, $viewpoint);
# Assert result # Assert result
$this->assertSame($template, $renderResult); $this->assertSame($template, $renderResult);
@@ -76,7 +82,7 @@ class TwigSceneRendererTest extends LotGDTestCase
public function testIfTwigSceneRendererParsesStringsWithCharacters() public function testIfTwigSceneRendererParsesStringsWithCharacters()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1); $eventManager->method("publish")->willReturnArgument(1);
# Get renderer # Get renderer
@@ -92,7 +98,27 @@ class TwigSceneRendererTest extends LotGDTestCase
."You are alive."; ."You are alive.";
# Create the result # Create the result
$renderResult = $renderer->render($template, $scene); $renderResult = $renderer->render($template, $viewpoint);
# Assert result
$this->assertSame($result, $renderResult);
}
public function testIfViewpointDataCanBeAccessed()
{
[$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1);
$viewpoint->method("getData")->willReturn(["test" => 7, "other" => "Hi"]);
# Get renderer
$renderer = new TwigSceneRenderer($game);
# Prepare the template string.
$template = "Hi {{ Viewpoint.data.test }}! {{ Viewpoint.data.other }}";
$result = "Hi 7! Hi";
# Create the result
$renderResult = $renderer->render($template, $viewpoint);
# Assert result # Assert result
$this->assertSame($result, $renderResult); $this->assertSame($result, $renderResult);
@@ -100,7 +126,7 @@ class TwigSceneRendererTest extends LotGDTestCase
public function testIfRawTemplateGetsReturnedIfTemplateContainsIllegalTokens() public function testIfRawTemplateGetsReturnedIfTemplateContainsIllegalTokens()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1); $eventManager->method("publish")->willReturnArgument(1);
# Get renderer # Get renderer
@@ -110,7 +136,7 @@ class TwigSceneRendererTest extends LotGDTestCase
$template = "Viewpoint: {{ Character.viewpoint }}"; $template = "Viewpoint: {{ Character.viewpoint }}";
# Try to parse the result # Try to parse the result
$renderResult = $renderer->render($template, $scene, true); $renderResult = $renderer->render($template, $viewpoint, true);
# If there was an error, it should have gotten ignored, giving back the raw template. # If there was an error, it should have gotten ignored, giving back the raw template.
$this->assertSame($template, $renderResult); $this->assertSame($template, $renderResult);
@@ -118,7 +144,7 @@ class TwigSceneRendererTest extends LotGDTestCase
public function testIfExceptionGetsRaisedIfTemplateContainsIllegalTokens() public function testIfExceptionGetsRaisedIfTemplateContainsIllegalTokens()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
$eventManager->method("publish")->willReturnArgument(1); $eventManager->method("publish")->willReturnArgument(1);
# Get renderer # Get renderer
@@ -131,12 +157,12 @@ class TwigSceneRendererTest extends LotGDTestCase
$this->expectException(InsecureTwigTemplateError::class); $this->expectException(InsecureTwigTemplateError::class);
# Try to parse the result # Try to parse the result
$renderResult = $renderer->render($template, $scene, false); $renderResult = $renderer->render($template, $viewpoint, false);
} }
public function testIfPublishedEventCanModifySecurityPolicy() public function testIfPublishedEventCanModifySecurityPolicy()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
# Set up a more complex "publish" method to emulate a real event. # Set up a more complex "publish" method to emulate a real event.
$eventManager->method("publish")->willReturnCallback(function($event, EventContextData $context) { $eventManager->method("publish")->willReturnCallback(function($event, EventContextData $context) {
@@ -164,15 +190,15 @@ class TwigSceneRendererTest extends LotGDTestCase
# Assert that if does not work anymore # Assert that if does not work anymore
$this->expectException(InsecureTwigTemplateError::class); $this->expectException(InsecureTwigTemplateError::class);
$renderer->render("{% if 5*1 %}Hallo{%endif%}", $scene, false); $renderer->render("{% if 5*1 %}Hallo{%endif%}", $viewpoint, false);
$this->expectException(InsecureTwigTemplateError::class); $this->expectException(InsecureTwigTemplateError::class);
$renderer->render("{{ Character.name }}", $scene, false); $renderer->render("{{ Character.name }}", $viewpoint, false);
} }
public function testIfPublishedEventCanModifyValueScope() public function testIfPublishedEventCanModifyValueScope()
{ {
[$game, $scene, $character, $eventManager] = $this->getMockeries(); [$game, $viewpoint, $character, $eventManager] = $this->getMockeries();
# Set up a more complex "publish" method to emulate a real event. # Set up a more complex "publish" method to emulate a real event.
$eventManager->method("publish")->willReturnCallback(function($event, EventContextData $context) { $eventManager->method("publish")->willReturnCallback(function($event, EventContextData $context) {
@@ -192,7 +218,7 @@ class TwigSceneRendererTest extends LotGDTestCase
$renderer = new TwigSceneRenderer($game); $renderer = new TwigSceneRenderer($game);
# Assert result # Assert result
$result = $renderer->render("{{ test }}", $scene, false); $result = $renderer->render("{{ test }}", $viewpoint, false);
$this->assertSame("A test", $result); $this->assertSame("A test", $result);
} }
} }