--- /dev/null
+#ifndef BLOBS_CREATURE_ATTACKGOAL_HPP_
+#define BLOBS_CREATURE_ATTACKGOAL_HPP_
+
+#include "Goal.hpp"
+
+namespace blobs {
+namespace creature {
+
+class AttackGoal
+: public Goal {
+
+public:
+ AttackGoal(Creature &self, Creature &target);
+ ~AttackGoal() override;
+
+public:
+ std::string Describe() const override;
+ void Tick(double dt) override;
+ void Action() override;
+ void OnBackground() override;
+
+ void SetDamageTarget(double t) noexcept { damage_target = t; }
+
+private:
+ Creature ⌖
+ double damage_target;
+ double damage_dealt;
+ double cooldown;
+
+};
+
+}
+}
+
+#endif
double StateProportion(int res) const noexcept;
double Compatibility(int res) const noexcept;
double TotalMass() const noexcept { return total_mass; }
+ double TotalVolume() const noexcept { return total_volume; }
+ double TotalDensity() const noexcept { return total_mass / total_volume; }
double StateMass(world::Resource::State s) const noexcept { return state_mass[s]; }
public:
const world::Set<world::Resource> &resources;
std::vector<Component> components;
double total_mass;
+ double total_volume;
double state_mass[4];
};
math::AABB CollisionBounds() const noexcept;
glm::dmat4 CollisionTransform() const noexcept;
+ void OnCollide(Creature &other);
+
glm::dmat4 LocalTransform() noexcept;
void BuildVAO();
/// location of the best fitting resource
bool RememberLocation(const Composition &, glm::dvec3 &pos) const noexcept;
+ void TrackCollision(Creature &);
+
void Tick(double dt);
private:
double time_spent;
};
std::map<int, Stay> known_types;
+ struct Profile {
+ double annoyance = 0.0;
+ double familiarity = 0.0;
+ };
+ std::map<Creature *, Profile> known_creatures;
};
#include "Situation.hpp"
#include "Steering.hpp"
+#include "AttackGoal.hpp"
#include "BlobBackgroundTask.hpp"
#include "Goal.hpp"
#include "IdleGoal.hpp"
: resources(resources)
, components()
, total_mass(0.0)
+, total_volume(0.0)
, state_mass{0.0} {
}
std::sort(components.begin(), components.end(), CompositionCompare);
state_mass[resources[res].state] += amount;
total_mass += amount;
+ total_volume += amount / resources[res].density;
}
bool Composition::Has(int res) const noexcept {
* glm::translate(glm::dvec3(0.0, half_size, 0.0));
}
+void Creature::OnCollide(Creature &other) {
+ memory.TrackCollision(other);
+}
+
glm::dmat4 Creature::LocalTransform() noexcept {
const double half_size = size * 0.5;
return CollisionTransform()
void Memory::Erase() {
known_types.clear();
+ known_creatures.clear();
}
bool Memory::RememberLocation(const Composition &accept, glm::dvec3 &pos) const noexcept {
}
}
+void Memory::TrackCollision(Creature &other) {
+ // TODO: find out whose fault it was
+ // TODO: source values from personality
+ Profile &p = known_creatures[&other];
+ p.annoyance += 0.1;
+ const double annoy_fact = p.annoyance / (p.annoyance + 1.0);
+ if (c.GetSimulation().Assets().random.UNorm() > annoy_fact * 0.1 * (1.0 - c.GetStats().Damage().value)) {
+ AttackGoal *g = new AttackGoal(c, other);
+ g->SetDamageTarget(annoy_fact);
+ g->Urgency(annoy_fact);
+ c.AddGoal(std::unique_ptr<Goal>(g));
+ p.annoyance *= 0.5;
+ }
+}
+
void Memory::Tick(double dt) {
Situation &s = c.GetSituation();
if (s.OnSurface()) {
+#include "AttackGoal.hpp"
#include "BlobBackgroundTask.hpp"
#include "Goal.hpp"
#include "IdleGoal.hpp"
namespace blobs {
namespace creature {
+AttackGoal::AttackGoal(Creature &self, Creature &target)
+: Goal(self)
+, target(target)
+, damage_target(0.25)
+, damage_dealt(0.0)
+, cooldown(0.0) {
+}
+
+AttackGoal::~AttackGoal() {
+}
+
+std::string AttackGoal::Describe() const {
+ return "attack " + target.Name();
+}
+
+void AttackGoal::Tick(double dt) {
+ cooldown -= dt;
+}
+
+void AttackGoal::Action() {
+ if (target.Dead() || !GetCreature().PerceptionTest(target.GetSituation().Position())) {
+ SetComplete();
+ return;
+ }
+ const glm::dvec3 diff(GetCreature().GetSituation().Position() - target.GetSituation().Position());
+ const double hit_range = GetCreature().Size() * 0.5 * GetCreature().DexertyFactor();
+ const double hit_dist = hit_range + (0.5 * GetCreature().Size()) + 0.5 * (target.Size());
+ if (GetCreature().GetStats().Damage().Critical()) {
+ // flee
+ GetCreature().GetSteering().Pass(diff * 5.0);
+ GetCreature().GetSteering().DontSeparate();
+ GetCreature().GetSteering().Haste(1.0);
+ } else if (glm::length2(diff) > hit_dist * hit_dist) {
+ // full throttle chase
+ GetCreature().GetSteering().Pass(target.GetSituation().Position());
+ GetCreature().GetSteering().DontSeparate();
+ GetCreature().GetSteering().Haste(1.0);
+ } else {
+ // attack
+ GetCreature().GetSteering().Halt();
+ GetCreature().GetSteering().DontSeparate();
+ GetCreature().GetSteering().Haste(1.0);
+ if (cooldown <= 0.0) {
+ constexpr double impulse = 0.05;
+ const double force = GetCreature().Strength();
+ const double damage =
+ force * impulse
+ * (GetCreature().GetComposition().TotalDensity() / target.GetComposition().TotalDensity())
+ * (GetCreature().Mass() / target.Mass())
+ / target.Mass();
+ GetCreature().DoWork(force * impulse * glm::length(diff));
+ target.Hurt(damage);
+ target.GetSituation().Accelerate(glm::normalize(diff) * force * -impulse);
+ damage_dealt += damage;
+ if (damage_dealt >= damage_target || target.Dead()) {
+ SetComplete();
+ if (target.Dead()) {
+ GetCreature().GetSimulation().Log() << GetCreature().Name()
+ << " killed " << target.Name() << std::endl;
+ }
+ }
+ cooldown = 1.0 + (4.0 * (1.0 - GetCreature().DexertyFactor()));
+ }
+ }
+}
+
+void AttackGoal::OnBackground() {
+ // abort if something more important comes up
+ SetComplete();
+}
+
+
BlobBackgroundTask::BlobBackgroundTask(Creature &c)
: Goal(c)
, breathing(false)
}
}
for (auto &c : collisions) {
+ c.A().OnCollide(c.B());
+ c.B().OnCollide(c.A());
c.A().GetSituation().Move(c.Normal() * (c.Depth() * -0.5));
c.B().GetSituation().Move(c.Normal() * (c.Depth() * 0.5));
c.A().GetSituation().Accelerate(c.Normal() * -glm::dot(c.Normal(), c.AVel()));
c.B().GetSituation().Accelerate(c.Normal() * -glm::dot(c.Normal(), c.BVel()));
- // TODO: notify participants so they can be annoyed
}
}