1 #include "BlobBackgroundTask.hpp"
3 #include "IdleGoal.hpp"
4 #include "IngestGoal.hpp"
5 #include "LocateResourceGoal.hpp"
7 #include "Creature.hpp"
8 #include "../app/Assets.hpp"
9 #include "../ui/string.hpp"
10 #include "../world/Planet.hpp"
11 #include "../world/Resource.hpp"
12 #include "../world/Simulation.hpp"
13 #include "../world/TileType.hpp"
18 #include <glm/gtx/io.hpp>
24 BlobBackgroundTask::BlobBackgroundTask(Creature &c)
27 , drink_subtask(nullptr)
28 , eat_subtask(nullptr) {
31 BlobBackgroundTask::~BlobBackgroundTask() {
34 std::string BlobBackgroundTask::Describe() const {
35 return "being a blob";
38 void BlobBackgroundTask::Tick(double dt) {
40 // TODO: derive breathing ability
41 int gas = Assets().data.resources["air"].id;
42 // TODO: check if in compatible atmosphere
43 double amount = GetCreature().GetStats().Breath().gain * -(1.0 + GetCreature().ExhaustionFactor());
44 GetCreature().GetStats().Breath().Add(amount * dt);
45 // maintain ~1% gas composition
46 double gas_amount = GetCreature().GetComposition().Get(gas);
47 if (gas_amount < GetCreature().GetComposition().TotalMass() * 0.01) {
48 double add = std::min(GetCreature().GetComposition().TotalMass() * 0.025 - gas_amount, -amount * dt);
49 GetCreature().Ingest(gas, add);
51 if (GetCreature().GetStats().Breath().Empty()) {
57 void BlobBackgroundTask::Action() {
63 void BlobBackgroundTask::CheckStats() {
64 Creature::Stats &stats = GetCreature().GetStats();
66 if (!breathing && stats.Breath().Bad()) {
70 if (!drink_subtask && stats.Thirst().Bad()) {
71 drink_subtask = new IngestGoal(GetCreature(), stats.Thirst());
72 for (const auto &cmp : GetCreature().GetComposition()) {
73 if (Assets().data.resources[cmp.resource].state == world::Resource::LIQUID) {
74 drink_subtask->Accept(cmp.resource, 1.0);
77 drink_subtask->OnComplete([&](Goal &) { drink_subtask = nullptr; });
78 GetCreature().AddGoal(std::unique_ptr<Goal>(drink_subtask));
81 if (!eat_subtask && stats.Hunger().Bad()) {
82 eat_subtask = new IngestGoal(GetCreature(), stats.Hunger());
83 for (const auto &cmp : GetCreature().GetComposition()) {
84 if (Assets().data.resources[cmp.resource].state == world::Resource::SOLID) {
85 eat_subtask->Accept(cmp.resource, 1.0);
88 eat_subtask->OnComplete([&](Goal &) { eat_subtask = nullptr; });
89 GetCreature().AddGoal(std::unique_ptr<Goal>(eat_subtask));
92 // when in bad shape, don't make much effort
93 if (stats.Damage().Bad() || stats.Exhaustion().Bad() || stats.Fatigue().Critical()) {
94 GetCreature().GetSteering().DontSeparate();
96 GetCreature().GetSteering().ResumeSeparate();
100 void BlobBackgroundTask::CheckSplit() {
101 if (GetCreature().Mass() > GetCreature().OffspringMass() * 2.0
102 && GetCreature().OffspringChance() > Assets().random.UNorm()) {
103 GetCreature().GetSimulation().Log() << GetCreature().Name() << " split" << std::endl;
104 Split(GetCreature());
109 void BlobBackgroundTask::CheckMutate() {
110 // check for random property mutation
111 if (GetCreature().MutateChance() > Assets().random.UNorm()) {
112 double amount = 1.0 + (Assets().random.SNorm() * 0.05);
113 math::Distribution &d = GetCreature().GetGenome().properties.props[Assets().random.UInt(9)];
114 if (Assets().random.UNorm() < 0.5) {
115 d.Mean(d.Mean() * amount);
117 d.StandardDeviation(d.StandardDeviation() * amount);
124 std::string summarize(const Composition &comp, const app::Assets &assets) {
127 for (const auto &c : comp) {
133 s << assets.data.resources[c.resource].label;
140 IngestGoal::IngestGoal(Creature &c, Creature::Stat &stat)
144 , locate_subtask(nullptr)
151 IngestGoal::~IngestGoal() {
154 void IngestGoal::Accept(int resource, double value) {
155 accept.Add(resource, value);
158 std::string IngestGoal::Describe() const {
159 if (resource == -1) {
160 return "ingest " + summarize(accept, Assets());
162 const world::Resource &r = Assets().data.resources[resource];
163 if (r.state == world::Resource::SOLID) {
164 return "eat " + r.label;
166 return "drink " + r.label;
171 void IngestGoal::Enable() {
174 void IngestGoal::Tick(double dt) {
176 if (locate_subtask) {
177 locate_subtask->Urgency(Urgency() + 0.1);
180 if (OnSuitableTile() && !GetSituation().Moving()) {
181 // TODO: determine satisfaction factor
182 GetCreature().Ingest(resource, yield * dt);
183 stat.Add(-1.0 * yield * dt);
188 // left tile somehow, some idiot probably pushed us off
195 void IngestGoal::Action() {
200 if (OnSuitableTile()) {
201 if (GetSituation().Moving()) {
202 // break with maximum force
203 GetSteering().Haste(1.0);
204 GetSteering().Halt();
207 Interruptible(false);
211 locate_subtask = new LocateResourceGoal(GetCreature());
212 for (const auto &c : accept) {
213 locate_subtask->Accept(c.resource, c.value);
215 locate_subtask->Urgency(Urgency() + 0.1);
216 locate_subtask->OnComplete([&](Goal &){ locate_subtask = nullptr; });
217 GetCreature().AddGoal(std::unique_ptr<Goal>(locate_subtask));
221 bool IngestGoal::OnSuitableTile() {
222 if (!GetSituation().OnSurface()) {
225 const world::TileType &t = GetSituation().GetTileType();
226 auto found = t.FindBestResource(accept);
227 if (found != t.resources.end()) {
228 resource = found->resource;
229 yield = found->ubiquity;
238 Goal::Goal(Creature &c)
242 , interruptible(true)
246 Goal::~Goal() noexcept {
249 Situation &Goal::GetSituation() noexcept {
250 return c.GetSituation();
253 const Situation &Goal::GetSituation() const noexcept {
254 return c.GetSituation();
257 Steering &Goal::GetSteering() noexcept {
258 return c.GetSteering();
261 const Steering &Goal::GetSteering() const noexcept {
262 return c.GetSteering();
265 app::Assets &Goal::Assets() noexcept {
266 return c.GetSimulation().Assets();
269 const app::Assets &Goal::Assets() const noexcept {
270 return c.GetSimulation().Assets();
273 void Goal::SetComplete() noexcept {
282 void Goal::OnComplete(std::function<void(Goal &)> cb) noexcept {
290 IdleGoal::IdleGoal(Creature &c)
296 IdleGoal::~IdleGoal() {
299 std::string IdleGoal::Describe() const {
303 void IdleGoal::Enable() {
306 void IdleGoal::Tick(double dt) {
309 void IdleGoal::Action() {
313 LocateResourceGoal::LocateResourceGoal(Creature &c)
322 LocateResourceGoal::~LocateResourceGoal() noexcept {
325 void LocateResourceGoal::Accept(int resource, double value) {
326 accept.Add(resource, value);
329 std::string LocateResourceGoal::Describe() const {
330 return "locate " + summarize(accept, Assets());
333 void LocateResourceGoal::Enable() {
337 void LocateResourceGoal::Tick(double dt) {
341 void LocateResourceGoal::Action() {
342 if (reevaluate < 0.0) {
349 double dist = glm::length2(GetSituation().Position() - target_pos);
353 GetSteering().GoTo(target_pos);
356 } else if (NearTarget()) {
357 GetSteering().Halt();
358 if (!GetSituation().Moving()) {
362 GetSteering().GoTo(target_pos);
364 GetSteering().Haste(Urgency());
367 void LocateResourceGoal::LocateResource() {
368 if (GetSituation().OnSurface()) {
369 const world::TileType &t = GetSituation().GetTileType();
370 auto yield = t.FindBestResource(accept);
371 if (yield != t.resources.cend()) {
373 GetSteering().Halt();
376 target_pos = GetSituation().Position();
378 // go find somewhere else
388 void LocateResourceGoal::SearchVicinity() {
389 const world::Planet &planet = GetSituation().GetPlanet();
390 const glm::dvec3 &pos = GetSituation().Position();
391 const glm::dvec3 normal(planet.NormalAt(pos));
392 const glm::dvec3 step_x(normalize(cross(normal, glm::dvec3(normal.z, normal.x, normal.y))));
393 const glm::dvec3 step_y(normalize(cross(step_x, normal)));
395 constexpr int search_radius = 2;
396 double rating[2 * search_radius + 1][2 * search_radius + 1] = {0};
398 // find close and rich field
399 for (int y = -search_radius; y < search_radius + 1; ++y) {
400 for (int x = -search_radius; x < search_radius + 1; ++x) {
401 const glm::dvec3 tpos(pos + (double(x) * step_x) + (double(y) * step_y));
402 if (!GetCreature().PerceptionTest(tpos)) continue;
403 const world::TileType &type = planet.TileTypeAt(tpos);
404 auto yield = type.FindBestResource(accept);
405 if (yield != type.resources.cend()) {
406 // TODO: subtract minimum yield
407 rating[y + search_radius][x + search_radius] = yield->ubiquity * accept.Get(yield->resource);
409 double dist = std::max(0.125, 0.25 * glm::length(tpos - pos));
410 rating[y + search_radius][x + search_radius] /= dist;
416 for (auto &c : planet.Creatures()) {
417 if (&*c == &GetCreature()) continue;
418 for (int y = -search_radius; y < search_radius + 1; ++y) {
419 for (int x = -search_radius; x < search_radius + 1; ++x) {
420 const glm::dvec3 tpos(pos + (double(x) * step_x) + (double(y) * step_y));
421 if (length2(tpos - c->GetSituation().Position()) < 1.0) {
422 rating[y + search_radius][x + search_radius] *= 0.8;
428 glm::ivec2 best_pos(0);
429 double best_rating = -1.0;
431 for (int y = -search_radius; y < search_radius + 1; ++y) {
432 for (int x = -search_radius; x < search_radius + 1; ++x) {
433 if (rating[y + search_radius][x + search_radius] > best_rating) {
434 best_pos = glm::ivec2(x, y);
435 best_rating = rating[y + search_radius][x + search_radius];
443 target_pos = normalize(pos + (double(best_pos.x) * step_x) + (double(best_pos.y) * step_y)) * planet.Radius();
444 GetSteering().GoTo(target_pos);
445 } else if (!searching) {
448 target_pos = GetSituation().Position();
449 target_pos += Assets().random.SNorm() * step_x;
450 target_pos += Assets().random.SNorm() * step_y;
451 // bias towards current heading
452 target_pos += GetSituation().Heading() * 0.5;
453 target_pos = normalize(target_pos) * planet.Radius();
454 GetSteering().GoTo(target_pos);
458 bool LocateResourceGoal::NearTarget() const noexcept {
459 const Situation &s = GetSituation();
460 return s.OnSurface() && length2(s.Position() - target_pos) < 0.5;