cyphesis/tests/rules/RulesIntegrationTest.cpp

821 lines
32 KiB
C++
Raw Permalink Normal View History

/*
Copyright (C) 2020 Erik Ogenvik
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "../TestBaseWithContext.h"
#include "../DatabaseNull.h"
#include "../TestWorld.h"
#include "common/Monitors.h"
#include "common/Inheritance.h"
#include "common/operations/Thought.h"
#include "rules/simulation/World.h"
#include "../NullEntityCreator.h"
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/Entity.h>
#include <wfmath/atlasconv.h>
#include <rules/simulation/CorePropertyManager.h>
2020-03-31 22:39:59 +02:00
#include <memory>
#include <rules/simulation/ModifiersProperty.h>
#include <rules/simulation/WorldRouter.h>
#include <rules/simulation/MindsProperty.h>
#include <rules/BBoxProperty.h>
#include "rules/AtlasProperties.h"
#include "rules/PhysicalProperties.h"
using Atlas::Objects::Operation::Set;
using Atlas::Objects::Operation::Wield;
using Atlas::Objects::Operation::Move;
using Atlas::Objects::Operation::Thought;
using Atlas::Objects::Entity::Anonymous;
using Atlas::Message::MapType;
using Atlas::Message::ListType;
struct TestContext
{
Atlas::Objects::Factories factories;
DatabaseNull database;
Ref<World> world;
Inheritance inheritance;
WorldRouter testWorld;
NullEntityCreator entityCreator;
CorePropertyManager propertyManager;
long entityId;
TestContext() :
world(new World()),
inheritance(factories),
2020-08-09 00:17:06 +02:00
testWorld(world, entityCreator, [] { return std::chrono::steady_clock::now().time_since_epoch(); }),
propertyManager(inheritance),
entityId(1)
{
}
~TestContext()
{
testWorld.shutdown();
}
};
OpVector resIgnored;
struct Tested : public Cyphesis::TestBaseWithContext<TestContext>
{
Tested()
{
ADD_TEST(test_move_entity);
ADD_TEST(test_delete_entity);
ADD_TEST(test_modifyWithAttachConstraints);
ADD_TEST(test_setModify);
ADD_TEST(test_setModifySelf);
ADD_TEST(test_setProps);
}
static void sendSetOp(const Ref<Thing>& entity, const std::string& propertyName, Atlas::Message::Element value)
{
Set set;
Anonymous arg1;
arg1->setAttr(propertyName, std::move(value));
set->setArgs1(arg1);
entity->operation(set, resIgnored);
}
void test_move_entity(TestContext& context)
{
auto thinkMoveFn = [&](const Ref<LocatedEntity>& destination, Anonymous ent) {
Move move;
move->setArgs1(ent);
Thought thought;
thought->setArgs1(move);
OpVector res;
destination->operation(thought, res);
for (auto& op : res) {
context.testWorld.operation(op, context.testWorld.getEntity(op->getFrom()));
}
};
/**
* Move within one domain
*
* All entities are placed at origo originally.
* Hierarchy looks like this:
* T1 has a physical domain
*
* T1#
* T2 T3
*
*/
{
Ref<Thing> t1 = new Thing(context.entityId++);
t1->requirePropertyClassFixed<BBoxProperty>().data() = {{-512, -10, -512},
{512, 10, 512}};
t1->setAttrValue("domain", "physical");
context.testWorld.addEntity(t1, context.world);
Ref<Thing> t2 = new Thing(context.entityId++);
t2->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t2->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t2->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t2, t1);
Ref<Thing> t3 = new Thing(context.entityId++);
t3->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t3->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t3->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t3, t1);
//Moving t2 directly should succeed.
OpVector res;
{
Move move;
Anonymous ent;
ent->setId(t2->getId());
ent->setPosAsList({10, 0, 10});
move->setArgs1(ent);
t2->MoveOperation(move, res);
}
ASSERT_EQUAL(t2->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(10, 0, 10))
//Moving t2 through t1 succeed.
{
Move move;
Anonymous ent;
ent->setId(t2->getId());
ent->setPosAsList({20, 0, 20});
ent->setAttr("orientation", WFMath::Quaternion(1, WFMath::numeric_constants<double>::pi() / 2.0).toAtlas());
move->setArgs1(ent);
t1->MoveOperation(move, res);
}
ASSERT_EQUAL(t2->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(20, 0, 20))
ASSERT_EQUAL(t2->requirePropertyClassFixed<OrientationProperty>().data(), WFMath::Quaternion(1, WFMath::numeric_constants<double>::pi() / 2.0))
//Moving t2 through t3 should fail.
{
Move move;
Anonymous ent;
ent->setId(t2->getId());
ent->setPosAsList({30, 0, 30});
move->setArgs1(ent);
t3->MoveOperation(move, res);
}
ASSERT_EQUAL(t2->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(20, 0, 20))
//Moving t2 directly to t3 should succeed.
{
Move move;
Anonymous ent;
ent->setId(t2->getId());
ent->setLoc(t3->getId());
move->setArgs1(ent);
t2->MoveOperation(move, res);
}
ASSERT_EQUAL(t2->m_parent, t3.get())
2020-06-13 18:00:03 +02:00
//Moving t2 directly to t1 should succeed even without pos and orientation.
{
Move move;
Anonymous ent;
ent->setId(t2->getId());
ent->setLoc(t1->getId());
move->setArgs1(ent);
t2->MoveOperation(move, res);
}
ASSERT_EQUAL(t2->m_parent, t1.get())
}
2020-06-13 18:00:03 +02:00
/**
* Move from one domain to another.
*
* All entities are placed at origo originally.
* Hierarchy looks like this:
* T1 has a physical domain.
* T2 and T3 have container domains.
2020-06-13 18:00:03 +02:00
*
* T1#
* T2* T3*
* T4 T5
*/
{
Ref<Thing> t1 = new Thing(context.entityId++);
t1->requirePropertyClassFixed<BBoxProperty>().data() = {{-512, -10, -512},
{512, 10, 512}};
t1->setAttrValue("domain", "physical");
context.testWorld.addEntity(t1, context.world);
Ref<Thing> t2 = new Thing(context.entityId++);
t2->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t2->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t2->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
t2->setAttrValue("domain", "container");
context.testWorld.addEntity(t2, t1);
Ref<Thing> t3 = new Thing(context.entityId++);
t3->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t3->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t3->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
t3->setAttrValue("domain", "container");
context.testWorld.addEntity(t3, t1);
Ref<Thing> t4 = new Thing(context.entityId++);
t4->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t4, t2);
Ref<Thing> t5 = new Thing(context.entityId++);
t5->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t5, t3);
2020-06-13 18:00:03 +02:00
//Moving t4 directly should succeed.
OpVector res;
{
Move move;
Anonymous ent;
ent->setId(t4->getId());
ent->setLoc(t3->getId());
move->setArgs1(ent);
t4->MoveOperation(move, res);
}
ASSERT_EQUAL(t4->m_parent, t3.get())
2020-06-13 18:00:03 +02:00
//Moving t4 through t3 (its parent) should succeed.
{
Move move;
Anonymous ent;
ent->setId(t4->getId());
ent->setLoc(t2->getId());
move->setArgs1(ent);
t3->MoveOperation(move, res);
}
ASSERT_EQUAL(t4->m_parent, t2.get())
2020-06-13 18:00:03 +02:00
//Moving t4 through t3 (its destination) should succeed.
{
Move move;
Anonymous ent;
ent->setId(t4->getId());
ent->setLoc(t3->getId());
move->setArgs1(ent);
t3->MoveOperation(move, res);
}
ASSERT_EQUAL(t4->m_parent, t3.get())
2020-06-13 18:00:03 +02:00
//Moving t4 through t1 (root domain) should fail.
{
Move move;
Anonymous ent;
ent->setId(t4->getId());
ent->setLoc(t2->getId());
move->setArgs1(ent);
t1->MoveOperation(move, res);
}
ASSERT_EQUAL(t4->m_parent, t3.get())
2020-06-13 18:00:03 +02:00
}
/**
* Let an entity move other entities.
*
* All entities are placed at origo originally.
* Hierarchy looks like this:
* T1 has a physical domain
* T2 has container domain.
* T6 is an entity with a mind, inventory and reach = 2.0
*
* T1#
* T2* T3 T6**
* T4 T5
*/
{
Ref<Thing> t1 = new Thing(context.entityId++);
t1->requirePropertyClassFixed<BBoxProperty>().data() = {{-512, -10, -512},
{512, 10, 512}};
t1->setAttrValue("domain", "physical");
context.testWorld.addEntity(t1, context.world);
Ref<Thing> t2 = new Thing(context.entityId++);
t2->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t2->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t2->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
t2->setAttrValue("domain", "container");
context.testWorld.addEntity(t2, t1);
Ref<Thing> t3 = new Thing(context.entityId++);
t3->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t3->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t3->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t3, t1);
Ref<Thing> t4 = new Thing(context.entityId++);
t4->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t4, t2);
Ref<Thing> t5 = new Thing(context.entityId++);
t5->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t5, t3);
Ref<Thing> t6 = new Thing(context.entityId++);
t6->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t6->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t6->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
t6->setAttrValue("domain", "inventory");
t6->setAttrValue("reach", 2.0);
t6->setAttrValue(MindsProperty::property_name, {});
context.testWorld.addEntity(t6, t1);
OpVector res;
//A Thought about a Move for moving t2 within t1 should work
{
Anonymous ent;
ent->setId(t2->getId());
ent->setPosAsList({1, 0, 0});
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t2->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(1, 0, 0))
//A Thought about a Move for moving t2 to t6 should work
{
Anonymous ent;
ent->setId(t2->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t2->m_parent, t6.get())
//Add a listener which blocks all operations to t1
struct : public OperationsListener
{
HandlerResult operation(LocatedEntity&, const Operation& op, OpVector& res) override
{
if (op->getClassNo() == Atlas::Objects::Operation::MOVE_NO) {
return OPERATION_BLOCKED;
}
return OPERATION_IGNORED;
}
} moveBlockListener;
t1->addListener(&moveBlockListener);
//A Thought about a Move for moving t3 to t6 should not work if there's a blocker on t1 (the parent of t3)
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
t1->removeListener(&moveBlockListener);
t6->addListener(&moveBlockListener);
//A Thought about a Move for moving t3 to t6 should not work if there's a blocker on t6
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
t6->removeListener(&moveBlockListener);
t3->addListener(&moveBlockListener);
//A Thought about a Move for moving t3 to t6 should not work if there's a blocker on t3
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
t3->removeListener(&moveBlockListener);
//A Thought about a Move for moving t3 to t6 should work if there no blockers
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
//A Thought about a Move for moving t3 to t1 should be blocked if the destination is too far away.
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t1->getId());
ent->setPosAsList({100.0, 0.0, 100.0});
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
//A Thought about a Move for moving t3 to t1 should be allowed if the destination is close.
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t1->getId());
ent->setPosAsList({1.0, 0.0, 1.0});
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
//Add a "mover" constaint to t6.
t6->setAttrValue("mover_constraint", "false");
//A Thought about a Move for moving t3 to t6 should not be allowed by the "mover_constraint"
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
t6->setAttrValue("mover_constraint", std::string("entity.id = ") + t3->getId());
//A Thought about a Move for moving t3 to t6 should be allowed if the "mover_constraint" matches.
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
//clear "mover_constraint"
t6->setAttrValue("mover_constraint", {});
t3->setAttrValue("move_constraint", "false");
//A Thought about a Move for moving t3 to t1 should not be allowed by the "move_constraint"
{
Anonymous ent;
ent->setId(t3->getId());
ent->setPosAsList({1.0, 0, 1.0});
ent->setLoc(t1->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
t3->setAttrValue("move_constraint", std::string("entity.id = ") + t3->getId());
//A Thought about a Move for moving t3 to t1 should be allowed if "move_constraint" matches
{
Anonymous ent;
ent->setId(t3->getId());
ent->setPosAsList({1.0, 0, 1.0});
ent->setLoc(t1->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
//clear "move_constraint"
t3->setAttrValue("move_constraint", {});
t1->setAttrValue("contain_constraint", "false");
//A Thought about a Move for moving t3 to t6 should not be allowed by the "contains_constraint" on t1
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
t1->setAttrValue("contain_constraint", "true");
//A Thought about a Move for moving t3 to t6 should not be allowed by the "contains_constraint" on t1
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t6->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
//clear "contain_constraint"
t1->setAttrValue("contain_constraint", {});
t1->setAttrValue("destination_constraint", "false");
//A Thought about a Move for moving t3 to t1 should not be allowed by the "destination_constraint" on t1
{
Anonymous ent;
ent->setId(t3->getId());
ent->setLoc(t1->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t6.get())
t1->setAttrValue("destination_constraint", "true");
//A Thought about a Move for moving t3 to t1 should be allowed is the "destination_constraint" matches
{
Anonymous ent;
ent->setId(t3->getId());
ent->setPosAsList({1.0, 0, 1.0});
ent->setLoc(t1->getId());
thinkMoveFn(t6, ent);
}
ASSERT_EQUAL(t3->m_parent, t1.get())
}
/**
* A standard case is not allowing a player entity to move too heavy things.
*
* All entities are placed at origo originally.
* Hierarchy looks like this:
* T1 has a physical domain
* T2 is an entity with a mind, inventory, reach = 2.0 and a "mover_constraint" of "mass < 20".
*
* T1#
* T2* T3
*/
{
Ref<Thing> t1 = new Thing(context.entityId++);
t1->requirePropertyClassFixed<BBoxProperty>().data() = {{-512, -10, -512},
{512, 10, 512}};
t1->setAttrValue("domain", "physical");
context.testWorld.addEntity(t1, context.world);
Ref<Thing> t2 = new Thing(context.entityId++);
t2->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t2->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t2->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
t2->setAttrValue("domain", "inventory");
t2->setAttrValue("reach", 2.0);
t2->setAttrValue(MindsProperty::property_name, {});
t2->setAttrValue("mover_constraint", "entity.mass = none or entity.mass < 20");
context.testWorld.addEntity(t2, t1);
Ref<Thing> t3 = new Thing(context.entityId++);
t3->requirePropertyClassFixed<PositionProperty>().data() = {0, 0, 0};
t3->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion::IDENTITY();
t3->requirePropertyClassFixed<BBoxProperty>().data() = {{-1, 0, -1},
{1, 1, 1}};
context.testWorld.addEntity(t3, t1);
OpVector res;
//A Thought about a Move for moving t3 within t1 should work
{
Anonymous ent;
ent->setId(t3->getId());
ent->setPosAsList({1, 0, 0});
thinkMoveFn(t2, ent);
}
ASSERT_EQUAL(t3->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(1, 0, 0))
t3->setAttrValue("mass", 30.0);
//A Thought about a Move for moving t3 within t1 should fail as t3 is too heavy
{
Anonymous ent;
ent->setId(t3->getId());
ent->setPosAsList({-1, 0, 0});
thinkMoveFn(t2, ent);
}
ASSERT_EQUAL(t3->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(1, 0, 0))
}
}
void test_delete_entity(TestContext& context)
{
WFMath::AxisBox<3> bbox(WFMath::Point<3>(-10, -10, -10), WFMath::Point<3>(10, 10, 10));
/**
* Handle the case where there's a physical domain and an Inventory domain.
*
* All entities are placed at origo
* Hierarchy looks like this:
* T1 has a physical domain
* T2 has a container domain
* T3, T4, T5 has no domain
*
* T1#
* T2* T3
* T4 T5
*/
{
Ref<Thing> t1 = new Thing(context.entityId++);
t1->requirePropertyClassFixed<BBoxProperty>().data() = {{-128, -128, -128},
{128, 128, 128}};
t1->setAttrValue("domain", "physical");
context.testWorld.addEntity(t1, context.world);
Ref<Thing> t2 = new Thing(context.entityId++);
t2->requirePropertyClassFixed<PositionProperty>().data() = {10, 0, 20};
t2->requirePropertyClassFixed<BBoxProperty>().data() = bbox;
t2->requirePropertyClassFixed<OrientationProperty>().data() = WFMath::Quaternion(1, 2.0);
t2->setAttrValue("domain", "container");
context.testWorld.addEntity(t2, t1);
Ref<Thing> t3 = new Thing(context.entityId++);
t3->requirePropertyClassFixed<PositionProperty>().data() = {20, 0, 20};
t3->requirePropertyClassFixed<BBoxProperty>().data() = bbox;
context.testWorld.addEntity(t3, t1);
Ref<Thing> t4 = new Thing(context.entityId++);
t4->requirePropertyClassFixed<PositionProperty>().data() = WFMath::Point<3>::ZERO();
t4->requirePropertyClassFixed<BBoxProperty>().data() = bbox;
t4->requirePropertyClassFixed<PositionProperty>().data() = {30, 0, 20};
context.testWorld.addEntity(t4, t2);
Ref<Thing> t5 = new Thing(context.entityId++);
t5->requirePropertyClassFixed<PositionProperty>().data() = WFMath::Point<3>::ZERO();
t5->requirePropertyClassFixed<BBoxProperty>().data() = bbox;
t5->requirePropertyClassFixed<PositionProperty>().data() = {40, 0, 20};
context.testWorld.addEntity(t5, t3);
OpVector res;
t2->DeleteOperation({}, res);
ASSERT_TRUE(t2->isDestroyed())
ASSERT_EQUAL(t1->getId(), t4->m_parent->getId())
ASSERT_EQUAL(t4->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(10, 0, 20))
ASSERT_EQUAL(t4->requirePropertyClassFixed<OrientationProperty>().data(), WFMath::Quaternion(1, 2.0))
t3->DeleteOperation({}, res);
ASSERT_TRUE(t3->isDestroyed())
ASSERT_EQUAL(t1->getId(), t5->m_parent->getId())
ASSERT_EQUAL(t5->requirePropertyClassFixed<PositionProperty>().data(), WFMath::Point<3>(20, 0, 20))
}
}
void test_setProps(TestContext& context)
{
Ref<Thing> entity(new Thing(context.entityId++));
ASSERT_FALSE(entity->getAttr("foo"));
sendSetOp(entity, "foo", 1);
ASSERT_TRUE(entity->getAttr("foo"));
ASSERT_EQUAL(*entity->getAttr("foo"), 1);
sendSetOp(entity, "foo!append", 2);
ASSERT_EQUAL(*entity->getAttr("foo"), 3);
sendSetOp(entity, "foo!prepend", 2);
ASSERT_EQUAL(*entity->getAttr("foo"), 5);
sendSetOp(entity, "foo!subtract", 3);
ASSERT_EQUAL(*entity->getAttr("foo"), 2);
}
void test_setModifySelf(TestContext& context)
{
Ref<Thing> entity(new Thing(context.entityId++));
sendSetOp(entity, "modify_self", MapType{{"set_by_test",
MapType{
{"constraint", "true = true"},
{"modifiers", MapType{
{"foo", MapType{
{"append", 1.0}}},
{"bar", MapType{
{"default", 1.0}}}
}}
}}});
ASSERT_TRUE(entity->getAttr("foo"));
ASSERT_EQUAL(*entity->getAttr("foo"), 1.0);
ASSERT_TRUE(entity->getAttr("bar"));
ASSERT_EQUAL(*entity->getAttr("bar"), 1.0);
sendSetOp(entity, "foo", 2.0);
ASSERT_EQUAL(*entity->getAttr("foo"), 3.0);
2020-03-31 22:39:59 +02:00
//Removing the "bar" modifier should remove the "bar" property.
sendSetOp(entity, "modify_self", MapType{{"set_by_test",
MapType{
{"constraint", "true = true"},
{"modifiers", MapType{
{"foo", MapType{
{"append", 1.0}}}
}}
}}});
ASSERT_EQUAL(*entity->getAttr("bar"), Atlas::Message::Element());
}
void test_setModify(TestContext& context)
{
Ref<Thing> entity(new Thing(context.entityId++));
Ref<Thing> entityChild(new Thing(context.entityId++));
entity->addChild(*entityChild);
sendSetOp(entityChild, "modify", ListType{
MapType{
{"constraint", "true = true"},
{"modifiers", MapType{
{"foo", MapType{
{"append", 1.0}}},
{"bar", MapType{
{"default", 1.0}}}
}}
}});
ASSERT_TRUE(entity->getAttr("foo"));
ASSERT_EQUAL(*entity->getAttr("foo"), 1.0);
ASSERT_TRUE(entity->getAttr("bar"));
ASSERT_EQUAL(*entity->getAttr("bar"), 1.0);
sendSetOp(entity, "foo", 2.0);
ASSERT_EQUAL(*entity->getAttr("foo"), 3.0);
2020-03-31 22:39:59 +02:00
//Trying to alter "_modifiers" should not be possible.
sendSetOp(entity, ModifiersProperty::property_name, ListType{});
ASSERT_TRUE(entity->getAttr("bar"));
ASSERT_EQUAL(*entity->getAttr("bar"), 1.0);
//Removing the "bar" modifier should remove the "bar" property.
sendSetOp(entityChild, "modify", ListType{
MapType{
{"constraint", "true = true"},
{"modifiers", MapType{
{"foo", MapType{
{"append", 1.0}}}
}}
}});
ASSERT_EQUAL(*entity->getAttr("bar"), Atlas::Message::Element());
entityChild->destroy();
2020-03-31 22:39:59 +02:00
}
void test_modifyWithAttachConstraints(TestContext& context)
{
Ref<Thing> entity(new Thing(context.entityId++));
context.testWorld.addEntity(entity, context.world);
sendSetOp(entity, "attachments", MapType{{"hand_primary", "contains(actor.contains, child = tool)"}});
Ref<Thing> entityChild(new Thing(context.entityId++));
context.testWorld.addEntity(entityChild, entity);
sendSetOp(entityChild, "modify", ListType{
MapType{
{"constraint", "entity.mode_data.mode = 'planted'"},
{"modifiers", MapType{
{"foo", MapType{
{"append", 1.0}}},
{"bar", MapType{
{"default", 1.0}}}
}},
{"observed_properties", ListType{"attached_hand_primary"}}
}});
ASSERT_FALSE(entity->getAttr("foo"))
{
Wield wield;
Anonymous arg1;
arg1->setAttr("attachment", "hand_primary");
arg1->setId(entityChild->getId());
wield->setArgs1(arg1);
entity->operation(wield, resIgnored);
}
ASSERT_TRUE(entity->getAttr("foo"))
ASSERT_EQUAL(*entity->getAttr("foo"), 1.0)
entityChild->destroy();
}
};
int main()
{
Monitors m;
Tested t;
return t.run();
}