for (auto a : planet.Creatures()) {
if (a != &c) {
state.GetCreaturePanel().Show(*a);
- a->OnDeath([&](creature::Creature &b) { (*this)(b); });
+ a->WhenDead([&](creature::Creature &b) { (*this)(b); });
break;
}
}
app::Application app(init.window, init.viewport);
SwitchPanel swp(planet, app, state);
- blob->OnDeath([&](creature::Creature &c) { swp(c); });
+ blob->WhenDead([&](creature::Creature &c) { swp(c); });
app.PushState(&state);
app.Run();
void Hurt(double d) noexcept;
void Die() noexcept;
bool Dead() const noexcept;
- void OnDeath(Callback cb) noexcept { on_death = cb; }
+ void WhenDead(Callback cb) noexcept { on_death = cb; }
void Remove() noexcept;
bool Removable() const noexcept { return removable; }
void Removed() noexcept;
namespace app {
struct Assets;
}
+namespace math {
+ class GaloisLFSR;
+}
namespace creature {
class Creature;
const Steering &GetSteering() const noexcept;
app::Assets &Assets() noexcept;
const app::Assets &Assets() const noexcept;
+ math::GaloisLFSR &Random() noexcept;
double Urgency() const noexcept { return urgency; }
void Urgency(double u) noexcept { urgency = u; }
void Interruptible(bool i) noexcept { interruptible = i; }
bool Complete() const noexcept { return complete; }
- void SetComplete() noexcept;
+ void SetComplete();
+ void SetForeground();
+ void SetBackground();
/// only supports one callback for now, new one will replace an old
- void OnComplete(Callback) noexcept;
+ void WhenComplete(Callback) noexcept;
+ void WhenForeground(Callback) noexcept;
+ /// on background will not be called when the goal is first inserted
+ /// but only after is has been in the foreground once
+ void WhenBackground(Callback) noexcept;
public:
virtual std::string Describe() const = 0;
virtual void Tick(double dt) { }
virtual void Action() { }
+private:
+ virtual void OnComplete() { }
+ virtual void OnForeground() { }
+ virtual void OnBackground() { }
+
private:
Creature &c;
Callback on_complete;
+ Callback on_foreground;
+ Callback on_background;
double urgency;
bool interruptible;
bool complete;
public:
std::string Describe() const override;
- void Enable() override;
- void Tick(double dt) override;
void Action() override;
+ void PickActivity();
+
};
}
void Move(const glm::dvec3 &dp) noexcept;
void Accelerate(const glm::dvec3 &dv) noexcept;
void EnforceConstraints(State &) noexcept;
+ void CheckWrap() noexcept;
void Heading(const glm::dvec3 &h) noexcept { state.dir = h; }
const glm::dvec3 &Heading() const noexcept { return state.dir; }
--- /dev/null
+#ifndef BLOBS_CREATURE_STROLLGOAL_HPP_
+#define BLOBS_CREATURE_STROLLGOAL_HPP_
+
+#include "Goal.hpp"
+
+#include "../math/glm.hpp"
+
+
+namespace blobs {
+namespace creature {
+
+class StrollGoal
+: public Goal {
+
+public:
+ explicit StrollGoal(Creature &);
+ ~StrollGoal() override;
+
+public:
+ std::string Describe() const override;
+ void Enable() override;
+ void Action() override;
+ void OnBackground() override;
+
+ void PickTarget() noexcept;
+
+private:
+ glm::dvec3 last;
+ glm::dvec3 next;
+
+};
+
+}
+}
+
+#endif
}
void Creature::DoWork(double amount) noexcept {
- stats.Exhaustion().Add(amount / Stamina());
+ stats.Exhaustion().Add(amount / (Stamina() + 1.0));
// burn resources proportional to composition
// factor = 1/total * 1/efficiency * amount * -1
double factor = -amount / (composition.TotalMass() * EnergyEfficiency());
double value = cmp.value * factor * sim.Resources()[cmp.resource].inverse_energy;
AddMass(cmp.resource, value);
}
+ // doing work improves strength a little
+ properties.Strength() += amount * 0.0001;
}
void Creature::Hurt(double amount) noexcept {
void Creature::AddGoal(std::unique_ptr<Goal> &&g) {
g->Enable();
+ if (goals.empty()) {
+ g->SetForeground();
+ }
goals.emplace_back(std::move(g));
}
for (auto &goal : goals) {
goal->Tick(dt);
}
+ Goal *top = &*goals.front();
// if active goal can be interrupted, check priorities
if (goals.size() > 1 && goals[0]->Interruptible()) {
std::sort(goals.begin(), goals.end(), GoalCompare);
}
+ if (&*goals.front() != top) {
+ top->SetBackground();
+ goals.front()->SetForeground();
+ top = &*goals.front();
+ }
goals[0]->Action();
for (auto goal = goals.begin(); goal != goals.end();) {
if ((*goal)->Complete()) {
++goal;
}
}
+ if (&*goals.front() != top) {
+ goals.front()->SetForeground();
+ }
}
math::AABB Creature::CollisionBox() const noexcept {
}
orient[0] = normalize(cross(orient[1], orient[2]));
orient[2] = normalize(cross(orient[0], orient[1]));
- return glm::translate(glm::dvec3(pos.x, pos.y, pos.z + half_size))
- * glm::dmat4(orient);
+ return glm::translate(glm::dvec3(pos.x, pos.y, pos.z))
+ * glm::dmat4(orient)
+ * glm::translate(glm::dvec3(0.0, half_size, 0.0));
}
glm::dmat4 Creature::LocalTransform() noexcept {
#include "IdleGoal.hpp"
#include "IngestGoal.hpp"
#include "LocateResourceGoal.hpp"
+#include "StrollGoal.hpp"
#include "Creature.hpp"
#include "../app/Assets.hpp"
drink_subtask->Accept(cmp.resource, 1.0);
}
}
- drink_subtask->OnComplete([&](Goal &) { drink_subtask = nullptr; });
+ drink_subtask->WhenComplete([&](Goal &) { drink_subtask = nullptr; });
GetCreature().AddGoal(std::unique_ptr<Goal>(drink_subtask));
}
eat_subtask->Accept(cmp.resource, 1.0);
}
}
- eat_subtask->OnComplete([&](Goal &) { eat_subtask = nullptr; });
+ eat_subtask->WhenComplete([&](Goal &) { eat_subtask = nullptr; });
GetCreature().AddGoal(std::unique_ptr<Goal>(eat_subtask));
}
void BlobBackgroundTask::CheckSplit() {
if (GetCreature().Mass() > GetCreature().OffspringMass() * 2.0
- && GetCreature().OffspringChance() > Assets().random.UNorm()) {
+ && GetCreature().OffspringChance() > Random().UNorm()) {
GetCreature().GetSimulation().Log() << GetCreature().Name() << " split" << std::endl;
Split(GetCreature());
return;
void BlobBackgroundTask::CheckMutate() {
// check for random property mutation
- if (GetCreature().MutateChance() > Assets().random.UNorm()) {
- double amount = 1.0 + (Assets().random.SNorm() * 0.05);
- math::Distribution &d = GetCreature().GetGenome().properties.props[Assets().random.UInt(9)];
- if (Assets().random.UNorm() < 0.5) {
+ if (GetCreature().MutateChance() > Random().UNorm()) {
+ double amount = 1.0 + (Random().SNorm() * 0.05);
+ math::Distribution &d = GetCreature().GetGenome().properties.props[Random().UInt(9)];
+ if (Random().UNorm() < 0.5) {
d.Mean(d.Mean() * amount);
} else {
d.StandardDeviation(d.StandardDeviation() * amount);
}
}
+
+Goal::Goal(Creature &c)
+: c(c)
+, on_complete()
+, on_foreground()
+, on_background()
+, urgency(0.0)
+, interruptible(true)
+, complete(false) {
+}
+
+Goal::~Goal() noexcept {
+}
+
+Situation &Goal::GetSituation() noexcept {
+ return c.GetSituation();
+}
+
+const Situation &Goal::GetSituation() const noexcept {
+ return c.GetSituation();
+}
+
+Steering &Goal::GetSteering() noexcept {
+ return c.GetSteering();
+}
+
+const Steering &Goal::GetSteering() const noexcept {
+ return c.GetSteering();
+}
+
+app::Assets &Goal::Assets() noexcept {
+ return c.GetSimulation().Assets();
+}
+
+const app::Assets &Goal::Assets() const noexcept {
+ return c.GetSimulation().Assets();
+}
+
+math::GaloisLFSR &Goal::Random() noexcept {
+ return Assets().random;
+}
+
+void Goal::SetComplete() {
+ if (!complete) {
+ complete = true;
+ OnComplete();
+ if (on_complete) {
+ on_complete(*this);
+ }
+ }
+}
+
+void Goal::SetForeground() {
+ OnForeground();
+ if (on_foreground) {
+ on_foreground(*this);
+ }
+}
+
+void Goal::SetBackground() {
+ OnBackground();
+ if (on_background) {
+ on_background(*this);
+ }
+}
+
+void Goal::WhenComplete(std::function<void(Goal &)> cb) noexcept {
+ on_complete = cb;
+ if (complete) {
+ on_complete(*this);
+ }
+}
+
+void Goal::WhenForeground(std::function<void(Goal &)> cb) noexcept {
+ on_foreground = cb;
+}
+
+void Goal::WhenBackground(std::function<void(Goal &)> cb) noexcept {
+ on_background = cb;
+}
+
+
+IdleGoal::IdleGoal(Creature &c)
+: Goal(c) {
+ Urgency(-1.0);
+ Interruptible(true);
+}
+
+IdleGoal::~IdleGoal() {
+}
+
+std::string IdleGoal::Describe() const {
+ return "idle";
+}
+
+void IdleGoal::Action() {
+ // use boredom as chance per minute
+ if (Random().UNorm() < GetCreature().GetStats().Boredom().value * (1.0 / 3600.0)) {
+ PickActivity();
+ }
+}
+
+void IdleGoal::PickActivity() {
+ GetCreature().AddGoal(std::unique_ptr<Goal>(new StrollGoal(GetCreature())));
+}
+
+
namespace {
std::string summarize(const Composition &comp, const app::Assets &assets) {
GetSteering().Halt();
} else {
// finally
+ // TODO: somehow this still gets interrupted
Interruptible(false);
ingesting = true;
}
locate_subtask->Accept(c.resource, c.value);
}
locate_subtask->Urgency(Urgency() + 0.1);
- locate_subtask->OnComplete([&](Goal &){ locate_subtask = nullptr; });
+ locate_subtask->WhenComplete([&](Goal &){ locate_subtask = nullptr; });
GetCreature().AddGoal(std::unique_ptr<Goal>(locate_subtask));
}
}
}
-Goal::Goal(Creature &c)
-: c(c)
-, on_complete()
-, urgency(0.0)
-, interruptible(true)
-, complete(false) {
-}
-
-Goal::~Goal() noexcept {
-}
-
-Situation &Goal::GetSituation() noexcept {
- return c.GetSituation();
-}
-
-const Situation &Goal::GetSituation() const noexcept {
- return c.GetSituation();
-}
-
-Steering &Goal::GetSteering() noexcept {
- return c.GetSteering();
-}
-
-const Steering &Goal::GetSteering() const noexcept {
- return c.GetSteering();
-}
-
-app::Assets &Goal::Assets() noexcept {
- return c.GetSimulation().Assets();
-}
-
-const app::Assets &Goal::Assets() const noexcept {
- return c.GetSimulation().Assets();
-}
-
-void Goal::SetComplete() noexcept {
- if (!complete) {
- complete = true;
- if (on_complete) {
- on_complete(*this);
- }
- }
-}
-
-void Goal::OnComplete(std::function<void(Goal &)> cb) noexcept {
- on_complete = cb;
- if (complete) {
- on_complete(*this);
- }
-}
-
-
-IdleGoal::IdleGoal(Creature &c)
-: Goal(c) {
- Urgency(-1.0);
- Interruptible(true);
-}
-
-IdleGoal::~IdleGoal() {
-}
-
-std::string IdleGoal::Describe() const {
- return "idle";
-}
-
-void IdleGoal::Enable() {
-}
-
-void IdleGoal::Tick(double dt) {
-}
-
-void IdleGoal::Action() {
-}
-
-
LocateResourceGoal::LocateResourceGoal(Creature &c)
: Goal(c)
, accept()
} else {
double dist = glm::length2(GetSituation().Position() - target_pos);
if (dist < 0.0001) {
+ searching = false;
LocateResource();
} else {
GetSteering().GoTo(target_pos);
}
}
- if (best_rating) {
+ if (best_rating > 0.0) {
found = true;
searching = false;
target_pos = normalize(pos + (double(best_pos.x) * step_x) + (double(best_pos.y) * step_y)) * planet.Radius();
found = false;
searching = true;
target_pos = GetSituation().Position();
- target_pos += Assets().random.SNorm() * step_x;
- target_pos += Assets().random.SNorm() * step_y;
+ target_pos += Random().SNorm() * step_x;
+ target_pos += Random().SNorm() * step_y;
// bias towards current heading
- target_pos += GetSituation().Heading() * 0.5;
+ target_pos += GetSituation().Heading() * 1.5;
target_pos = normalize(target_pos) * planet.Radius();
GetSteering().GoTo(target_pos);
}
return s.OnSurface() && length2(s.Position() - target_pos) < 0.5;
}
+
+StrollGoal::StrollGoal(Creature &c)
+: Goal(c)
+, last(GetSituation().Position())
+, next(last) {
+}
+
+StrollGoal::~StrollGoal() {
+}
+
+std::string StrollGoal::Describe() const {
+ return "take a walk";
+}
+
+void StrollGoal::Enable() {
+ last = GetSituation().Position();
+ GetSteering().Haste(0.0);
+ PickTarget();
+}
+
+void StrollGoal::Action() {
+ if (length2(next - GetSituation().Position()) < 0.0001) {
+ PickTarget();
+ }
+}
+
+void StrollGoal::OnBackground() {
+ SetComplete();
+}
+
+void StrollGoal::PickTarget() noexcept {
+ last = next;
+ next += GetSituation().Heading() * 1.5;
+ const glm::dvec3 normal(GetSituation().GetPlanet().NormalAt(GetSituation().Position()));
+ glm::dvec3 rand_x(GetSituation().Heading());
+ if (std::abs(dot(normal, rand_x)) > 0.999) {
+ rand_x = glm::dvec3(normal.z, normal.x, normal.y);
+ }
+ glm::dvec3 rand_y = cross(normal, rand_x);
+ next += ((rand_x * Random().SNorm()) + (rand_y * Random().SNorm())) * 1.5;
+ next = normalize(next) * GetSituation().GetPlanet().Radius();
+ GetSteering().GoTo(next);
+}
+
}
}
Label *age;
Label *mass;
Label *goal;
+ Label *pos;
+ Label *tile;
+ Label *head;
Panel *composition;
std::vector<Label *> components;
Meter *stats[7];
, age(new Label(assets.fonts.medium))
, mass(new Label(assets.fonts.medium))
, goal(new Label(assets.fonts.medium))
+, pos(new Label(assets.fonts.medium))
+, tile(new Label(assets.fonts.medium))
+, head(new Label(assets.fonts.medium))
, composition(new Panel)
, stats{nullptr}
, props{nullptr}
->Add(info_label_panel)
->Add(info_value_panel);
+ Label *pos_label = new Label(assets.fonts.medium);
+ pos_label->Text("Pos");
+ Label *tile_label = new Label(assets.fonts.medium);
+ tile_label->Text("Tile");
+ Label *head_label = new Label(assets.fonts.medium);
+ head_label->Text("Heading");
+
+ Panel *loc_label_panel = new Panel;
+ loc_label_panel
+ ->Direction(Panel::VERTICAL)
+ ->Add(pos_label)
+ ->Add(tile_label)
+ ->Add(head_label);
+ Panel *loc_value_panel = new Panel;
+ loc_value_panel
+ ->Direction(Panel::VERTICAL)
+ ->Add(pos)
+ ->Add(tile)
+ ->Add(head);
+ Panel *loc_panel = new Panel;
+ loc_panel
+ ->Direction(Panel::HORIZONTAL)
+ ->Spacing(10.0f)
+ ->Add(loc_label_panel)
+ ->Add(loc_value_panel);
+
Label *stat_label[7];
for (int i = 0; i < 7; ++i) {
stat_label[i] = new Label(assets.fonts.medium);
panel
.Add(name)
->Add(info_panel)
+ ->Add(loc_panel)
->Add(composition)
->Add(stat_panel)
->Add(prop_panel)
goal->Text(c->Goals()[0]->Describe());
}
+ pos->Text(VectorString(c->GetSituation().Position(), 2));
+ tile->Text(c->GetSituation().GetTileType().label);
+ head->Text(VectorString(c->GetSituation().Heading(), 2));
+
const creature::Composition &comp = c->GetComposition();
if (comp.size() < components.size()) {
composition->Clear();
glm::vec3 pos[5];
pos[0][(surface + 0) % 3] = x + 0 - offset;
pos[0][(surface + 1) % 3] = y + 0 - offset;
- pos[0][(surface + 2) % 3] = surface < 3 ? offset : -offset;
+ pos[0][(surface + 2) % 3] = offset;
pos[1][(surface + 0) % 3] = x + 0 - offset;
pos[1][(surface + 1) % 3] = y + 1 - offset;
- pos[1][(surface + 2) % 3] = surface < 3 ? offset : -offset;
+ pos[1][(surface + 2) % 3] = offset;
pos[2][(surface + 0) % 3] = x + 1 - offset;
pos[2][(surface + 1) % 3] = y + 0 - offset;
- pos[2][(surface + 2) % 3] = surface < 3 ? offset : -offset;
+ pos[2][(surface + 2) % 3] = offset;
pos[3][(surface + 0) % 3] = x + 1 - offset;
pos[3][(surface + 1) % 3] = y + 1 - offset;
- pos[3][(surface + 2) % 3] = surface < 3 ? offset : -offset;
+ pos[3][(surface + 2) % 3] = offset;
float tex = ts[TileAt(surface, x, y).type].texture;
const float tex_v_begin = surface < 3 ? 1.0f : 0.0f;
const float tex_v_end = surface < 3 ? 0.0f : 1.0f;
- attrib[4 * index + 0].position = normalize(pos[0]) * offset;
+ attrib[4 * index + 0].position = normalize(pos[0]) * (surface < 3 ? offset : -offset);
attrib[4 * index + 0].normal = pos[0];
attrib[4 * index + 0].tex_coord[0] = 0.0f;
attrib[4 * index + 0].tex_coord[1] = tex_v_begin;
attrib[4 * index + 0].tex_coord[2] = tex;
- attrib[4 * index + 1].position = normalize(pos[1]) * offset;
+ attrib[4 * index + 1].position = normalize(pos[1]) * (surface < 3 ? offset : -offset);
attrib[4 * index + 1].normal = pos[1];
attrib[4 * index + 1].tex_coord[0] = 0.0f;
attrib[4 * index + 1].tex_coord[1] = tex_v_end;
attrib[4 * index + 1].tex_coord[2] = tex;
- attrib[4 * index + 2].position = normalize(pos[2]) * offset;
+ attrib[4 * index + 2].position = normalize(pos[2]) * (surface < 3 ? offset : -offset);
attrib[4 * index + 2].normal = pos[2];
attrib[4 * index + 2].tex_coord[0] = 1.0f;
attrib[4 * index + 2].tex_coord[1] = tex_v_begin;
attrib[4 * index + 2].tex_coord[2] = tex;
- attrib[4 * index + 3].position = normalize(pos[3]) * offset;
+ attrib[4 * index + 3].position = normalize(pos[3]) * (surface < 3 ? offset : -offset);
attrib[4 * index + 3].normal = pos[3];
attrib[4 * index + 3].tex_coord[0] = 1.0f;
attrib[4 * index + 3].tex_coord[1] = tex_v_end;