diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-05-03 14:41:01 -0400 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-05-03 14:41:01 -0400 |
commit | 83f51a6892629921b4cc482b986656a0a5cc5f6a (patch) | |
tree | 50dcd4485a165b29591dafbec907c20dcf9284b1 /src/systems | |
parent | b2311e1ba43a72a205b51a24376a1f363faa569e (diff) | |
download | therapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.tar.gz therapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.tar.bz2 therapy-83f51a6892629921b4cc482b986656a0a5cc5f6a.zip |
Added simple AI implementation
The new AutomatingSystem and AutomatableComponent are responsible for simple AI tasks. This currently is limited to moving entities at a certain speed for certain periods of time. These tasks are arranged as a set of behaviors, which are picked randomly when automation starts or when a behavior finishes executing. A behavior is a sequence of actions that run one after another. Currently, if an automated entity is blocked from moving by a collision, it will be coerced out of its intended path. This is because the automation parameters are stored as a speed and a duration, rather than a starting location and an ending location. This may end up being changed, or made configurable, as this is an early implementation of this feature and will need to be more complex later. Added an RNG object to the Game class, so that the AutomatingSystem can pick behaviors at random.
Diffstat (limited to 'src/systems')
-rw-r--r-- | src/systems/automating.cpp | 71 | ||||
-rw-r--r-- | src/systems/automating.h | 19 | ||||
-rw-r--r-- | src/systems/realizing.cpp | 174 |
3 files changed, 264 insertions, 0 deletions
diff --git a/src/systems/automating.cpp b/src/systems/automating.cpp new file mode 100644 index 0000000..0d85957 --- /dev/null +++ b/src/systems/automating.cpp | |||
@@ -0,0 +1,71 @@ | |||
1 | #include "automating.h" | ||
2 | #include "game.h" | ||
3 | #include "components/automatable.h" | ||
4 | #include "components/ponderable.h" | ||
5 | #include "systems/pondering.h" | ||
6 | |||
7 | void AutomatingSystem::tick(double dt) | ||
8 | { | ||
9 | auto entities = game_.getEntityManager().getEntitiesWithComponents< | ||
10 | AutomatableComponent, | ||
11 | PonderableComponent>(); | ||
12 | |||
13 | for (id_type entity : entities) | ||
14 | { | ||
15 | auto& automatable = game_.getEntityManager(). | ||
16 | getComponent<AutomatableComponent>(entity); | ||
17 | |||
18 | if (!automatable.active) | ||
19 | { | ||
20 | continue; | ||
21 | } | ||
22 | |||
23 | if (automatable.behaviorRunning && | ||
24 | (automatable.remaining <= 0.0)) | ||
25 | { | ||
26 | automatable.currentAction++; | ||
27 | automatable.actionRunning = false; | ||
28 | |||
29 | if (automatable.currentAction == | ||
30 | automatable.behaviors[automatable.currentBehavior].size()) | ||
31 | { | ||
32 | automatable.behaviorRunning = false; | ||
33 | } | ||
34 | } | ||
35 | |||
36 | if (!automatable.behaviorRunning) | ||
37 | { | ||
38 | automatable.currentBehavior = automatable.behaviorDist(game_.getRng()); | ||
39 | automatable.currentAction = 0; | ||
40 | automatable.behaviorRunning = true; | ||
41 | } | ||
42 | |||
43 | AutomatableComponent::Action& curAction = | ||
44 | automatable.behaviors | ||
45 | [automatable.currentBehavior] | ||
46 | [automatable.currentAction]; | ||
47 | |||
48 | if (!automatable.actionRunning) | ||
49 | { | ||
50 | automatable.remaining = curAction.dur; | ||
51 | automatable.actionRunning = true; | ||
52 | } | ||
53 | |||
54 | auto& ponderable = game_.getEntityManager(). | ||
55 | getComponent<PonderableComponent>(entity); | ||
56 | |||
57 | ponderable.velX = curAction.speedX; | ||
58 | ponderable.velY = curAction.speedY; | ||
59 | |||
60 | automatable.remaining -= dt; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | void AutomatingSystem::initPrototype(id_type prototype) | ||
65 | { | ||
66 | auto& automatable = game_.getEntityManager(). | ||
67 | getComponent<AutomatableComponent>(prototype); | ||
68 | |||
69 | automatable.behaviorRunning = false; | ||
70 | automatable.actionRunning = false; | ||
71 | } | ||
diff --git a/src/systems/automating.h b/src/systems/automating.h new file mode 100644 index 0000000..c78b7cf --- /dev/null +++ b/src/systems/automating.h | |||
@@ -0,0 +1,19 @@ | |||
1 | #ifndef AUTOMATING_H_E6E5D76E | ||
2 | #define AUTOMATING_H_E6E5D76E | ||
3 | |||
4 | #include "system.h" | ||
5 | |||
6 | class AutomatingSystem : public System { | ||
7 | public: | ||
8 | |||
9 | AutomatingSystem(Game& game) : System(game) | ||
10 | { | ||
11 | } | ||
12 | |||
13 | void tick(double dt); | ||
14 | |||
15 | void initPrototype(id_type prototype); | ||
16 | |||
17 | }; | ||
18 | |||
19 | #endif /* end of include guard: AUTOMATING_H_E6E5D76E */ | ||
diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp index 3656acb..8e670ac 100644 --- a/src/systems/realizing.cpp +++ b/src/systems/realizing.cpp | |||
@@ -12,9 +12,11 @@ | |||
12 | #include "components/playable.h" | 12 | #include "components/playable.h" |
13 | #include "components/ponderable.h" | 13 | #include "components/ponderable.h" |
14 | #include "components/transformable.h" | 14 | #include "components/transformable.h" |
15 | #include "components/automatable.h" | ||
15 | #include "systems/mapping.h" | 16 | #include "systems/mapping.h" |
16 | #include "systems/animating.h" | 17 | #include "systems/animating.h" |
17 | #include "systems/pondering.h" | 18 | #include "systems/pondering.h" |
19 | #include "systems/automating.h" | ||
18 | 20 | ||
19 | inline xmlChar* getProp(xmlNodePtr node, const char* attr) | 21 | inline xmlChar* getProp(xmlNodePtr node, const char* attr) |
20 | { | 22 | { |
@@ -27,6 +29,92 @@ inline xmlChar* getProp(xmlNodePtr node, const char* attr) | |||
27 | return key; | 29 | return key; |
28 | } | 30 | } |
29 | 31 | ||
32 | void parseAI( | ||
33 | xmlNodePtr node, | ||
34 | std::vector<AutomatableComponent::Action>& behavior, | ||
35 | const std::map<std::string, int>& items) | ||
36 | { | ||
37 | xmlChar* key = nullptr; | ||
38 | |||
39 | if (!xmlStrcmp( | ||
40 | node->name, | ||
41 | reinterpret_cast<const xmlChar*>("switch"))) | ||
42 | { | ||
43 | key = getProp(node, "item"); | ||
44 | std::string switchItem = reinterpret_cast<char*>(key); | ||
45 | xmlFree(key); | ||
46 | |||
47 | for (xmlNodePtr switchNode = node->xmlChildrenNode; | ||
48 | switchNode != nullptr; | ||
49 | switchNode = switchNode->next) | ||
50 | { | ||
51 | if (!xmlStrcmp( | ||
52 | switchNode->name, | ||
53 | reinterpret_cast<const xmlChar*>("case"))) | ||
54 | { | ||
55 | key = getProp(switchNode, "value"); | ||
56 | int caseValue = atoi(reinterpret_cast<char*>(key)); | ||
57 | xmlFree(key); | ||
58 | |||
59 | if (items.at(switchItem) == caseValue) | ||
60 | { | ||
61 | for (xmlNodePtr caseNode = switchNode->xmlChildrenNode; | ||
62 | caseNode != nullptr; | ||
63 | caseNode = caseNode->next) | ||
64 | { | ||
65 | parseAI( | ||
66 | caseNode, | ||
67 | behavior, | ||
68 | items); | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | } else if (!xmlStrcmp( | ||
74 | node->name, | ||
75 | reinterpret_cast<const xmlChar*>("move"))) | ||
76 | { | ||
77 | key = getProp(node, "direction"); | ||
78 | std::string direction = reinterpret_cast<char*>(key); | ||
79 | xmlFree(key); | ||
80 | |||
81 | key = getProp(node, "length-var"); | ||
82 | std::string lengthVar = reinterpret_cast<char*>(key); | ||
83 | xmlFree(key); | ||
84 | |||
85 | key = getProp(node, "speed-var"); | ||
86 | std::string speedVar = reinterpret_cast<char*>(key); | ||
87 | xmlFree(key); | ||
88 | |||
89 | double length = items.at(lengthVar); | ||
90 | double speed = items.at(speedVar); | ||
91 | |||
92 | AutomatableComponent::Action action; | ||
93 | |||
94 | if (direction == "left") | ||
95 | { | ||
96 | action.speedX = -speed; | ||
97 | action.speedY = 0; | ||
98 | } else if (direction == "right") | ||
99 | { | ||
100 | action.speedX = speed; | ||
101 | action.speedY = 0; | ||
102 | } else if (direction == "up") | ||
103 | { | ||
104 | action.speedX = 0; | ||
105 | action.speedY = -speed; | ||
106 | } else if (direction == "down") | ||
107 | { | ||
108 | action.speedX = 0; | ||
109 | action.speedY = speed; | ||
110 | } | ||
111 | |||
112 | action.dur = length / speed; | ||
113 | |||
114 | behavior.push_back(std::move(action)); | ||
115 | } | ||
116 | } | ||
117 | |||
30 | // TODO: neither the XML doc nor any of the emplaced entities are properly | 118 | // TODO: neither the XML doc nor any of the emplaced entities are properly |
31 | // destroyed if this method throws an exception. | 119 | // destroyed if this method throws an exception. |
32 | EntityManager::id_type RealizingSystem::initSingleton( | 120 | EntityManager::id_type RealizingSystem::initSingleton( |
@@ -211,6 +299,70 @@ EntityManager::id_type RealizingSystem::initSingleton( | |||
211 | game_.getSystemManager().getSystem<PonderingSystem>(). | 299 | game_.getSystemManager().getSystem<PonderingSystem>(). |
212 | initializeBody(mapObject, PonderableComponent::Type::vacuumed); | 300 | initializeBody(mapObject, PonderableComponent::Type::vacuumed); |
213 | 301 | ||
302 | // Look for any object configuration. | ||
303 | std::map<std::string, int> items; | ||
304 | |||
305 | for (xmlNodePtr objectNode = mapNode->xmlChildrenNode; | ||
306 | objectNode != nullptr; | ||
307 | objectNode = objectNode->next) | ||
308 | { | ||
309 | if (!xmlStrcmp( | ||
310 | objectNode->name, | ||
311 | reinterpret_cast<const xmlChar*>("item"))) | ||
312 | { | ||
313 | key = getProp(objectNode, "id"); | ||
314 | std::string itemName = reinterpret_cast<char*>(key); | ||
315 | xmlFree(key); | ||
316 | |||
317 | key = xmlNodeGetContent(objectNode); | ||
318 | int itemVal = atoi(reinterpret_cast<char*>(key)); | ||
319 | xmlFree(key); | ||
320 | |||
321 | items[itemName] = itemVal; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | // Add any AI behaviors. | ||
326 | std::vector<double> behaviorWeights; | ||
327 | |||
328 | for (xmlNodePtr protoSubNode = prototypeNode->xmlChildrenNode; | ||
329 | protoSubNode != nullptr; | ||
330 | protoSubNode = protoSubNode->next) | ||
331 | { | ||
332 | if (!xmlStrcmp( | ||
333 | protoSubNode->name, | ||
334 | reinterpret_cast<const xmlChar*>("ai"))) | ||
335 | { | ||
336 | if (!game_.getEntityManager(). | ||
337 | hasComponent<AutomatableComponent>(mapObject)) | ||
338 | { | ||
339 | game_.getEntityManager(). | ||
340 | emplaceComponent<AutomatableComponent>(mapObject); | ||
341 | } | ||
342 | |||
343 | auto& automatable = game_.getEntityManager(). | ||
344 | getComponent<AutomatableComponent>(mapObject); | ||
345 | |||
346 | key = getProp(protoSubNode, "chance"); | ||
347 | behaviorWeights.push_back(atof(reinterpret_cast<char*>(key))); | ||
348 | xmlFree(key); | ||
349 | |||
350 | std::vector<AutomatableComponent::Action> behavior; | ||
351 | |||
352 | for (xmlNodePtr aiNode = protoSubNode->xmlChildrenNode; | ||
353 | aiNode != nullptr; | ||
354 | aiNode = aiNode->next) | ||
355 | { | ||
356 | parseAI( | ||
357 | aiNode, | ||
358 | behavior, | ||
359 | items); | ||
360 | } | ||
361 | |||
362 | automatable.behaviors.push_back(std::move(behavior)); | ||
363 | } | ||
364 | } | ||
365 | |||
214 | mappable.objects.push_back(mapObject); | 366 | mappable.objects.push_back(mapObject); |
215 | } else if (!xmlStrcmp( | 367 | } else if (!xmlStrcmp( |
216 | mapNode->name, | 368 | mapNode->name, |
@@ -304,6 +456,7 @@ void RealizingSystem::loadMap(id_type mapEntity) | |||
304 | 456 | ||
305 | auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>(); | 457 | auto& animating = game_.getSystemManager().getSystem<AnimatingSystem>(); |
306 | auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>(); | 458 | auto& pondering = game_.getSystemManager().getSystem<PonderingSystem>(); |
459 | auto& automating = game_.getSystemManager().getSystem<AutomatingSystem>(); | ||
307 | 460 | ||
308 | std::set<id_type> players = | 461 | std::set<id_type> players = |
309 | game_.getEntityManager().getEntitiesWithComponents< | 462 | game_.getEntityManager().getEntitiesWithComponents< |
@@ -366,6 +519,11 @@ void RealizingSystem::loadMap(id_type mapEntity) | |||
366 | pondering.initPrototype(prototype); | 519 | pondering.initPrototype(prototype); |
367 | } | 520 | } |
368 | 521 | ||
522 | if (game_.getEntityManager().hasComponent<AutomatableComponent>(prototype)) | ||
523 | { | ||
524 | automating.initPrototype(prototype); | ||
525 | } | ||
526 | |||
369 | enterActiveMap(prototype); | 527 | enterActiveMap(prototype); |
370 | } | 528 | } |
371 | 529 | ||
@@ -399,6 +557,14 @@ void RealizingSystem::enterActiveMap(id_type entity) | |||
399 | 557 | ||
400 | ponderable.active = true; | 558 | ponderable.active = true; |
401 | } | 559 | } |
560 | |||
561 | if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity)) | ||
562 | { | ||
563 | auto& automatable = game_.getEntityManager(). | ||
564 | getComponent<AutomatableComponent>(entity); | ||
565 | |||
566 | automatable.active = true; | ||
567 | } | ||
402 | } | 568 | } |
403 | 569 | ||
404 | void RealizingSystem::leaveActiveMap(id_type entity) | 570 | void RealizingSystem::leaveActiveMap(id_type entity) |
@@ -418,4 +584,12 @@ void RealizingSystem::leaveActiveMap(id_type entity) | |||
418 | 584 | ||
419 | ponderable.active = false; | 585 | ponderable.active = false; |
420 | } | 586 | } |
587 | |||
588 | if (game_.getEntityManager().hasComponent<AutomatableComponent>(entity)) | ||
589 | { | ||
590 | auto& automatable = game_.getEntityManager(). | ||
591 | getComponent<AutomatableComponent>(entity); | ||
592 | |||
593 | automatable.active = false; | ||
594 | } | ||
421 | } | 595 | } |