]> git.localhorst.tv Git - blobs.git/blob - src/creature/goal.cpp
overhaul need system
[blobs.git] / src / creature / goal.cpp
1 #include "BlobBackgroundTask.hpp"
2 #include "Goal.hpp"
3 #include "IdleGoal.hpp"
4 #include "IngestGoal.hpp"
5 #include "LocateResourceGoal.hpp"
6
7 #include "Creature.hpp"
8 #include "../app/Assets.hpp"
9 #include "../world/Planet.hpp"
10 #include "../world/Resource.hpp"
11 #include "../world/Simulation.hpp"
12 #include "../world/TileType.hpp"
13
14 #include <cstring>
15 #include <iostream>
16 #include <sstream>
17 #include <glm/gtx/io.hpp>
18
19
20 namespace blobs {
21 namespace creature {
22
23 BlobBackgroundTask::BlobBackgroundTask(Creature &c)
24 : Goal(c)
25 , breathing(false)
26 , drink_subtask(nullptr)
27 , eat_subtask(nullptr) {
28 }
29
30 BlobBackgroundTask::~BlobBackgroundTask() {
31 }
32
33 std::string BlobBackgroundTask::Describe() const {
34         return "being a blob";
35 }
36
37 void BlobBackgroundTask::Tick(double dt) {
38         if (breathing) {
39                 // TODO: derive breathing ability
40                 double amount = GetCreature().GetStats().Breath().gain * -(1.0 + GetCreature().ExhaustionFactor());
41                 GetCreature().GetStats().Breath().Add(amount * dt);
42                 if (GetCreature().GetStats().Breath().Empty()) {
43                         breathing = false;
44                 }
45         }
46 }
47
48 void BlobBackgroundTask::Action() {
49         CheckStats();
50         CheckSplit();
51         CheckMutate();
52 }
53
54 void BlobBackgroundTask::CheckStats() {
55         Creature::Stats &stats = GetCreature().GetStats();
56
57         if (!breathing && stats.Breath().Bad()) {
58                 breathing = true;
59         }
60
61         if (!drink_subtask && stats.Thirst().Bad()) {
62                 drink_subtask = new IngestGoal(GetCreature(), stats.Thirst());
63                 for (const auto &cmp : GetCreature().GetComposition()) {
64                         if (Assets().data.resources[cmp.resource].state == world::Resource::LIQUID) {
65                                 drink_subtask->Accept(cmp.resource, 1.0);
66                         }
67                 }
68                 drink_subtask->OnComplete([&](Goal &) { drink_subtask = nullptr; });
69                 GetCreature().AddGoal(std::unique_ptr<Goal>(drink_subtask));
70         }
71
72         if (!eat_subtask && stats.Hunger().Bad()) {
73                 eat_subtask = new IngestGoal(GetCreature(), stats.Hunger());
74                 for (const auto &cmp : GetCreature().GetComposition()) {
75                         if (Assets().data.resources[cmp.resource].state == world::Resource::SOLID) {
76                                 eat_subtask->Accept(cmp.resource, 1.0);
77                         }
78                 }
79                 eat_subtask->OnComplete([&](Goal &) { eat_subtask = nullptr; });
80                 GetCreature().AddGoal(std::unique_ptr<Goal>(eat_subtask));
81         }
82 }
83
84 void BlobBackgroundTask::CheckSplit() {
85         if (GetCreature().Mass() > GetCreature().OffspringMass() * 2.0
86                 && GetCreature().OffspringChance() > Assets().random.UNorm()) {
87                 std::cout << "[" << int(GetCreature().GetSimulation().Time())
88                         << "s] " << GetCreature().Name() << " split" << std::endl;
89                 Split(GetCreature());
90                 return;
91         }
92 }
93
94 void BlobBackgroundTask::CheckMutate() {
95         // check for random property mutation
96         if (GetCreature().MutateChance() > Assets().random.UNorm()) {
97                 double amount = 1.0 + (Assets().random.SNorm() * 0.05);
98                 math::Distribution &d = GetCreature().GetGenome().properties.props[(int(Assets().random.UNorm() * 8.0) % 8)];
99                 if (Assets().random.UNorm() < 0.5) {
100                         d.Mean(d.Mean() * amount);
101                 } else {
102                         d.StandardDeviation(d.StandardDeviation() * amount);
103                 }
104         }
105 }
106
107 namespace {
108
109 std::string summarize(const Composition &comp, const app::Assets &assets) {
110         std::stringstream s;
111         bool first = true;
112         for (const auto &c : comp) {
113                 if (first) {
114                         first = false;
115                 } else {
116                         s << " or ";
117                 }
118                 s << assets.data.resources[c.resource].label;
119         }
120         return s.str();
121 }
122
123 }
124
125 IngestGoal::IngestGoal(Creature &c, Creature::Stat &stat)
126 : Goal(c)
127 , stat(stat)
128 , accept()
129 , locate_subtask(nullptr)
130 , ingesting(false)
131 , resource(-1)
132 , yield(0.0) {
133         Urgency(stat.value);
134 }
135
136 IngestGoal::~IngestGoal() {
137 }
138
139 void IngestGoal::Accept(int resource, double value) {
140         accept.Add(resource, value);
141 }
142
143 std::string IngestGoal::Describe() const {
144         if (resource == -1) {
145                 return "ingest " + summarize(accept, Assets());
146         } else {
147                 const world::Resource &r = Assets().data.resources[resource];
148                 if (r.state == world::Resource::SOLID) {
149                         return "eat " + r.label;
150                 } else {
151                         return "drink " + r.label;
152                 }
153         }
154 }
155
156 void IngestGoal::Enable() {
157 }
158
159 void IngestGoal::Tick(double dt) {
160         Urgency(stat.value);
161         if (locate_subtask) {
162                 locate_subtask->Urgency(Urgency() + 0.1);
163         }
164         if (ingesting) {
165                 if (OnSuitableTile() && !GetSituation().Moving()) {
166                         // TODO: determine satisfaction factor
167                         GetCreature().Ingest(resource, yield * dt);
168                         stat.Add(-1.0 * yield * dt);
169                         if (stat.Empty()) {
170                                 SetComplete();
171                         }
172                 } else {
173                         // left tile somehow, some idiot probably pushed us off
174                         ingesting = false;
175                         Interruptible(true);
176                 }
177         }
178 }
179
180 void IngestGoal::Action() {
181         if (ingesting) {
182                 // all good
183                 return;
184         }
185         if (OnSuitableTile()) {
186                 if (GetSituation().Moving()) {
187                         // break with maximum force
188                         GetSteering().Haste(1.0);
189                         GetSteering().Halt();
190                 } else {
191                         // finally
192                         Interruptible(false);
193                         ingesting = true;
194                 }
195         } else {
196                 locate_subtask = new LocateResourceGoal(GetCreature());
197                 for (const auto &c : accept) {
198                         locate_subtask->Accept(c.resource, c.value);
199                 }
200                 locate_subtask->Urgency(Urgency() + 0.1);
201                 locate_subtask->OnComplete([&](Goal &){ locate_subtask = nullptr; });
202                 GetCreature().AddGoal(std::unique_ptr<Goal>(locate_subtask));
203         }
204 }
205
206 bool IngestGoal::OnSuitableTile() {
207         if (!GetSituation().OnSurface()) {
208                 return false;
209         }
210         const world::TileType &t = GetSituation().GetTileType();
211         auto found = t.FindBestResource(accept);
212         if (found != t.resources.end()) {
213                 resource = found->resource;
214                 yield = found->ubiquity;
215                 return true;
216         } else {
217                 resource = -1;
218                 return false;
219         }
220 }
221
222
223 Goal::Goal(Creature &c)
224 : c(c)
225 , on_complete()
226 , urgency(0.0)
227 , interruptible(true)
228 , complete(false) {
229 }
230
231 Goal::~Goal() noexcept {
232 }
233
234 Situation &Goal::GetSituation() noexcept {
235         return c.GetSituation();
236 }
237
238 const Situation &Goal::GetSituation() const noexcept {
239         return c.GetSituation();
240 }
241
242 Steering &Goal::GetSteering() noexcept {
243         return c.GetSteering();
244 }
245
246 const Steering &Goal::GetSteering() const noexcept {
247         return c.GetSteering();
248 }
249
250 app::Assets &Goal::Assets() noexcept {
251         return c.GetSimulation().Assets();
252 }
253
254 const app::Assets &Goal::Assets() const noexcept {
255         return c.GetSimulation().Assets();
256 }
257
258 void Goal::SetComplete() noexcept {
259         if (!complete) {
260                 complete = true;
261                 if (on_complete) {
262                         on_complete(*this);
263                 }
264         }
265 }
266
267 void Goal::OnComplete(std::function<void(Goal &)> cb) noexcept {
268         on_complete = cb;
269         if (complete) {
270                 on_complete(*this);
271         }
272 }
273
274
275 IdleGoal::IdleGoal(Creature &c)
276 : Goal(c) {
277         Urgency(-1.0);
278         Interruptible(true);
279 }
280
281 IdleGoal::~IdleGoal() {
282 }
283
284 std::string IdleGoal::Describe() const {
285         return "idle";
286 }
287
288 void IdleGoal::Enable() {
289 }
290
291 void IdleGoal::Tick(double dt) {
292 }
293
294 void IdleGoal::Action() {
295 }
296
297
298 LocateResourceGoal::LocateResourceGoal(Creature &c)
299 : Goal(c)
300 , accept()
301 , found(false)
302 , target_pos(0.0)
303 , target_srf(0)
304 , target_tile(0)
305 , searching(false)
306 , reevaluate(0.0) {
307 }
308
309 LocateResourceGoal::~LocateResourceGoal() noexcept {
310 }
311
312 void LocateResourceGoal::Accept(int resource, double value) {
313         accept.Add(resource, value);
314 }
315
316 std::string LocateResourceGoal::Describe() const {
317         return "locate " + summarize(accept, Assets());
318 }
319
320 void LocateResourceGoal::Enable() {
321
322 }
323
324 void LocateResourceGoal::Tick(double dt) {
325         reevaluate -= dt;
326 }
327
328 void LocateResourceGoal::Action() {
329         if (reevaluate < 0.0) {
330                 LocateResource();
331                 reevaluate = 3.0;
332         } else if (!found) {
333                 if (!searching) {
334                         LocateResource();
335                 } else {
336                         double dist = glm::length2(GetSituation().Position() - target_pos);
337                         if (dist < 0.0001) {
338                                 LocateResource();
339                         } else {
340                                 GetSteering().GoTo(target_pos);
341                         }
342                 }
343         } else if (OnTargetTile()) {
344                 GetSteering().Halt();
345                 if (!GetSituation().Moving()) {
346                         SetComplete();
347                 }
348         } else {
349                 GetSteering().GoTo(target_pos);
350         }
351         GetSteering().Haste(Urgency());
352 }
353
354 void LocateResourceGoal::LocateResource() {
355         if (GetSituation().OnSurface()) {
356                 const world::TileType &t = GetSituation().GetTileType();
357                 auto yield = t.FindBestResource(accept);
358                 if (yield != t.resources.cend()) {
359                         // hoooray
360                         GetSteering().Halt();
361                         found = true;
362                         searching = false;
363                         target_pos = GetSituation().Position();
364                         target_srf = GetSituation().Surface();
365                         target_tile = GetSituation().GetPlanet().SurfacePosition(target_srf, target_pos);
366                 } else {
367                         // go find somewhere else
368                         SearchVicinity();
369                 }
370         } else {
371                 // well, what now?
372         }
373 }
374
375 void LocateResourceGoal::SearchVicinity() {
376         const world::Planet &planet = GetSituation().GetPlanet();
377         int srf = GetSituation().Surface();
378         const glm::dvec3 &pos = GetSituation().Position();
379
380         glm::ivec2 loc = planet.SurfacePosition(srf, pos);
381         glm::ivec2 seek_radius(2);
382         glm::ivec2 begin(glm::max(glm::ivec2(0), loc - seek_radius));
383         glm::ivec2 end(glm::min(glm::ivec2(planet.SideLength()), loc + seek_radius + glm::ivec2(1)));
384
385         // this happens when location is way off the planet
386         // that's a bug in Situation, actually, but I'm working aound that here
387         if (end.x <= begin.x) {
388                 end.x = begin.x + 2;
389         }
390         if (end.y <= begin.y) {
391                 end.y = begin.y + 2;
392         }
393
394         double rating[end.y - begin.y][end.x - begin.x];
395         std::memset(rating, 0, sizeof(double) * (end.y - begin.y) * (end.x - begin.x));
396
397         // find close and rich field
398         for (int y = begin.y; y < end.y; ++y) {
399                 for (int x = begin.x; x < end.x; ++x) {
400                         const world::TileType &type = planet.TypeAt(srf, x, y);
401                         auto yield = type.FindBestResource(accept);
402                         if (yield != type.resources.cend()) {
403                                 // TODO: subtract minimum yield
404                                 rating[y - begin.y][x - begin.x] = yield->ubiquity * accept.Get(yield->resource);
405                                 double dist = std::max(0.125, 0.25 * glm::length(planet.TileCenter(srf, x, y) - pos));
406                                 rating[y - begin.y][x - begin.x] /= dist;
407                         }
408                 }
409         }
410
411         // demote crowded tiles
412         for (auto &c : planet.Creatures()) {
413                 if (&*c == &GetCreature()) continue;
414                 if (c->GetSituation().Surface() != srf) continue;
415                 glm::ivec2 coords(c->GetSituation().SurfacePosition());
416                 if (coords.x < begin.x || coords.x >= end.x) continue;
417                 if (coords.y < begin.y || coords.y >= end.y) continue;
418                 rating[coords.y - begin.y][coords.x - begin.x] *= 0.9;
419         }
420
421         glm::ivec2 best_pos(0);
422         double best_rating = -1.0;
423
424         for (int y = begin.y; y < end.y; ++y) {
425                 for (int x = begin.x; x < end.x; ++x) {
426                         if (rating[y - begin.y][x - begin.x] > best_rating) {
427                                 best_pos = glm::ivec2(x, y);
428                                 best_rating = rating[y - begin.y][x - begin.x];
429                         }
430                 }
431         }
432
433         if (best_rating) {
434                 found = true;
435                 searching = false;
436                 target_pos = planet.TileCenter(srf, best_pos.x, best_pos.y);
437                 target_srf = srf;
438                 target_tile = best_pos;
439                 GetSteering().GoTo(target_pos);
440         } else if (!searching) {
441                 found = false;
442                 searching = true;
443                 target_pos = GetSituation().Position();
444                 target_pos[(srf + 0) % 3] += Assets().random.SNorm();
445                 target_pos[(srf + 1) % 3] += Assets().random.SNorm();
446                 // bias towards current direction
447                 target_pos += glm::normalize(GetSituation().Velocity()) * 0.5;
448                 target_pos = clamp(target_pos, -planet.Radius(), planet.Radius());
449                 GetSteering().GoTo(target_pos);
450         }
451 }
452
453 bool LocateResourceGoal::OnTargetTile() const noexcept {
454         const Situation &s = GetSituation();
455         return s.OnSurface()
456                 && s.Surface() == target_srf
457                 && s.OnTile()
458                 && s.SurfacePosition() == target_tile;
459 }
460
461 }
462 }