]> git.localhorst.tv Git - blobs.git/commitdiff
overhaul need system
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 1 Dec 2017 23:38:26 +0000 (00:38 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 1 Dec 2017 23:38:26 +0000 (00:38 +0100)
24 files changed:
src/app/states.cpp
src/blobs.cpp
src/creature/BlobBackgroundTask.hpp
src/creature/Composition.hpp [new file with mode: 0644]
src/creature/Creature.hpp
src/creature/EatGoal.hpp [new file with mode: 0644]
src/creature/Genome.hpp
src/creature/IngestGoal.hpp [new file with mode: 0644]
src/creature/IngestNeed.hpp [deleted file]
src/creature/InhaleNeed.hpp [deleted file]
src/creature/LocateResourceGoal.hpp
src/creature/Need.hpp [deleted file]
src/creature/Situation.hpp
src/creature/Steering.hpp
src/creature/creature.cpp
src/creature/goal.cpp
src/creature/need.cpp [deleted file]
src/ui/CreaturePanel.hpp
src/ui/Label.hpp
src/ui/Panel.hpp
src/ui/ui.cpp
src/ui/widgets.cpp
src/world/TileType.hpp
src/world/world.cpp

index 8afd7ef6b35a9159099e5254e00ed5c9f5b46039..e45356b5a29f6779a878ca07a6d4c490d44c591f 100644 (file)
@@ -110,7 +110,7 @@ void MasterState::OnMouseWheel(const SDL_MouseWheelEvent &e) {
        constexpr double zoom_scale = -1.0;
        constexpr double zoom_base = 1.125;
        cam_orient.z = glm::clamp(cam_orient.z + double(e.x) * roll_scale, PI * -0.5, PI * 0.5);
-       cam_tgt_dist = std::max(1.0, cam_tgt_dist * std::pow(zoom_base, double(e.y) * zoom_scale));
+       cam_tgt_dist = std::max(cp.GetCreature().Size() * 2.0, cam_tgt_dist * std::pow(zoom_base, double(e.y) * zoom_scale));
 }
 
 void MasterState::OnRender(graphics::Viewport &viewport) {
index f628a1bfcb02f58b151aba54ba22974df6a66e75..85f992304eae8fa8cf120a415cc7fb519f59788a 100644 (file)
@@ -89,19 +89,11 @@ int main(int argc, char *argv[]) {
        world::GenerateTest(assets.data.tile_types, moon);
        world::GenerateTest(assets.data.tile_types, second_planet);
 
-       std::cout << "length of year: " << planet.OrbitalPeriod() << "s" << std::endl;
-       std::cout << "length of moon cycle: " << moon.OrbitalPeriod() << "s" << std::endl;
-       std::cout << "length of day: " << planet.RotationalPeriod() << "s" << std::endl;
-       std::cout << "days per year: " << (planet.OrbitalPeriod() / planet.RotationalPeriod()) << std::endl;
-       std::cout << "moon cycle in days: " << (moon.OrbitalPeriod() / planet.RotationalPeriod()) << std::endl;
-       std::cout << "moon cycles per year: " << (planet.OrbitalPeriod() / moon.OrbitalPeriod()) << std::endl;
-
        auto blob = new creature::Creature(sim);
        blob->Name(assets.name.Sequential());
        Spawn(*blob, planet);
        // decrease chances of ur-blob dying without splitting
-       blob->GetProperties().Youth().fertility = 1.0;
-       blob->GetProperties().Adult().fertility = 1.0;
+       blob->GetProperties().Fertility() = 1.0;
        blob->BuildVAO();
 
        app::MasterState state(assets, sim);
index 5cdae798814150c81455cac4be2578f4b4f55595..bbb5f96c2aa73746de664bd53d7cb3be7decd89d 100644 (file)
@@ -7,6 +7,8 @@
 namespace blobs {
 namespace creature {
 
+class IngestGoal;
+
 class BlobBackgroundTask
 : public Goal {
 
@@ -19,6 +21,16 @@ public:
        void Tick(double dt) override;
        void Action() override;
 
+private:
+       void CheckStats();
+       void CheckSplit();
+       void CheckMutate();
+
+private:
+       bool breathing;
+       IngestGoal *drink_subtask;
+       IngestGoal *eat_subtask;
+
 };
 
 }
diff --git a/src/creature/Composition.hpp b/src/creature/Composition.hpp
new file mode 100644 (file)
index 0000000..06b1c74
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef BLOBLS_CREATURE_COMPOSITION_HPP_
+#define BLOBLS_CREATURE_COMPOSITION_HPP_
+
+#include <vector>
+
+
+namespace blobs {
+namespace creature {
+
+class Composition {
+
+public:
+       struct Component {
+               int resource;
+               double value;
+               Component(int r, double v)
+               : resource(r), value(v) { }
+       };
+
+public:
+       Composition();
+       ~Composition();
+
+       Composition(const Composition &) = delete;
+       Composition &operator =(const Composition &) = delete;
+
+       Composition(Composition &&) = delete;
+       Composition &operator =(Composition &&) = delete;
+
+public:
+       void Add(int res, double amount);
+       bool Has(int res) const noexcept;
+       double Get(int res) const noexcept;
+
+public:
+       std::vector<Component>::iterator begin() noexcept { return components.begin(); }
+       std::vector<Component>::iterator end() noexcept { return components.end(); }
+       std::vector<Component>::const_iterator begin() const noexcept { return components.begin(); }
+       std::vector<Component>::const_iterator end() const noexcept { return components.end(); }
+       std::vector<Component>::const_iterator cbegin() noexcept { return components.cbegin(); }
+       std::vector<Component>::const_iterator cend() noexcept { return components.cend(); }
+
+private:
+       std::vector<Component> components;
+
+};
+
+}
+}
+
+#endif
index 3cff2e04cd5264aefab4510679d4abf80bca5f59..44107bea32dd62b3f390b1e1b0575cb39d232fdf 100644 (file)
@@ -1,10 +1,10 @@
 #ifndef BLOBS_CREATURE_CREATURE_HPP_
 #define BLOBS_CREATURE_CREATURE_HPP_
 
+#include "Composition.hpp"
 #include "Genome.hpp"
 #include "Goal.hpp"
 #include "Memory.hpp"
-#include "Need.hpp"
 #include "Situation.hpp"
 #include "Steering.hpp"
 #include "../graphics/SimpleVAO.hpp"
@@ -34,6 +34,40 @@ class Creature {
 public:
        using Callback = std::function<void(Creature &)>;
 
+       struct Stat {
+               // [0,1], zero being good, one bad
+               double value = 0.0;
+               // static gain per second
+               double gain = 0.0;
+               // adjust value by delta
+               void Add(double delta) noexcept {
+                       value = glm::clamp(value + delta, 0.0, 1.0);
+               }
+               bool Empty() const noexcept { return value < 0.000001; }
+               bool Good() const noexcept { return value < 0.25; }
+               bool Okay() const noexcept { return value < 0.5; }
+               bool Bad() const noexcept { return !Okay(); }
+               bool Critical() const noexcept { return value > 0.75; }
+               bool Full() const noexcept { return value > 0.999999; }
+       };
+       struct Stats {
+               Stat stat[7];
+               Stat &Damage() noexcept { return stat[0]; }
+               const Stat &Damage() const noexcept { return stat[0]; }
+               Stat &Breath() noexcept { return stat[1]; }
+               const Stat &Breath() const noexcept { return stat[1]; }
+               Stat &Thirst() noexcept { return stat[2]; }
+               const Stat &Thirst() const noexcept { return stat[2]; }
+               Stat &Hunger() noexcept { return stat[3]; }
+               const Stat &Hunger() const noexcept { return stat[3]; }
+               Stat &Exhaustion() noexcept { return stat[4]; }
+               const Stat &Exhaustion() const noexcept { return stat[4]; }
+               Stat &Fatigue() noexcept { return stat[5]; }
+               const Stat &Fatigue() const noexcept { return stat[5]; }
+               Stat &Boredom() noexcept { return stat[6]; }
+               const Stat &Boredom() const noexcept { return stat[6]; }
+       };
+
 public:
        explicit Creature(world::Simulation &);
        ~Creature();
@@ -57,39 +91,54 @@ public:
        Genome::Properties<double> &GetProperties() noexcept { return properties; }
        const Genome::Properties<double> &GetProperties() const noexcept { return properties; }
 
-       const Genome::PropertySet<double> &CurProps() const noexcept { return properties.props[cur_prop]; }
-       const Genome::PropertySet<double> &NextProps() const noexcept { return properties.props[std::min(5, cur_prop + 1)]; }
+       void AddMass(int res, double amount);
+       const Composition &GetComposition() const noexcept { return composition; }
 
        void BaseColor(const glm::dvec3 &c) noexcept { base_color = c; }
        const glm::dvec3 &BaseColor() const noexcept { return base_color; }
 
-       void HighlightColor(const glm::dvec3 &c) noexcept { highlight_color = c; }
-       glm::dvec4 HighlightColor() const noexcept;
+       void HighlightColor(const glm::dvec3 &c) noexcept;
+       glm::dvec4 HighlightColor() const noexcept { return highlight_color; }
 
-       void Mass(double m) noexcept { mass = m; size = std::cbrt(mass / density); }
+       void Mass(double m) noexcept { mass = m; }
        double Mass() const noexcept { return mass; }
        void Ingest(int res, double amount) noexcept;
 
-       void Density(double d) noexcept { density = d; size = std::cbrt(mass / density); }
-       double Density() const noexcept { return density; }
+       void Size(double s) noexcept { size = s; }
+       double Size() const noexcept { return size; }
 
-       double Size() const noexcept;
+       double Born() const noexcept { return birth; }
        double Age() const noexcept;
-       std::string AgeName() const;
-       double AgeLerp(double from, double to) const noexcept;
-       // chance of giving birth per tick
+       /// age-depended multiplier, peak being the maximum in lifetime [0,1]
+       double AgeFactor(double peak) const noexcept;
+
+       double ExhaustionFactor() const noexcept;
+       double FatigueFactor() const noexcept;
+
+       // stats with effects applied
+       double Strength() const noexcept;
+       double Stamina() const noexcept;
+       double Dexerty() const noexcept;
+       double Intelligence() const noexcept;
+       double Lifetime() const noexcept;
        double Fertility() const noexcept;
-       // chance of random genetic mutation per tick
        double Mutability() const noexcept;
+       double OffspringMass() const noexcept;
+
+       /// chance of giving birth per tick
+       double OffspringChance() const noexcept;
+       /// chance of random genetic mutation per tick
+       double MutateChance() const noexcept;
 
-       void Health(double h) noexcept { health = h; }
-       double Health() const noexcept { return health; }
        void Hurt(double d) noexcept;
        void Die() noexcept;
        void OnDeath(Callback cb) noexcept { on_death = cb; }
        void Remove() noexcept { removable = true; }
        bool Removable() const noexcept { return removable; }
 
+       Stats &GetStats() noexcept { return stats; }
+       const Stats &GetStats() const noexcept { return stats; }
+
        Memory &GetMemory() noexcept { return memory; }
        const Memory &GetMemory() const noexcept { return memory; }
 
@@ -97,9 +146,6 @@ public:
        void SetBackgroundTask(std::unique_ptr<Goal> &&g) { bg_task = std::move(g); }
        Goal &BackgroundTask() { return *bg_task; }
 
-       void AddNeed(std::unique_ptr<Need> &&n) { needs.emplace_back(std::move(n)); }
-       const std::vector<std::unique_ptr<Need>> &Needs() const noexcept { return needs; }
-
        void AddGoal(std::unique_ptr<Goal> &&);
        const std::vector<std::unique_ptr<Goal>> &Goals() const noexcept { return goals; }
 
@@ -117,6 +163,9 @@ public:
        void Draw(graphics::Viewport &);
 
 private:
+       void TickState(double dt);
+       void TickStats(double dt);
+       void TickBrain(double dt);
        Situation::Derivative Step(const Situation::Derivative &ds, double dt) const noexcept;
 
 private:
@@ -125,24 +174,22 @@ private:
 
        Genome genome;
        Genome::Properties<double> properties;
-       int cur_prop;
+       Composition composition;
 
        glm::dvec3 base_color;
-       glm::dvec3 highlight_color;
+       glm::dvec4 highlight_color;
 
        double mass;
-       double density;
        double size;
 
        double birth;
-       double health;
        Callback on_death;
        bool removable;
 
+       Stats stats;
        Memory memory;
 
        std::unique_ptr<Goal> bg_task;
-       std::vector<std::unique_ptr<Need>> needs;
        std::vector<std::unique_ptr<Goal>> goals;
 
        Situation situation;
diff --git a/src/creature/EatGoal.hpp b/src/creature/EatGoal.hpp
new file mode 100644 (file)
index 0000000..e64763b
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef BLOBS_CREATURE_EATGOAL_HPP_
+#define BLOBS_CREATURE_EATGOAL_HPP_
+
+#include "Goal.hpp"
+
+
+namespace blobs {
+namespace creature {
+
+class EatGoal
+: public Goal {
+
+public:
+       explicit EatGoal(Creature &);
+       ~EatGoal() override;
+
+public:
+       std::string Describe() const override;
+       void Enable() override;
+       void Tick(double dt) override;
+       void Action() override;
+
+private:
+       bool OnSuitableTile() const;
+
+private:
+       Goal *locate_subtask;
+       bool eating;
+
+};
+
+}
+}
+
+#endif
index 3361fdef2037d6f913a72ba12c098fe1ddc2e5de..75ca79c70188c439c4683b3a238e8271663d9196 100644 (file)
@@ -18,99 +18,57 @@ class Creature;
 
 struct Genome {
 
-       template<class T>
-       struct PropertySet {
-               /// the age at which to transition to the next phase
-               T age;
-               /// maximum body mass
-               T mass;
-               /// fertility factor
-               T fertility;
-               /// skin highlight pronounciation
-               T highlight;
-       };
        template<class T>
        struct Properties {
-               PropertySet<T> props[6];
-               PropertySet<T> &Birth() noexcept { return props[0]; }
-               const PropertySet<T> &Birth() const noexcept { return props[0]; }
-               PropertySet<T> &Child() noexcept { return props[1]; }
-               const PropertySet<T> &Child() const noexcept { return props[1]; }
-               PropertySet<T> &Youth() noexcept { return props[2]; }
-               const PropertySet<T> &Youth() const noexcept { return props[2]; }
-               PropertySet<T> &Adult() noexcept { return props[3]; }
-               const PropertySet<T> &Adult() const noexcept { return props[3]; }
-               PropertySet<T> &Elder() noexcept { return props[4]; }
-               const PropertySet<T> &Elder() const noexcept { return props[4]; }
-               PropertySet<T> &Death() noexcept { return props[5]; }
-               const PropertySet<T> &Death() const noexcept { return props[5]; }
-
-               /// "typical" properties
                /// every one of these should have at least one
                /// negative impact to prevent super-beings evolving
+               T props[8];
                /// power at the cost of higher solid intake
-               T strength;
+               T &Strength() noexcept { return props[0]; }
+               const T &Strength() const noexcept { return props[0]; }
                /// more endurance at the cost of higher liquid intake
-               T stamina;
+               T &Stamina() noexcept { return props[1]; }
+               const T &Stamina() const noexcept { return props[1]; }
                /// more speed at the cost of higher fatigue
-               T dexerty;
+               T &Dexerty() noexcept { return props[2]; }
+               const T &Dexerty() const noexcept { return props[2]; }
                /// higher mental capacity at the cost of boredom
-               T intelligence;
+               T &Intelligence() noexcept { return props[3]; }
+               const T &Intelligence() const noexcept { return props[3]; }
+               /// average lifetime in seconds
+               T &Lifetime() noexcept { return props[4]; }
+               const T &Lifetime() const noexcept { return props[4]; }
+               /// how likely to succeed in reproduction
+               T &Fertility() noexcept { return props[5]; }
+               const T &Fertility() const noexcept { return props[5]; }
                /// how likely to mutate
-               T mutability;
+               T &Mutability() noexcept { return props[6]; }
+               const T &Mutability() const noexcept { return props[6]; }
+               /// mass of offspring
+               T &OffspringMass() noexcept { return props[7]; }
+               const T &OffspringMass() const noexcept { return props[7]; }
        };
        Properties<math::Distribution> properties;
 
-
-       struct Composition {
-               // which resource
-               int resource;
-               // how much contained in the body
-               // relative value to determine average density
-               math::Distribution mass;
-               // how much to circulate
-               math::Distribution intake;
-               // how important for alive-being
-               math::Distribution penalty;
-               // how much off the mass may stay in the body
-               math::Distribution growth;
-       };
-       std::vector<Composition> composition;
-
        math::Distribution base_hue;
        math::Distribution base_saturation;
        math::Distribution base_lightness;
 
        void Configure(Creature &) const;
 
-       static PropertySet<double> Instantiate(
-               const PropertySet<math::Distribution> &p,
-               math::GaloisLFSR &rand
-       ) noexcept {
-               return {
-                       p.age.FakeNormal(rand.SNorm()),
-                       p.mass.FakeNormal(rand.SNorm()),
-                       p.fertility.FakeNormal(rand.SNorm()),
-                       glm::clamp(p.highlight.FakeNormal(rand.SNorm()), 0.0, 1.0)
-               };
-       }
-
        static Properties<double> Instantiate(
                const Properties<math::Distribution> &p,
                math::GaloisLFSR &rand
        ) noexcept {
                return {
-                       Instantiate(p.props[0], rand),
-                       Instantiate(p.props[1], rand),
-                       Instantiate(p.props[2], rand),
-                       Instantiate(p.props[3], rand),
-                       Instantiate(p.props[4], rand),
-                       Instantiate(p.props[5], rand),
-                       p.strength.FakeNormal(rand.SNorm()),
-                       p.stamina.FakeNormal(rand.SNorm()),
-                       p.dexerty.FakeNormal(rand.SNorm()),
-                       p.intelligence.FakeNormal(rand.SNorm()),
-                       p.mutability.FakeNormal(rand.SNorm())
+                       p.props[0].FakeNormal(rand.SNorm()),
+                       p.props[1].FakeNormal(rand.SNorm()),
+                       p.props[2].FakeNormal(rand.SNorm()),
+                       p.props[3].FakeNormal(rand.SNorm()),
+                       p.props[4].FakeNormal(rand.SNorm()),
+                       p.props[5].FakeNormal(rand.SNorm()),
+                       p.props[6].FakeNormal(rand.SNorm()),
+                       p.props[7].FakeNormal(rand.SNorm())
                };
        }
 
diff --git a/src/creature/IngestGoal.hpp b/src/creature/IngestGoal.hpp
new file mode 100644 (file)
index 0000000..fd5515b
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef BLOBS_CREATURE_INGESTGOAL_HPP_
+#define BLOBS_CREATURE_INGESTGOAL_HPP_
+
+#include "Goal.hpp"
+
+#include "Composition.hpp"
+#include "Creature.hpp"
+
+
+namespace blobs {
+namespace creature {
+
+class LocateResourceGoal;
+
+class IngestGoal
+: public Goal {
+
+public:
+       explicit IngestGoal(Creature &, Creature::Stat &);
+       ~IngestGoal() override;
+
+public:
+       void Accept(int resource, double value);
+
+       std::string Describe() const override;
+       void Enable() override;
+       void Tick(double dt) override;
+       void Action() override;
+
+private:
+       bool OnSuitableTile();
+
+private:
+       Creature::Stat &stat;
+       Composition accept;
+       LocateResourceGoal *locate_subtask;
+       bool ingesting;
+
+       int resource;
+       double yield;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/creature/IngestNeed.hpp b/src/creature/IngestNeed.hpp
deleted file mode 100644 (file)
index b0262b8..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#ifndef BLOBS_CREATURE_INGESTNEED_HPP_
-#define BLOBS_CREATURE_INGESTNEED_HPP_
-
-#include "Need.hpp"
-
-
-namespace blobs {
-namespace creature {
-
-class Goal;
-
-class IngestNeed
-: public Need {
-
-public:
-       IngestNeed(int resource, double speed, double damage);
-       ~IngestNeed() override;
-
-public:
-       void ApplyEffect(Creature &, double dt) override;
-
-private:
-       void OnLocateComplete(Goal &);
-
-private:
-       Goal *locate_goal;
-       int resource;
-       double speed;
-       double damage;
-       bool ingesting;
-
-};
-
-}
-}
-
-#endif
diff --git a/src/creature/InhaleNeed.hpp b/src/creature/InhaleNeed.hpp
deleted file mode 100644 (file)
index 3907a8b..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef BLOBS_CREATURE_INHALENEED_HPP_
-#define BLOBS_CREATURE_INHALENEED_HPP_
-
-#include "Need.hpp"
-
-
-namespace blobs {
-namespace creature {
-
-class Need;
-
-class InhaleNeed
-: public Need {
-
-public:
-       InhaleNeed(int resource, double speed, double damage);
-       ~InhaleNeed() override;
-
-public:
-       void ApplyEffect(Creature &, double dt) override;
-
-private:
-       int resource;
-       double speed;
-       double damage;
-       bool inhaling;
-
-};
-
-}
-}
-
-#endif
index c69e3e687af3cdee4ade75e01586b0ffb60ad792..f0937c93c3e00d4f92109864b806438565226e97 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef BLOBS_CREATURE_LOCATERESOURCEGOAL_HPP_
 #define BLOBS_CREATURE_LOCATERESOURCEGOAL_HPP_
 
+#include "Composition.hpp"
 #include "../math/glm.hpp"
 
 
@@ -11,10 +12,12 @@ class LocateResourceGoal
 : public Goal {
 
 public:
-       LocateResourceGoal(Creature &, int resource);
+       LocateResourceGoal(Creature &);
        ~LocateResourceGoal() noexcept override;
 
 public:
+       void Accept(int resource, double attractiveness);
+
        std::string Describe() const override;
        void Enable() override;
        void Tick(double dt) override;
@@ -26,7 +29,7 @@ private:
        bool OnTargetTile() const noexcept;
 
 private:
-       int res;
+       Composition accept;
        bool found;
        glm::dvec3 target_pos;
        int target_srf;
diff --git a/src/creature/Need.hpp b/src/creature/Need.hpp
deleted file mode 100644 (file)
index 295b00f..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef BLOBS_CREATURE_NEED_HPP_
-#define BLOBS_CREATURE_NEED_HPP_
-
-#include <string>
-
-
-namespace blobs {
-namespace creature {
-
-class Creature;
-class Effect;
-
-struct Need {
-
-       std::string name;
-
-       double value = 0.0;
-
-       // how fast value grows per second
-       double gain = 0.0;
-       // the value at which this need is no longer satisfied
-       double inconvenient = 0.0;
-       // the value at which this need starts to hurt
-       double critical = 0.0;
-       // factor of the intake that may stay in the body
-       double growth = 0.0;
-
-       virtual ~Need() noexcept;
-
-       void Tick(double dt) noexcept;
-       void Increase(double) noexcept;
-       void Decrease(double) noexcept;
-
-       bool IsSatisfied() const noexcept { return value < inconvenient; }
-       bool IsInconvenient() const noexcept { return value >= inconvenient && value < critical; }
-       bool IsCritical() const noexcept { return value >= critical; }
-
-       virtual void ApplyEffect(Creature &, double dt) = 0;
-
-};
-
-}
-}
-
-#endif
index c94d2d5c615bdb60a40349160899d87a0e494397..4205cff449c8e0f1d57cdaedd39c56662c62ddd2 100644 (file)
@@ -64,7 +64,7 @@ public:
        const State &GetState() const noexcept { return state; }
 
        const glm::dvec3 &Velocity() const noexcept { return state.vel; }
-       bool Moving() const noexcept { return glm::length2(state.vel) < 0.00000001; }
+       bool Moving() const noexcept { return glm::length2(state.vel) > 0.0000001; }
        void Move(const glm::dvec3 &dp) noexcept;
 
        void Heading(const glm::dvec3 &h) noexcept { state.dir = h; }
index d8f2cebfb7ba91fe1e35d736ea1261746d76448d..c9c2332f9a212e0faed9b5520a328d9d364f371d 100644 (file)
@@ -21,8 +21,8 @@ public:
        void Haste(double h) noexcept { haste = h; }
        double Haste() const noexcept { return haste; }
 
-       void MaxAcceleration(double a) noexcept { max_accel = a; }
-       double MaxAcceleration() const noexcept { return max_accel; }
+       void MaxForce(double f) noexcept { max_force = f; }
+       double MaxForce() const noexcept { return max_force; }
 
        void MaxSpeed(double s) noexcept { max_speed = s; }
        double MaxSpeed() const noexcept { return max_speed; }
@@ -34,7 +34,7 @@ public:
        void Pass(const glm::dvec3 &) noexcept;
        void GoTo(const glm::dvec3 &) noexcept;
 
-       glm::dvec3 Acceleration(const Situation::State &) const noexcept;
+       glm::dvec3 Force(const Situation::State &) const noexcept;
 
 private:
        bool SumForce(glm::dvec3 &out, const glm::dvec3 &in, double max) const noexcept;
@@ -45,7 +45,7 @@ private:
        glm::dvec3 target;
 
        double haste;
-       double max_accel;
+       double max_force;
        double max_speed;
        double min_dist;
        double max_look;
index 12924b94b9239651e212392868958d0d041f3915..d0000107b683e5b454d1864ba65c9c4c6dfe3873 100644 (file)
@@ -1,3 +1,4 @@
+#include "Composition.hpp"
 #include "Creature.hpp"
 #include "Genome.hpp"
 #include "Memory.hpp"
@@ -8,9 +9,6 @@
 #include "BlobBackgroundTask.hpp"
 #include "Goal.hpp"
 #include "IdleGoal.hpp"
-#include "InhaleNeed.hpp"
-#include "IngestNeed.hpp"
-#include "Need.hpp"
 #include "../app/Assets.hpp"
 #include "../math/const.hpp"
 #include "../world/Body.hpp"
 namespace blobs {
 namespace creature {
 
+Composition::Composition()
+: components() {
+}
+
+Composition::~Composition() {
+}
+
+namespace {
+bool CompositionCompare(const Composition::Component &a, const Composition::Component &b) {
+       return b.value < a.value;
+}
+}
+
+void Composition::Add(int res, double amount) {
+       bool found = false;
+       for (auto &c : components) {
+               if (c.resource == res) {
+                       c.value += amount;
+                       found = true;
+                       break;
+               }
+       }
+       if (!found) {
+               components.emplace_back(res, amount);
+       }
+       std::sort(components.begin(), components.end(), CompositionCompare);
+}
+
+bool Composition::Has(int res) const noexcept {
+       for (auto &c : components) {
+               if (c.resource == res) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+double Composition::Get(int res) const noexcept {
+       for (auto &c : components) {
+               if (c.resource == res) {
+                       return c.value;
+               }
+       }
+       return 0.0;
+}
+
+
 Creature::Creature(world::Simulation &sim)
 : sim(sim)
 , name()
 , genome()
 , properties()
-, cur_prop(0)
+, composition()
 , base_color(1.0)
-, highlight_color(0.0)
+, highlight_color(0.0, 0.0, 0.0, 1.0)
 , mass(1.0)
-, density(1.0)
 , size(1.0)
 , birth(sim.Time())
-, health(1.0)
 , on_death()
 , removable(false)
+, stats()
 , memory(*this)
 , bg_task()
-, needs()
 , goals()
 , situation()
 , steering(*this)
@@ -59,37 +102,52 @@ Creature::Creature(world::Simulation &sim)
 Creature::~Creature() {
 }
 
-glm::dvec4 Creature::HighlightColor() const noexcept {
-       return glm::dvec4(highlight_color, AgeLerp(CurProps().highlight, NextProps().highlight));
-}
-
-void Creature::Ingest(int res, double amount) noexcept {
-       const Genome::Composition *cmp = nullptr;
-       for (const auto &c : genome.composition) {
-               if (c.resource == res) {
-                       cmp = &c;
-                       break;
+void Creature::AddMass(int res, double amount) {
+       composition.Add(res, amount);
+       double mass = 0.0;
+       double nonsolid = 0.0;
+       double volume = 0.0;
+       for (const auto &c : composition) {
+               mass += c.value;
+               volume += c.value / sim.Assets().data.resources[c.resource].density;
+               if (sim.Assets().data.resources[c.resource].state != world::Resource::SOLID) {
+                       nonsolid += c.value;
                }
        }
-       if (cmp) {
-               const double max_mass = AgeLerp(CurProps().mass, NextProps().mass);
-               Mass(std::min(max_mass, mass + amount));
-       } else {
-               // foreign material. poisonous?
-       }
+       Mass(mass);
+       Size(std::cbrt(volume));
+       highlight_color.a = nonsolid / mass;
 }
 
-void Creature::Hurt(double dt) noexcept {
-       health = std::max(0.0, health - dt);
-       if (health == 0.0) {
-               std::cout << "[" << int(sim.Time()) << "s] "
-                       << name << " died" << std::endl;
+void Creature::HighlightColor(const glm::dvec3 &c) noexcept {
+       highlight_color = glm::dvec4(c, highlight_color.a);
+}
+
+void Creature::Ingest(int res, double amount) noexcept {
+       // TODO: check foreign materials
+       // 10% stays in body
+       AddMass(res, amount * 0.1);
+}
+
+void Creature::Hurt(double amount) noexcept {
+       stats.Damage().Add(amount);
+       if (stats.Damage().Full()) {
+               std::cout << "[" << int(sim.Time()) << "s] " << name << " ";
+               if (stats.Breath().Full()) {
+                       std::cout << "suffocated";
+               } else if (stats.Thirst().Full()) {
+                       std::cout << "died of thirst";
+               } else if (stats.Hunger().Full()) {
+                       std::cout << "starved to death";
+               } else {
+                       std::cout << "succumed to wounds";
+               }
+               std::cout << std::endl;
                Die();
        }
 }
 
 void Creature::Die() noexcept {
-       needs.clear();
        goals.clear();
        steering.Halt();
        if (on_death) {
@@ -98,43 +156,64 @@ void Creature::Die() noexcept {
        Remove();
 }
 
-double Creature::Size() const noexcept {
-       return size;
-}
-
 double Creature::Age() const noexcept {
        return sim.Time() - birth;
 }
 
-std::string Creature::AgeName() const {
-       switch (cur_prop) {
-               case 0:
-                       return "Newborn";
-               case 1:
-                       return "Child";
-               case 2:
-                       return "Youth";
-               case 3:
-                       return "Adult";
-               case 4:
-                       return "Elder";
-               case 5:
-                       return "Dead";
-               default:
-                       return "Unknown";
-       }
+double Creature::AgeFactor(double peak) const noexcept {
+       // shifted inverse hermite, y = 1 - (3t² - 2t³) with t = normalized age - peak
+       // goes negative below -0.5 and starts to rise again above 1.0
+       double t = glm::clamp((Age() / properties.Lifetime()) - peak, -0.5, 1.0);
+       return 1.0 - (3.0 * t * t) + (2.0 * t * t * t);
+}
+
+double Creature::ExhaustionFactor() const noexcept {
+       return 1.0 - (glm::smoothstep(0.5, 1.0, stats.Exhaustion().value) * 0.5);
+}
+
+double Creature::FatigueFactor() const noexcept {
+       return 1.0 - (glm::smoothstep(0.5, 1.0, stats.Fatigue().value) * 0.5);
+}
+
+double Creature::Strength() const noexcept {
+       // TODO: replace all age factors with actual growth and decay
+       return properties.Strength() * ExhaustionFactor() * AgeFactor(0.25);
 }
 
-double Creature::AgeLerp(double from, double to) const noexcept {
-       return glm::mix(from, to, glm::smoothstep(CurProps().age, NextProps().age, Age()));
+double Creature::Stamina() const noexcept {
+       return properties.Stamina() * ExhaustionFactor() * AgeFactor(0.25);
+}
+
+double Creature::Dexerty() const noexcept {
+       return properties.Dexerty() * ExhaustionFactor() * AgeFactor(0.25);
+}
+
+double Creature::Intelligence() const noexcept {
+       return properties.Intelligence() * FatigueFactor() * AgeFactor(0.25);
+}
+
+double Creature::Lifetime() const noexcept {
+       return properties.Lifetime();
 }
 
 double Creature::Fertility() const noexcept {
-       return AgeLerp(CurProps().fertility, NextProps().fertility) * (1.0 / 3600.0);
+       return properties.Fertility() * AgeFactor(0.25);
 }
 
 double Creature::Mutability() const noexcept {
-       return GetProperties().mutability * (1.0 / 3600.0);
+       return properties.Mutability();
+}
+
+double Creature::OffspringMass() const noexcept {
+       return properties.OffspringMass();
+}
+
+double Creature::OffspringChance() const noexcept {
+       return AgeFactor(0.25) * properties.Fertility() * (1.0 / 3600.0);
+}
+
+double Creature::MutateChance() const noexcept {
+       return GetProperties().Mutability() * (1.0 / 3600.0);
 }
 
 void Creature::AddGoal(std::unique_ptr<Goal> &&g) {
@@ -151,62 +230,82 @@ bool GoalCompare(const std::unique_ptr<Goal> &a, const std::unique_ptr<Goal> &b)
 }
 
 void Creature::Tick(double dt) {
-       if (cur_prop < 5 && Age() > NextProps().age) {
-               ++cur_prop;
-               if (cur_prop == 5) {
-                       std::cout << "[" << int(sim.Time()) << "s] "
-                               << name << " died of old age" << std::endl;
-                       Die();
+       TickState(dt);
+       TickStats(dt);
+       TickBrain(dt);
+}
+
+void Creature::TickState(double dt) {
+       steering.MaxSpeed(Dexerty());
+       steering.MaxForce(Strength());
+       Situation::State state(situation.GetState());
+       Situation::Derivative a(Step(Situation::Derivative(), 0.0));
+       Situation::Derivative b(Step(a, dt * 0.5));
+       Situation::Derivative c(Step(b, dt * 0.5));
+       Situation::Derivative d(Step(c, dt));
+       Situation::Derivative f(
+               (1.0 / 6.0) * (a.vel + 2.0 * (b.vel + c.vel) + d.vel),
+               (1.0 / 6.0) * (a.acc + 2.0 * (b.acc + c.acc) + d.acc)
+       );
+       state.pos += f.vel * dt;
+       state.vel += f.acc * dt;
+       if (length2(state.vel) > 0.000001) {
+               glm::dvec3 nvel(normalize(state.vel));
+               double ang = angle(nvel, state.dir);
+               double turn_rate = PI * 0.5 * dt;
+               if (ang < turn_rate) {
+                       state.dir = normalize(state.vel);
+               } else if (std::abs(ang - PI) < 0.001) {
+                       state.dir = rotate(state.dir, turn_rate, world::Planet::SurfaceNormal(situation.Surface()));
                } else {
-                       std::cout << "[" << int(sim.Time()) << "s] "
-                               << name << " grew up to " << AgeName() << std::endl;
+                       state.dir = rotate(state.dir, turn_rate, normalize(cross(state.dir, nvel)));
                }
        }
+       situation.SetState(state);
+       stats.Exhaustion().Add(length(f.acc) * Mass() / Stamina() * dt);
+}
 
-       {
-               Situation::State state(situation.GetState());
-               Situation::Derivative a(Step(Situation::Derivative(), 0.0));
-               Situation::Derivative b(Step(a, dt * 0.5));
-               Situation::Derivative c(Step(b, dt * 0.5));
-               Situation::Derivative d(Step(c, dt));
-               Situation::Derivative f(
-                       (1.0 / 6.0) * (a.vel + 2.0 * (b.vel + c.vel) + d.vel),
-                       (1.0 / 6.0) * (a.acc + 2.0 * (b.acc + c.acc) + d.acc)
-               );
-               state.pos += f.vel * dt;
-               state.vel += f.acc * dt;
-               if (length2(state.vel) > 0.000001) {
-                       glm::dvec3 nvel(normalize(state.vel));
-                       double ang = angle(nvel, state.dir);
-                       double turn_rate = PI * dt;
-                       if (ang < turn_rate) {
-                               state.dir = normalize(state.vel);
-                       } else if (std::abs(ang - PI) < 0.001) {
-                               state.dir = rotate(state.dir, turn_rate, world::Planet::SurfaceNormal(situation.Surface()));
-                       } else {
-                               state.dir = rotate(state.dir, turn_rate, normalize(cross(state.dir, nvel)));
-                       }
-               }
+Situation::Derivative Creature::Step(const Situation::Derivative &ds, double dt) const noexcept {
+       Situation::State s = situation.GetState();
+       s.pos += ds.vel * dt;
+       s.vel += ds.acc * dt;
+       return {
+               s.vel,
+               steering.Force(s) / Mass()
+       };
+}
 
-               situation.SetState(state);
+void Creature::TickStats(double dt) {
+       for (auto &s : stats.stat) {
+               s.Add(s.gain * dt);
+       }
+       stats.Breath().Add(stats.Breath().gain * stats.Exhaustion().value * dt);
+       // TODO: damage values depending on properties
+       if (stats.Breath().Full()) {
+               constexpr double dps = 1.0 / 4.0;
+               Hurt(dps * dt);
+       }
+       if (stats.Thirst().Full()) {
+               constexpr double dps = 1.0 / 32.0;
+               Hurt(dps * dt);
        }
+       if (stats.Hunger().Full()) {
+               constexpr double dps = 1.0 / 128.0;
+               Hurt(dps * dt);
+       }
+}
 
+void Creature::TickBrain(double dt) {
        bg_task->Tick(dt);
        bg_task->Action();
        memory.Tick(dt);
-       for (auto &need : needs) {
-               need->Tick(dt);
-       }
-       for (auto &goal : goals) {
-               goal->Tick(dt);
-       }
        // do background stuff
-       for (auto &need : needs) {
-               need->ApplyEffect(*this, dt);
-       }
        if (goals.empty()) {
                return;
        }
+       for (auto &goal : goals) {
+               goal->Tick(dt);
+       }
        // if active goal can be interrupted, check priorities
        if (goals.size() > 1 && goals[0]->Interruptible()) {
                std::sort(goals.begin(), goals.end(), GoalCompare);
@@ -221,16 +320,6 @@ void Creature::Tick(double dt) {
        }
 }
 
-Situation::Derivative Creature::Step(const Situation::Derivative &ds, double dt) const noexcept {
-       Situation::State s = situation.GetState();
-       s.pos += ds.vel * dt;
-       s.vel += ds.acc * dt;
-       return {
-               s.vel,
-               steering.Acceleration(s)
-       };
-}
-
 glm::dmat4 Creature::LocalTransform() noexcept {
        const double half_size = size * 0.5;
        const glm::dvec3 &pos = situation.Position();
@@ -361,76 +450,30 @@ void Spawn(Creature &c, world::Planet &p) {
        }
 
        Genome genome;
-
-       genome.properties.Birth().age = { 0.0, 0.0 };
-       genome.properties.Birth().mass = { 0.5, 0.05 };
-       genome.properties.Birth().fertility = { 0.0, 0.0 };
-       genome.properties.Birth().highlight = { 0.0, 0.0 };
-
-       genome.properties.Child().age = { 30.0, 1.0 };
-       genome.properties.Child().mass = { 0.7, 0.05 };
-       genome.properties.Child().fertility = { 0.0, 0.0 };
-       genome.properties.Child().highlight = { 0.2, 0.05 };
-
-       genome.properties.Youth().age = { 60.0, 5.0 };
-       genome.properties.Youth().mass = { 0.9, 0.1 };
-       genome.properties.Youth().fertility = { 0.5, 0.03 };
-       genome.properties.Youth().highlight = { 0.9, 0.1 };
-
-       genome.properties.Adult().age = { 120.0, 10.0 };
-       genome.properties.Adult().mass = { 1.3, 0.1 };
-       genome.properties.Adult().fertility = { 0.4, 0.01 };
-       genome.properties.Adult().highlight = { 0.7, 0.1 };
-
-       genome.properties.Elder().age = { 360.0, 30.0 };
-       genome.properties.Elder().mass = { 1.0, 0.05 };
-       genome.properties.Elder().fertility = { 0.1, 0.01 };
-       genome.properties.Elder().highlight = { 0.6, 0.1 };
-
-       genome.properties.Death().age = { 480.0, 60.0 };
-       genome.properties.Death().mass = { 0.9, 0.05 };
-       genome.properties.Death().fertility = { 0.0, 0.0 };
-       genome.properties.Death().highlight = { 0.5, 0.1 };
-
-       genome.properties.strength = { 1.0, 0.1 };
-       genome.properties.stamina = { 1.0, 0.1 };
-       genome.properties.dexerty = { 1.0, 0.1 };
-       genome.properties.intelligence = { 1.0, 0.1 };
-       genome.properties.mutability = { 1.0, 0.1 };
+       genome.properties.Strength() = { 2.0, 0.1 };
+       genome.properties.Stamina() = { 4.0, 0.1 };
+       genome.properties.Dexerty() = { 2.0, 0.1 };
+       genome.properties.Intelligence() = { 1.0, 0.1 };
+       genome.properties.Lifetime() = { 480.0, 60.0 };
+       genome.properties.Fertility() = { 0.5, 0.03 };
+       genome.properties.Mutability() = { 1.0, 0.1 };
+       genome.properties.OffspringMass() = { 0.3, 0.02 };
 
        glm::dvec3 color_avg(0.0);
        double color_divisor = 0.0;
 
        if (p.HasAtmosphere()) {
-               genome.composition.push_back({
-                       p.Atmosphere(),    // resource
-                       { 0.01, 0.00001 }, // mass
-                       { 0.5,  0.001 },   // intake
-                       { 0.1,  0.0005 },  // penalty
-                       { 0.0,  0.0 },     // growth
-               });
+               c.AddMass(p.Atmosphere(), 0.01);
                color_avg += c.GetSimulation().Resources()[p.Atmosphere()].base_color * 0.1;
                color_divisor += 0.1;
        }
        if (liquid > -1) {
-               genome.composition.push_back({
-                       liquid,          // resource
-                       { 0.6,  0.01 },  // mass
-                       { 0.2,  0.001 }, // intake
-                       { 0.01, 0.002 }, // penalty
-                       { 0.1, 0.0 },   // growth
-               });
+               c.AddMass(liquid, 0.3);
                color_avg += c.GetSimulation().Resources()[liquid].base_color * 0.5;
                color_divisor += 0.5;
        }
        if (solid > -1) {
-               genome.composition.push_back({
-                       solid,             // resource
-                       { 0.4,   0.01 },   // mass
-                       { 0.4,   0.001 },  // intake
-                       { 0.001, 0.0001 }, // penalty
-                       { 10.0,  0.002 },   // growth
-               });
+               c.AddMass(solid, 0.1);
                color_avg += c.GetSimulation().Resources()[solid].base_color;
                color_divisor += 1.0;
        }
@@ -453,37 +496,14 @@ void Genome::Configure(Creature &c) const {
 
        c.GetProperties() = Instantiate(properties, random);
 
-       double mass = 0.0;
-       double volume = 0.0;
-       for (const auto &comp : composition) {
-               const world::Resource &resource = c.GetSimulation().Resources()[comp.resource];
-               double comp_mass = comp.mass.FakeNormal(random.SNorm());
-               double intake = comp.intake.FakeNormal(random.SNorm());
-               double penalty = comp.penalty.FakeNormal(random.SNorm());
-
-               mass += comp_mass;
-               volume += comp_mass / c.GetSimulation().Resources()[comp.resource].density;
-
-               std::unique_ptr<Need> need;
-               if (resource.state == world::Resource::SOLID) {
-                       intake *= std::atan(c.GetProperties().strength);
-                       need.reset(new IngestNeed(comp.resource, intake, penalty));
-                       need->gain = intake * 0.05;
-               } else if (resource.state == world::Resource::LIQUID) {
-                       intake *= std::atan(c.GetProperties().stamina);
-                       need.reset(new IngestNeed(comp.resource, intake, penalty));
-                       need->gain = intake * 0.1;
-               } else {
-                       need.reset(new InhaleNeed(comp.resource, intake, penalty));
-                       need->gain = intake * 0.5;
-               }
-               need->name = c.GetSimulation().Resources()[comp.resource].label;
-               need->growth = comp.growth.FakeNormal(random.SNorm());
-               need->value = 0.4;
-               need->inconvenient = 0.5;
-               need->critical = 0.95;
-               c.AddNeed(std::move(need));
-       }
+       // TODO: derive stats from properties
+       c.GetStats().Damage().gain = (-1.0 / 100.0);
+       c.GetStats().Breath().gain = (1.0 / 5.0);
+       c.GetStats().Thirst().gain = (1.0 / 60.0);
+       c.GetStats().Hunger().gain = (1.0 / 200.0);
+       c.GetStats().Exhaustion().gain = (-1.0 / 100.0);
+       c.GetStats().Fatigue().gain = (-1.0 / 100.0);
+       c.GetStats().Boredom().gain = (1.0 / 300.0);
 
        glm::dvec3 base_color(
                std::fmod(base_hue.FakeNormal(random.SNorm()) + 1.0, 1.0),
@@ -497,11 +517,6 @@ void Genome::Configure(Creature &c) const {
        );
        c.BaseColor(hsl2rgb(base_color));
        c.HighlightColor(hsl2rgb(highlight_color));
-
-       c.Mass(c.GetProperties().props[0].mass);
-       c.Density(mass / volume);
-       c.GetSteering().MaxAcceleration(1.4 * std::atan(c.GetProperties().strength));
-       c.GetSteering().MaxSpeed(4.4 * std::atan(c.GetProperties().dexerty));
        c.SetBackgroundTask(std::unique_ptr<Goal>(new BlobBackgroundTask(c)));
        c.AddGoal(std::unique_ptr<Goal>(new IdleGoal(c)));
 }
@@ -512,11 +527,14 @@ void Split(Creature &c) {
        const Situation &s = c.GetSituation();
        a->Name(c.GetSimulation().Assets().name.Sequential());
        c.GetGenome().Configure(*a);
+       for (const auto &cmp : c.GetComposition()) {
+               a->AddMass(cmp.resource, cmp.value * 0.5);
+       }
        s.GetPlanet().AddCreature(a);
        // TODO: duplicate situation somehow
        a->GetSituation().SetPlanetSurface(
                s.GetPlanet(), s.Surface(),
-               s.Position() + glm::dvec3(0.0, a->Size() * 0.51, 0.0));
+               s.Position() + glm::dvec3(0.0, a->Size() + 0.1, 0.0));
        a->BuildVAO();
        std::cout << "[" << int(c.GetSimulation().Time()) << "s] "
                << a->Name() << " was born" << std::endl;
@@ -524,10 +542,13 @@ void Split(Creature &c) {
        Creature *b = new Creature(c.GetSimulation());
        b->Name(c.GetSimulation().Assets().name.Sequential());
        c.GetGenome().Configure(*b);
+       for (const auto &cmp : c.GetComposition()) {
+               b->AddMass(cmp.resource, cmp.value * 0.5);
+       }
        s.GetPlanet().AddCreature(b);
        b->GetSituation().SetPlanetSurface(
                s.GetPlanet(), s.Surface(),
-               s.Position() + glm::dvec3(0.0, b->Size() * -0.51, 0.0));
+               s.Position() + glm::dvec3(0.0, b->Size() - 0.1, 0.0));
        b->BuildVAO();
        std::cout << "[" << int(c.GetSimulation().Time()) << "s] "
                << b->Name() << " was born" << std::endl;
@@ -554,6 +575,16 @@ void Memory::TrackStay(const Location &l, double t) {
        const world::TileType &type = l.planet->TypeAt(l.surface, l.coords.x, l.coords.y);
        auto entry = known_types.find(type.id);
        if (entry != known_types.end()) {
+               if (c.GetSimulation().Time() - entry->second.last_been > c.GetProperties().Lifetime() * 0.1) {
+                       // "it's been ages"
+                       if (entry->second.time_spent > c.Age() * 0.25) {
+                               // the place is very familiar
+                               c.GetStats().Boredom().Add(-0.2);
+                       } else {
+                               // infrequent stays
+                               c.GetStats().Boredom().Add(-0.1);
+                       }
+               }
                entry->second.last_been = c.GetSimulation().Time();
                entry->second.last_loc = l;
                entry->second.time_spent += t;
@@ -565,6 +596,9 @@ void Memory::TrackStay(const Location &l, double t) {
                        l,
                        t
                });
+               // completely new place, interesting
+               // TODO: scale by personality trait
+               c.GetStats().Boredom().Add(-0.25);
        }
 }
 
@@ -646,7 +680,7 @@ Steering::Steering(const Creature &c)
 : c(c)
 , target(0.0)
 , haste(0.0)
-, max_accel(1.0)
+, max_force(1.0)
 , max_speed(1.0)
 , min_dist(0.0)
 , max_look(0.0)
@@ -689,9 +723,9 @@ void Steering::GoTo(const glm::dvec3 &t) noexcept {
        arriving = true;
 }
 
-glm::dvec3 Steering::Acceleration(const Situation::State &s) const noexcept {
+glm::dvec3 Steering::Force(const Situation::State &s) const noexcept {
        double speed = max_speed * glm::clamp(max_speed * haste * haste, 0.25, 1.0);
-       double accel = max_speed * glm::clamp(max_accel * haste * haste, 0.5, 1.0);
+       double force = max_speed * glm::clamp(max_force * haste * haste, 0.5, 1.0);
        glm::dvec3 result(0.0);
        if (separating) {
                // TODO: off surface situation
@@ -706,22 +740,22 @@ glm::dvec3 Steering::Acceleration(const Situation::State &s) const noexcept {
                                repulse += normalize(diff) * (1.0 - sep / min_dist);
                        }
                }
-               SumForce(result, repulse, accel);
+               SumForce(result, repulse, force);
        }
        if (halting) {
-               SumForce(result, s.vel * -accel, accel);
+               SumForce(result, s.vel * -force, force);
        }
        if (seeking) {
                glm::dvec3 diff = target - s.pos;
                if (!allzero(diff)) {
-                       SumForce(result, TargetVelocity(s, (normalize(diff) * speed), accel), accel);
+                       SumForce(result, TargetVelocity(s, (normalize(diff) * speed), force), force);
                }
        }
        if (arriving) {
                glm::dvec3 diff = target - s.pos;
                double dist = length(diff);
                if (!allzero(diff) && dist > std::numeric_limits<double>::epsilon()) {
-                       SumForce(result, TargetVelocity(s, diff * std::min(dist * accel, speed) / dist, accel), accel);
+                       SumForce(result, TargetVelocity(s, diff * std::min(dist * force, speed) / dist, force), force);
                }
        }
        return result;
index 9000bc2dc10a61ba59776c033bd24a6a1ca3083d..31983dee92fe6d0650381bd6b1fa90a2edfadd75 100644 (file)
@@ -1,6 +1,7 @@
 #include "BlobBackgroundTask.hpp"
 #include "Goal.hpp"
 #include "IdleGoal.hpp"
+#include "IngestGoal.hpp"
 #include "LocateResourceGoal.hpp"
 
 #include "Creature.hpp"
@@ -12,6 +13,7 @@
 
 #include <cstring>
 #include <iostream>
+#include <sstream>
 #include <glm/gtx/io.hpp>
 
 
@@ -19,7 +21,10 @@ namespace blobs {
 namespace creature {
 
 BlobBackgroundTask::BlobBackgroundTask(Creature &c)
-: Goal(c) {
+: Goal(c)
+, breathing(false)
+, drink_subtask(nullptr)
+, eat_subtask(nullptr) {
 }
 
 BlobBackgroundTask::~BlobBackgroundTask() {
@@ -30,55 +35,187 @@ std::string BlobBackgroundTask::Describe() const {
 }
 
 void BlobBackgroundTask::Tick(double dt) {
+       if (breathing) {
+               // TODO: derive breathing ability
+               double amount = GetCreature().GetStats().Breath().gain * -(1.0 + GetCreature().ExhaustionFactor());
+               GetCreature().GetStats().Breath().Add(amount * dt);
+               if (GetCreature().GetStats().Breath().Empty()) {
+                       breathing = false;
+               }
+       }
 }
 
 void BlobBackgroundTask::Action() {
-       // check if eligible to split
-       if (GetCreature().Mass() > GetCreature().GetProperties().Birth().mass * 1.8) {
-               double fert = GetCreature().Fertility();
-               double rand = Assets().random.UNorm();
-               if (fert > rand) {
-                       std::cout << "[" << int(GetCreature().GetSimulation().Time())
-                               << "s] " << GetCreature().Name() << " split" << std::endl;
-                       Split(GetCreature());
-                       return;
+       CheckStats();
+       CheckSplit();
+       CheckMutate();
+}
+
+void BlobBackgroundTask::CheckStats() {
+       Creature::Stats &stats = GetCreature().GetStats();
+
+       if (!breathing && stats.Breath().Bad()) {
+               breathing = true;
+       }
+
+       if (!drink_subtask && stats.Thirst().Bad()) {
+               drink_subtask = new IngestGoal(GetCreature(), stats.Thirst());
+               for (const auto &cmp : GetCreature().GetComposition()) {
+                       if (Assets().data.resources[cmp.resource].state == world::Resource::LIQUID) {
+                               drink_subtask->Accept(cmp.resource, 1.0);
+                       }
+               }
+               drink_subtask->OnComplete([&](Goal &) { drink_subtask = nullptr; });
+               GetCreature().AddGoal(std::unique_ptr<Goal>(drink_subtask));
+       }
+
+       if (!eat_subtask && stats.Hunger().Bad()) {
+               eat_subtask = new IngestGoal(GetCreature(), stats.Hunger());
+               for (const auto &cmp : GetCreature().GetComposition()) {
+                       if (Assets().data.resources[cmp.resource].state == world::Resource::SOLID) {
+                               eat_subtask->Accept(cmp.resource, 1.0);
+                       }
                }
+               eat_subtask->OnComplete([&](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()) {
+               std::cout << "[" << int(GetCreature().GetSimulation().Time())
+                       << "s] " << GetCreature().Name() << " split" << std::endl;
+               Split(GetCreature());
+               return;
+       }
+}
+
+void BlobBackgroundTask::CheckMutate() {
        // check for random property mutation
-       if (GetCreature().Mutability() > Assets().random.UNorm()) {
+       if (GetCreature().MutateChance() > Assets().random.UNorm()) {
                double amount = 1.0 + (Assets().random.SNorm() * 0.05);
-               auto &props = GetCreature().GetGenome().properties;
-               double r = Assets().random.UNorm();
-               math::Distribution *d = nullptr;
+               math::Distribution &d = GetCreature().GetGenome().properties.props[(int(Assets().random.UNorm() * 8.0) % 8)];
                if (Assets().random.UNorm() < 0.5) {
-                       auto &set = props.props[(int(Assets().random.UNorm() * 4.0) % 4) + 1];
-                       if (r < 0.25) {
-                               d = &set.age;
-                       } else if (r < 0.5) {
-                               d = &set.mass;
-                       } else if (r < 0.75) {
-                               d = &set.fertility;
-                       } else {
-                               d = &set.highlight;
-                       }
+                       d.Mean(d.Mean() * amount);
                } else {
-                       if (r < 0.2) {
-                               d = &props.strength;
-                       } else if (r < 0.4) {
-                               d = &props.stamina;
-                       } else if (r < 0.6) {
-                               d = &props.dexerty;
-                       } else if (r < 0.8) {
-                               d = &props.intelligence;
-                       } else {
-                               d = &props.mutability;
+                       d.StandardDeviation(d.StandardDeviation() * amount);
+               }
+       }
+}
+
+namespace {
+
+std::string summarize(const Composition &comp, const app::Assets &assets) {
+       std::stringstream s;
+       bool first = true;
+       for (const auto &c : comp) {
+               if (first) {
+                       first = false;
+               } else {
+                       s << " or ";
+               }
+               s << assets.data.resources[c.resource].label;
+       }
+       return s.str();
+}
+
+}
+
+IngestGoal::IngestGoal(Creature &c, Creature::Stat &stat)
+: Goal(c)
+, stat(stat)
+, accept()
+, locate_subtask(nullptr)
+, ingesting(false)
+, resource(-1)
+, yield(0.0) {
+       Urgency(stat.value);
+}
+
+IngestGoal::~IngestGoal() {
+}
+
+void IngestGoal::Accept(int resource, double value) {
+       accept.Add(resource, value);
+}
+
+std::string IngestGoal::Describe() const {
+       if (resource == -1) {
+               return "ingest " + summarize(accept, Assets());
+       } else {
+               const world::Resource &r = Assets().data.resources[resource];
+               if (r.state == world::Resource::SOLID) {
+                       return "eat " + r.label;
+               } else {
+                       return "drink " + r.label;
+               }
+       }
+}
+
+void IngestGoal::Enable() {
+}
+
+void IngestGoal::Tick(double dt) {
+       Urgency(stat.value);
+       if (locate_subtask) {
+               locate_subtask->Urgency(Urgency() + 0.1);
+       }
+       if (ingesting) {
+               if (OnSuitableTile() && !GetSituation().Moving()) {
+                       // TODO: determine satisfaction factor
+                       GetCreature().Ingest(resource, yield * dt);
+                       stat.Add(-1.0 * yield * dt);
+                       if (stat.Empty()) {
+                               SetComplete();
                        }
+               } else {
+                       // left tile somehow, some idiot probably pushed us off
+                       ingesting = false;
+                       Interruptible(true);
                }
-               if (Assets().random.UNorm() < 0.5) {
-                       d->Mean(d->Mean() * amount);
+       }
+}
+
+void IngestGoal::Action() {
+       if (ingesting) {
+               // all good
+               return;
+       }
+       if (OnSuitableTile()) {
+               if (GetSituation().Moving()) {
+                       // break with maximum force
+                       GetSteering().Haste(1.0);
+                       GetSteering().Halt();
                } else {
-                       d->StandardDeviation(d->StandardDeviation() * amount);
+                       // finally
+                       Interruptible(false);
+                       ingesting = true;
                }
+       } else {
+               locate_subtask = new LocateResourceGoal(GetCreature());
+               for (const auto &c : accept) {
+                       locate_subtask->Accept(c.resource, c.value);
+               }
+               locate_subtask->Urgency(Urgency() + 0.1);
+               locate_subtask->OnComplete([&](Goal &){ locate_subtask = nullptr; });
+               GetCreature().AddGoal(std::unique_ptr<Goal>(locate_subtask));
+       }
+}
+
+bool IngestGoal::OnSuitableTile() {
+       if (!GetSituation().OnSurface()) {
+               return false;
+       }
+       const world::TileType &t = GetSituation().GetTileType();
+       auto found = t.FindBestResource(accept);
+       if (found != t.resources.end()) {
+               resource = found->resource;
+               yield = found->ubiquity;
+               return true;
+       } else {
+               resource = -1;
+               return false;
        }
 }
 
@@ -158,9 +295,9 @@ void IdleGoal::Action() {
 }
 
 
-LocateResourceGoal::LocateResourceGoal(Creature &c, int res)
+LocateResourceGoal::LocateResourceGoal(Creature &c)
 : Goal(c)
-, res(res)
+, accept()
 , found(false)
 , target_pos(0.0)
 , target_srf(0)
@@ -172,8 +309,12 @@ LocateResourceGoal::LocateResourceGoal(Creature &c, int res)
 LocateResourceGoal::~LocateResourceGoal() noexcept {
 }
 
+void LocateResourceGoal::Accept(int resource, double value) {
+       accept.Add(resource, value);
+}
+
 std::string LocateResourceGoal::Describe() const {
-       return "locate " + GetCreature().GetSimulation().Resources()[res].name;
+       return "locate " + summarize(accept, Assets());
 }
 
 void LocateResourceGoal::Enable() {
@@ -213,7 +354,7 @@ void LocateResourceGoal::Action() {
 void LocateResourceGoal::LocateResource() {
        if (GetSituation().OnSurface()) {
                const world::TileType &t = GetSituation().GetTileType();
-               auto yield = t.FindResource(res);
+               auto yield = t.FindBestResource(accept);
                if (yield != t.resources.cend()) {
                        // hoooray
                        GetSteering().Halt();
@@ -241,6 +382,15 @@ void LocateResourceGoal::SearchVicinity() {
        glm::ivec2 begin(glm::max(glm::ivec2(0), loc - seek_radius));
        glm::ivec2 end(glm::min(glm::ivec2(planet.SideLength()), loc + seek_radius + glm::ivec2(1)));
 
+       // this happens when location is way off the planet
+       // that's a bug in Situation, actually, but I'm working aound that here
+       if (end.x <= begin.x) {
+               end.x = begin.x + 2;
+       }
+       if (end.y <= begin.y) {
+               end.y = begin.y + 2;
+       }
+
        double rating[end.y - begin.y][end.x - begin.x];
        std::memset(rating, 0, sizeof(double) * (end.y - begin.y) * (end.x - begin.x));
 
@@ -248,10 +398,10 @@ void LocateResourceGoal::SearchVicinity() {
        for (int y = begin.y; y < end.y; ++y) {
                for (int x = begin.x; x < end.x; ++x) {
                        const world::TileType &type = planet.TypeAt(srf, x, y);
-                       auto yield = type.FindResource(res);
+                       auto yield = type.FindBestResource(accept);
                        if (yield != type.resources.cend()) {
                                // TODO: subtract minimum yield
-                               rating[y - begin.y][x - begin.x] = yield->ubiquity;
+                               rating[y - begin.y][x - begin.x] = yield->ubiquity * accept.Get(yield->resource);
                                double dist = std::max(0.125, 0.25 * glm::length(planet.TileCenter(srf, x, y) - pos));
                                rating[y - begin.y][x - begin.x] /= dist;
                        }
diff --git a/src/creature/need.cpp b/src/creature/need.cpp
deleted file mode 100644 (file)
index 576655a..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-#include "Need.hpp"
-#include "InhaleNeed.hpp"
-#include "IngestNeed.hpp"
-
-#include "Creature.hpp"
-#include "LocateResourceGoal.hpp"
-#include "../world/Planet.hpp"
-#include "../world/TileType.hpp"
-
-
-namespace blobs {
-namespace creature {
-
-Need::~Need() {
-}
-
-void Need::Tick(double dt) noexcept {
-       Increase(gain * dt);
-}
-
-void Need::Increase(double delta) noexcept {
-       value = std::min(1.0, value + delta);
-}
-
-void Need::Decrease(double delta) noexcept {
-       value = std::max(0.0, value - delta);
-}
-
-
-IngestNeed::IngestNeed(int resource, double speed, double damage)
-: locate_goal(nullptr)
-, resource(resource)
-, speed(speed)
-, damage(damage)
-, ingesting(false) {
-}
-
-IngestNeed::~IngestNeed() {
-}
-
-void IngestNeed::ApplyEffect(Creature &c, double dt) {
-       if (!IsSatisfied()) {
-               ingesting = true;
-       }
-       if (ingesting) {
-               if (c.GetSituation().OnSurface()) {
-                       const world::TileType &t = c.GetSituation().GetTileType();
-                       bool found = false;
-                       for (auto &yield : t.resources) {
-                               if (yield.resource == resource) {
-                                       found = true;
-                                       // TODO: check if not busy with something else
-                                       double amount = std::min(yield.ubiquity, speed) * dt;
-                                       c.Ingest(resource, amount * growth * dt);
-                                       Decrease(amount);
-                                       if (value == 0.0) {
-                                               ingesting = false;
-                                               if (locate_goal) {
-                                                       // abort
-                                                       locate_goal->Complete();
-                                               }
-                                       }
-                                       break;
-                               }
-                       }
-                       if (!found && !locate_goal) {
-                               locate_goal = new LocateResourceGoal(c, resource);
-                               locate_goal->OnComplete([&](Goal &g){ OnLocateComplete(g); });
-                               c.AddGoal(std::unique_ptr<Goal>(locate_goal));
-                       }
-               }
-       }
-       if (IsCritical()) {
-               c.Hurt(damage * dt);
-       }
-       if (locate_goal) {
-               locate_goal->Urgency(value);
-       }
-}
-
-void IngestNeed::OnLocateComplete(Goal &g) {
-       if (&g == locate_goal) {
-               locate_goal = nullptr;
-       }
-}
-
-
-InhaleNeed::InhaleNeed(int resource, double speed, double damage)
-: resource(resource)
-, speed(speed)
-, damage(damage)
-, inhaling(false) {
-}
-
-InhaleNeed::~InhaleNeed() {
-}
-
-void InhaleNeed::ApplyEffect(Creature &c, double dt) {
-       if (!IsSatisfied()) {
-               inhaling = true;
-       }
-       if (inhaling) {
-               if (c.GetSituation().OnPlanet() && c.GetSituation().GetPlanet().Atmosphere() == resource) {
-                       Decrease(speed * dt);
-                       if (value == 0.0) {
-                               inhaling = false;
-                       }
-               } else {
-                       // TODO: panic
-               }
-       }
-       if (IsCritical()) {
-               c.Hurt(damage * dt);
-       }
-}
-
-}
-}
index b8a7bf602890af49c3a752939f41e79d34e3374a..864fede83c983756ad5f2af333b984bc37c62b53 100644 (file)
@@ -48,17 +48,16 @@ private:
        creature::Creature *c;
 
        Label *name;
+       Label *born;
        Label *age;
        Label *mass;
        Label *pos;
        Label *tile;
        Label *goal;
-       Panel *needs;
+       Meter *stats[7];
+       Label *props[8];
        Panel panel;
 
-       Meter *health_meter;
-       std::vector<Meter *> need_meters;
-
 };
 
 }
index 1bc7e86bc4f0de8e733818c5eb3bd427b556bf1c..267cc639c6bc1b39470593f57db496a0d86aae68 100644 (file)
@@ -22,6 +22,11 @@ public:
 
 public:
        Label *Text(const std::string &);
+       Label *Decimal(double n, int prec = 2);
+       Label *Length(double m);
+       Label *Mass(double kg);
+       Label *Percentage(double n);
+       Label *Time(double s);
        Label *Font(const graphics::Font &);
        Label *Foreground(const glm::vec4 &);
        Label *Background(const glm::vec4 &);
index 926766191e9b709a72bda65e9456a741472b91e8..d49dbf411a94534f513cce6a54f9320cf9d7a42c 100644 (file)
@@ -37,7 +37,7 @@ public:
        Panel *Direction(Dir);
 
        glm::vec2 Size() override;
-       void Relayout();
+       void Layout();
        void Draw(app::Assets &, graphics::Viewport &) noexcept override;
 
 private:
index e62894db3cdeef62af513a5d3e5aa4da10b9f5e5..91ae1062fe4a176157ff5d7ee0e5d0505dd72710 100644 (file)
@@ -4,11 +4,12 @@
 #include "Meter.hpp"
 #include "../app/Assets.hpp"
 #include "../creature/Creature.hpp"
-#include "../creature/Need.hpp"
 #include "../graphics/Viewport.hpp"
 
 #include <iomanip>
+#include <iostream>
 #include <sstream>
+#include <glm/gtx/io.hpp>
 #include <glm/gtx/transform.hpp>
 
 
@@ -19,31 +20,26 @@ CreaturePanel::CreaturePanel(const app::Assets &assets)
 : assets(assets)
 , c(nullptr)
 , name(new Label(assets.fonts.large))
+, born(new Label(assets.fonts.medium))
 , age(new Label(assets.fonts.medium))
 , mass(new Label(assets.fonts.medium))
 , pos(new Label(assets.fonts.medium))
 , tile(new Label(assets.fonts.medium))
 , goal(new Label(assets.fonts.medium))
-, needs(new Panel)
-, panel()
-, health_meter(new Meter)
-, need_meters() {
-       Label *health_label = new Label(assets.fonts.medium);
-       health_label->Text("Health");
-       health_meter
-               ->Size(glm::vec2(100.0f, assets.fonts.medium.Height() + assets.fonts.medium.Descent()))
-               ->Padding(glm::vec2(1.0f))
-               ->Border(1.0f)
-               ->FillColor(glm::vec4(0.9f, 0.0f, 0.0f, 1.0f))
-               ->BorderColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
-       Panel *health_panel = new Panel;
-       health_panel
-               ->Add(health_label)
-               ->Add(health_meter)
+, stats{nullptr}
+, props{nullptr}
+, panel() {
+       born->Text("00h 00m 00s");
+       Label *born_label = new Label(assets.fonts.medium);
+       born_label->Text("Born");
+       Panel *born_panel = new Panel;
+       born_panel
+               ->Add(born_label)
+               ->Add(born)
                ->Spacing(10.0f)
                ->Direction(Panel::HORIZONTAL);
 
-       age->Text("0000s (Newborn)");
+       age->Text("00h 00m 00s");
        Label *age_label = new Label(assets.fonts.medium);
        age_label->Text("Age");
        Panel *age_panel = new Panel;
@@ -93,15 +89,86 @@ CreaturePanel::CreaturePanel(const app::Assets &assets)
                ->Spacing(10.0f)
                ->Direction(Panel::HORIZONTAL);
 
+       Label *stat_label[7];
+       for (int i = 0; i < 7; ++i) {
+               stat_label[i] = new Label(assets.fonts.medium);
+               stats[i] = new Meter;
+               stats[i]
+                       ->Size(glm::vec2(100.0f, assets.fonts.medium.Height() + assets.fonts.medium.Descent()))
+                       ->Padding(glm::vec2(1.0f))
+                       ->Border(1.0f)
+                       ->BorderColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
+       }
+       stat_label[0]->Text("Damage");
+       stat_label[1]->Text("Breath");
+       stat_label[2]->Text("Thirst");
+       stat_label[3]->Text("Hunger");
+       stat_label[4]->Text("Exhaustion");
+       stat_label[5]->Text("Fatigue");
+       stat_label[6]->Text("Boredom");
+
+       Panel *stat_label_panel = new Panel;
+       stat_label_panel
+               ->Spacing(2)
+               ->Direction(Panel::VERTICAL);
+       Panel *stat_meter_panel = new Panel;
+       stat_meter_panel
+               ->Spacing(stat_label[0]->Size().y - stats[0]->Size().y + 2)
+               ->Direction(Panel::VERTICAL);
+       for (int i = 0; i < 7; ++i) {
+               stat_label_panel->Add(stat_label[i]);
+               stat_meter_panel->Add(stats[i]);
+       }
+       Panel *stat_panel = new Panel;
+       stat_panel
+               ->Direction(Panel::HORIZONTAL)
+               ->Spacing(10)
+               ->Add(stat_label_panel)
+               ->Add(stat_meter_panel);
+
+       Label *prop_label[8];
+       for (int i = 0; i < 8; ++i) {
+               prop_label[i] = new Label(assets.fonts.medium);
+               props[i] = new Label(assets.fonts.medium);
+       }
+       prop_label[0]->Text("Strength");
+       prop_label[1]->Text("Stamina");
+       prop_label[2]->Text("Dexerty");
+       prop_label[3]->Text("Intelligence");
+       prop_label[4]->Text("Lifetime");
+       prop_label[5]->Text("Fertility");
+       prop_label[6]->Text("Mutability");
+       prop_label[7]->Text("Offspring mass");
+
+       Panel *prop_label_panel = new Panel;
+       prop_label_panel
+               ->Spacing(2)
+               ->Direction(Panel::VERTICAL);
+       Panel *prop_meter_panel = new Panel;
+       prop_meter_panel
+               ->Spacing(2)
+               ->Direction(Panel::VERTICAL);
+       for (int i = 0; i < 8; ++i) {
+               prop_label_panel->Add(prop_label[i]);
+               prop_meter_panel->Add(props[i]);
+       }
+       Panel *prop_panel = new Panel;
+       prop_panel
+               ->Direction(Panel::HORIZONTAL)
+               ->Spacing(10)
+               ->Add(prop_label_panel)
+               ->Add(prop_meter_panel);
+
        panel
                .Add(name)
                ->Add(age_panel)
+               ->Add(born_panel)
                ->Add(mass_panel)
                ->Add(pos_panel)
                ->Add(tile_panel)
                ->Add(goal_panel)
-               ->Add(health_panel)
-               ->Add(needs)
+               ->Add(stat_panel)
+               ->Add(prop_panel)
                ->Padding(glm::vec2(10.0f))
                ->Spacing(10.0f)
                ->Direction(Panel::VERTICAL)
@@ -115,34 +182,7 @@ CreaturePanel::~CreaturePanel() {
 void CreaturePanel::Show(creature::Creature &cr) {
        c = &cr;
        name->Text(c->Name());
-       CreateNeeds();
-}
-
-void CreaturePanel::CreateNeeds() {
-       needs->Clear()->Reserve(c->Needs().size());
-       need_meters.clear();
-       need_meters.reserve(c->Needs().size());
-       for (auto &need : c->Needs()) {
-               Label *label = new Label(assets.fonts.medium);
-               label->Text(need->name);
-               Meter *meter = new Meter;
-               meter
-                       ->Value(1.0f - need->value)
-                       ->Size(glm::vec2(100.0f, assets.fonts.medium.Height() + assets.fonts.medium.Descent()))
-                       ->Padding(glm::vec2(1.0f))
-                       ->Border(1.0f)
-                       ->FillColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f))
-                       ->BorderColor(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
-               Panel *need_panel = new Panel;
-               need_panel
-                       ->Direction(Panel::HORIZONTAL)
-                       ->Spacing(10.0f)
-                       ->Add(label)
-                       ->Add(meter);
-               needs->Add(need_panel);
-               need_meters.push_back(meter);
-       }
-       panel.Relayout();
+       born->Time(c->Born());
 }
 
 void CreaturePanel::Hide() noexcept {
@@ -152,12 +192,8 @@ void CreaturePanel::Hide() noexcept {
 void CreaturePanel::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
        if (!c) return;
 
-       age->Text(std::to_string(int(c->Age())) + "s (" + c->AgeName() + ")");
-       {
-               std::stringstream ss;
-               ss << std::fixed << std::setprecision(3) << c->Mass() << "kg";
-               mass->Text(ss.str());
-       }
+       age->Time(c->Age());
+       mass->Mass(c->Mass());
        {
                const glm::dvec3 &p = c->GetSituation().Position();
                std::stringstream ss;
@@ -178,26 +214,27 @@ void CreaturePanel::Draw(app::Assets &assets, graphics::Viewport &viewport) noex
        } else {
                goal->Text(c->Goals()[0]->Describe());
        }
-       health_meter->Value(c->Health());
 
-       if (need_meters.size() != c->Needs().size()) {
-               CreateNeeds();
-       } else {
-               auto need = c->Needs().begin();
-               auto need_end = c->Needs().end();
-               auto meter = need_meters.begin();
-               for (; need != need_end; ++need, ++meter) {
-                       (*meter)->Value(1.0f - (*need)->value);
-                       if ((*need)->IsSatisfied()) {
-                               (*meter)->FillColor(glm::vec4(0.0f, 0.7f, 0.0f, 1.0f));
-                       } else if ((*need)->IsInconvenient()) {
-                               (*meter)->FillColor(glm::vec4(0.7f, 0.5f, 0.0f, 1.0f));
-                       } else {
-                               (*meter)->FillColor(glm::vec4(0.9f, 0.0f, 0.0f, 1.0f));
-                       }
+       for (int i = 0; i < 7; ++i) {
+               stats[i]->Value(c->GetStats().stat[i].value);
+               if (c->GetStats().stat[i].Okay()) {
+                       stats[i]->FillColor(glm::vec4(0.0f, 0.7f, 0.0f, 1.0f));
+               } else if (c->GetStats().stat[i].Critical()) {
+                       stats[i]->FillColor(glm::vec4(0.7f, 0.0f, 0.0f, 1.0f));
+               } else {
+                       stats[i]->FillColor(glm::vec4(0.9f, 0.4f, 0.0f, 1.0f));
                }
        }
 
+       props[0]->Decimal(c->Strength());
+       props[1]->Decimal(c->Stamina());
+       props[2]->Decimal(c->Dexerty());
+       props[3]->Decimal(c->Intelligence());
+       props[4]->Time(c->Lifetime());
+       props[5]->Percentage(c->Fertility());
+       props[6]->Percentage(c->Mutability());
+       props[7]->Mass(c->OffspringMass());
+
        const glm::vec2 margin(20.0f);
 
        panel.Position(glm::vec2(viewport.Width() - margin.x - panel.Size().x, margin.y));
index 6d88ad71b46772d7b636c53c6c3e89beee853a30..b0d44e20969fa23a55bbfe589e76d4b0fa3332be 100644 (file)
@@ -7,6 +7,8 @@
 #include "../graphics/Font.hpp"
 #include "../graphics/Viewport.hpp"
 
+#include <iomanip>
+#include <sstream>
 #include <glm/gtx/transform.hpp>
 
 
@@ -33,6 +35,61 @@ Label *Label::Text(const std::string &t) {
        return this;
 }
 
+Label *Label::Decimal(double n, int prec) {
+       std::stringstream s;
+       s << std::fixed << std::setprecision(prec) << n;
+       return Text(s.str());
+}
+
+Label *Label::Length(double m) {
+       std::stringstream s;
+       s << std::fixed << std::setprecision(3);
+       if (m > 1500.0) {
+               s << (m * 0.001) << "km";
+       } else if (m < 0.1) {
+               s << (m * 1000.0) << "mm";
+       } else {
+               s << m << "m";
+       }
+       return Text(s.str());
+}
+
+Label *Label::Mass(double kg) {
+       std::stringstream s;
+       s << std::fixed << std::setprecision(3);
+       if (kg > 1500.0) {
+               s << (kg * 0.001) << "t";
+       } else if (kg < 0.1) {
+               s << (kg * 1000.0) << "g";
+       } else if (kg < 0.0001) {
+               s << (kg * 1.0e6) << "mg";
+       } else {
+               s << kg << "kg";
+       }
+       return Text(s.str());
+}
+
+Label *Label::Percentage(double n) {
+       std::stringstream s;
+       s << std::fixed << std::setprecision(1) << (n * 100.0) << '%';
+       return Text(s.str());
+}
+
+Label *Label::Time(double s) {
+       int is = int(s);
+       std::stringstream ss;
+       if (is >= 3600) {
+               ss << (is / 3600) << "h ";
+               is %= 3600;
+       }
+       if (is >= 60) {
+               ss << (is / 60) << "m ";
+               is %= 60;
+       }
+       ss << is << 's';
+       return Text(ss.str());
+}
+
 Label *Label::Font(const graphics::Font &f) {
        if (font != &f) {
                dirty = true;
@@ -60,6 +117,7 @@ glm::vec2 Label::Size() {
 }
 
 void Label::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
+       if (text.empty()) return;
        Update();
        glm::vec2 size = Size();
 
@@ -73,7 +131,7 @@ void Label::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
 }
 
 void Label::Update() {
-       if (!dirty) return;
+       if (!dirty || text.empty()) return;
        font->Render(text, tex);
        dirty = false;
 }
@@ -170,7 +228,7 @@ Panel *Panel::Spacing(float s) {
 
 Panel *Panel::Direction(Dir d) {
        dir = d;
-       Relayout();
+       Layout();
        return this;
 }
 
@@ -180,7 +238,7 @@ glm::vec2 Panel::Size() {
        return (2.0f * padding) + space + size;
 }
 
-void Panel::Relayout() {
+void Panel::Layout() {
        size = glm::vec2(0.0f);
        if (dir == HORIZONTAL) {
                for (auto &w : widgets) {
@@ -198,6 +256,8 @@ void Panel::Relayout() {
 }
 
 void Panel::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
+       // TODO: separate draw and layout, it's inefficient and the wrong tree order anyway
+       Layout();
        if (bg_color.a > 0.0f) {
                assets.shaders.canvas.Activate();
                assets.shaders.canvas.ZIndex(ZIndex());
@@ -205,9 +265,7 @@ void Panel::Draw(app::Assets &assets, graphics::Viewport &viewport) noexcept {
                assets.shaders.canvas.FillRect(Position(), Position() + Size());
        }
 
-       glm::vec2 cursor = Position();
-       cursor.x += padding.x;
-       cursor.y += padding.y;
+       glm::vec2 cursor = Position() + padding;
        for (auto &w : widgets) {
                w->Position(cursor)->ZIndex(ZIndex() + 1.0f);
                w->Draw(assets, viewport);
index b5561fffa7a014875975c1325b96f84a456883a2..7f18ee30261ac4ed0156956afa0787e6f197519e 100644 (file)
@@ -6,6 +6,9 @@
 
 
 namespace blobs {
+namespace creature {
+       class Composition;
+}
 namespace world {
 
 struct TileType {
@@ -23,6 +26,7 @@ struct TileType {
        std::vector<Yield> resources;
 
        std::vector<Yield>::const_iterator FindResource(int) const;
+       std::vector<Yield>::const_iterator FindBestResource(const creature::Composition &) const;
 
 };
 
index dd7d28d3d06d34c92095df1a4c126ba8a44e2bd5..1b2e058f90d666dfd79d52474ad12ad0fe652686 100644 (file)
@@ -9,6 +9,7 @@
 #include "TileType.hpp"
 
 #include "../app/Assets.hpp"
+#include "../creature/Composition.hpp"
 #include "../creature/Creature.hpp"
 #include "../graphics/Viewport.hpp"
 #include "../math/const.hpp"
@@ -574,5 +575,18 @@ std::vector<TileType::Yield>::const_iterator TileType::FindResource(int r) const
        return yield;
 }
 
+std::vector<TileType::Yield>::const_iterator TileType::FindBestResource(const creature::Composition &comp) const {
+       auto best = resources.cend();
+       double best_value = 0.0;
+       for (auto yield = resources.cbegin(); yield != resources.cend(); ++yield) {
+               double value = comp.Get(yield->resource);
+               if (value > best_value) {
+                       best = yield;
+                       best_value = value;
+               }
+       }
+       return best;
+}
+
 }
 }