From cead4f0686af352cdbac1f2c2df9b6a21ad9faec Mon Sep 17 00:00:00 2001
From: Daniel Karbach <daniel.karbach@localhorst.tv>
Date: Wed, 13 Dec 2017 02:28:42 +0100
Subject: [PATCH] aggression

---
 src/creature/AttackGoal.hpp  | 35 +++++++++++++++++
 src/creature/Composition.hpp |  3 ++
 src/creature/Creature.hpp    |  2 +
 src/creature/Memory.hpp      |  7 ++++
 src/creature/creature.cpp    | 23 ++++++++++++
 src/creature/goal.cpp        | 73 ++++++++++++++++++++++++++++++++++++
 src/world/world.cpp          |  3 +-
 7 files changed, 145 insertions(+), 1 deletion(-)
 create mode 100644 src/creature/AttackGoal.hpp

diff --git a/src/creature/AttackGoal.hpp b/src/creature/AttackGoal.hpp
new file mode 100644
index 0000000..ab8056f
--- /dev/null
+++ b/src/creature/AttackGoal.hpp
@@ -0,0 +1,35 @@
+#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 &target;
+	double damage_target;
+	double damage_dealt;
+	double cooldown;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/creature/Composition.hpp b/src/creature/Composition.hpp
index 969407f..243c079 100644
--- a/src/creature/Composition.hpp
+++ b/src/creature/Composition.hpp
@@ -41,6 +41,8 @@ public:
 	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:
@@ -56,6 +58,7 @@ private:
 	const world::Set<world::Resource> &resources;
 	std::vector<Component> components;
 	double total_mass;
+	double total_volume;
 	double state_mass[4];
 
 };
diff --git a/src/creature/Creature.hpp b/src/creature/Creature.hpp
index d55ab6f..f22f3da 100644
--- a/src/creature/Creature.hpp
+++ b/src/creature/Creature.hpp
@@ -180,6 +180,8 @@ public:
 	math::AABB CollisionBounds() const noexcept;
 	glm::dmat4 CollisionTransform() const noexcept;
 
+	void OnCollide(Creature &other);
+
 	glm::dmat4 LocalTransform() noexcept;
 
 	void BuildVAO();
diff --git a/src/creature/Memory.hpp b/src/creature/Memory.hpp
index 1d72c75..192d03a 100644
--- a/src/creature/Memory.hpp
+++ b/src/creature/Memory.hpp
@@ -35,6 +35,8 @@ public:
 	/// location of the best fitting resource
 	bool RememberLocation(const Composition &, glm::dvec3 &pos) const noexcept;
 
+	void TrackCollision(Creature &);
+
 	void Tick(double dt);
 
 private:
@@ -52,6 +54,11 @@ 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;
 
 };
 
diff --git a/src/creature/creature.cpp b/src/creature/creature.cpp
index 1cf4a3a..7ad0657 100644
--- a/src/creature/creature.cpp
+++ b/src/creature/creature.cpp
@@ -6,6 +6,7 @@
 #include "Situation.hpp"
 #include "Steering.hpp"
 
+#include "AttackGoal.hpp"
 #include "BlobBackgroundTask.hpp"
 #include "Goal.hpp"
 #include "IdleGoal.hpp"
@@ -33,6 +34,7 @@ Composition::Composition(const world::Set<world::Resource> &resources)
 : resources(resources)
 , components()
 , total_mass(0.0)
+, total_volume(0.0)
 , state_mass{0.0} {
 }
 
@@ -64,6 +66,7 @@ void Composition::Add(int res, double amount) {
 	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 {
@@ -591,6 +594,10 @@ glm::dmat4 Creature::CollisionTransform() 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()
@@ -844,6 +851,7 @@ Memory::~Memory() {
 
 void Memory::Erase() {
 	known_types.clear();
+	known_creatures.clear();
 }
 
 bool Memory::RememberLocation(const Composition &accept, glm::dvec3 &pos) const noexcept {
@@ -877,6 +885,21 @@ bool Memory::RememberLocation(const Composition &accept, glm::dvec3 &pos) const
 	}
 }
 
+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()) {
diff --git a/src/creature/goal.cpp b/src/creature/goal.cpp
index abac467..41f91f3 100644
--- a/src/creature/goal.cpp
+++ b/src/creature/goal.cpp
@@ -1,3 +1,4 @@
+#include "AttackGoal.hpp"
 #include "BlobBackgroundTask.hpp"
 #include "Goal.hpp"
 #include "IdleGoal.hpp"
@@ -22,6 +23,78 @@
 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)
diff --git a/src/world/world.cpp b/src/world/world.cpp
index 2f0d130..1758c5e 100644
--- a/src/world/world.cpp
+++ b/src/world/world.cpp
@@ -208,11 +208,12 @@ void Body::CheckCollision() noexcept {
 		}
 	}
 	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
 	}
 }
 
-- 
2.39.5