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