From 18a763bcb19c5ece4b7b7d079dab07a1d915deb6 Mon Sep 17 00:00:00 2001 From: Aki Date: Sat, 3 Dec 2022 00:44:07 +0100 Subject: Moved battles module files to sim --- sim/CMakeLists.txt | 21 +++++ sim/include/kurator/sim/Battle.h | 31 +++++++ sim/include/kurator/sim/Point.h | 26 ++++++ sim/include/kurator/sim/Scenario.h | 24 ++++++ sim/include/kurator/sim/ShipConfig.h | 22 +++++ sim/include/kurator/sim/components.h | 59 +++++++++++++ sim/include/kurator/sim/events.h | 21 +++++ sim/include/kurator/sim/scenarios.h | 19 ++++ sim/src/BaseBattle.cpp | 162 +++++++++++++++++++++++++++++++++++ sim/src/BaseBattle.h | 40 +++++++++ sim/src/Battle.cpp | 24 ++++++ sim/src/Builder.cpp | 57 ++++++++++++ sim/src/Builder.h | 35 ++++++++ sim/src/Point.cpp | 72 ++++++++++++++++ sim/src/RandomSpawner.cpp | 42 +++++++++ sim/src/RandomSpawner.h | 30 +++++++ sim/src/Scenario.cpp | 23 +++++ sim/src/Spawner.h | 21 +++++ sim/src/TeamManager.cpp | 62 ++++++++++++++ sim/src/TeamManager.h | 31 +++++++ sim/src/scenarios.cpp | 43 ++++++++++ 21 files changed, 865 insertions(+) create mode 100644 sim/CMakeLists.txt create mode 100644 sim/include/kurator/sim/Battle.h create mode 100644 sim/include/kurator/sim/Point.h create mode 100644 sim/include/kurator/sim/Scenario.h create mode 100644 sim/include/kurator/sim/ShipConfig.h create mode 100644 sim/include/kurator/sim/components.h create mode 100644 sim/include/kurator/sim/events.h create mode 100644 sim/include/kurator/sim/scenarios.h create mode 100644 sim/src/BaseBattle.cpp create mode 100644 sim/src/BaseBattle.h create mode 100644 sim/src/Battle.cpp create mode 100644 sim/src/Builder.cpp create mode 100644 sim/src/Builder.h create mode 100644 sim/src/Point.cpp create mode 100644 sim/src/RandomSpawner.cpp create mode 100644 sim/src/RandomSpawner.h create mode 100644 sim/src/Scenario.cpp create mode 100644 sim/src/Spawner.h create mode 100644 sim/src/TeamManager.cpp create mode 100644 sim/src/TeamManager.h create mode 100644 sim/src/scenarios.cpp (limited to 'sim') diff --git a/sim/CMakeLists.txt b/sim/CMakeLists.txt new file mode 100644 index 0000000..f7478e3 --- /dev/null +++ b/sim/CMakeLists.txt @@ -0,0 +1,21 @@ +project(battles) +add_library( + ${PROJECT_NAME} + src/BaseBattle.cpp + src/Battle.cpp + src/Builder.cpp + src/Point.cpp + src/RandomSpawner.cpp + src/Scenario.cpp + src/scenarios.cpp + src/TeamManager.cpp +) +target_include_directories( + ${PROJECT_NAME} + PUBLIC include +) +target_link_libraries( + ${PROJECT_NAME} + PUBLIC EnTT::EnTT + PUBLIC universe +) diff --git a/sim/include/kurator/sim/Battle.h b/sim/include/kurator/sim/Battle.h new file mode 100644 index 0000000..1542597 --- /dev/null +++ b/sim/include/kurator/sim/Battle.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include +#include + +#include "Scenario.h" + + +namespace kurator +{ +namespace battles +{ + + +class Battle +{ +public: + virtual ~Battle() = default; + virtual entt::registry& registry() = 0; + virtual entt::dispatcher& dispatcher() = 0; + virtual void update(float dt) = 0; +}; + + +auto prepare(const Scenario& scenario) -> std::unique_ptr; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/Point.h b/sim/include/kurator/sim/Point.h new file mode 100644 index 0000000..ba9ab63 --- /dev/null +++ b/sim/include/kurator/sim/Point.h @@ -0,0 +1,26 @@ +#pragma once + + +namespace kurator +{ +namespace battles +{ + + +struct Point +{ + double x; + double y; + double magnitude() const; + double distance(const Point& other) const; + double angle() const; + Point rotate(double angle) const; + Point scale(double _scale) const; + Point normalized() const; + Point operator-(const Point& other) const; + Point operator+(const Point& other) const; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/Scenario.h b/sim/include/kurator/sim/Scenario.h new file mode 100644 index 0000000..afbb74c --- /dev/null +++ b/sim/include/kurator/sim/Scenario.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "ShipConfig.h" + + +namespace kurator +{ +namespace battles +{ + + +struct Scenario +{ + std::string name; + std::vector ships; + int total_teams() const; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/ShipConfig.h b/sim/include/kurator/sim/ShipConfig.h new file mode 100644 index 0000000..2066430 --- /dev/null +++ b/sim/include/kurator/sim/ShipConfig.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + + +namespace kurator +{ +namespace battles +{ + + +struct ShipConfig +{ + int team; + std::string type; + std::vector turrets; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/components.h b/sim/include/kurator/sim/components.h new file mode 100644 index 0000000..d4363c4 --- /dev/null +++ b/sim/include/kurator/sim/components.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include "Point.h" + + +namespace kurator +{ +namespace battles +{ + + +struct Transform +{ + Point position; + double angle; + entt::entity reference_frame = entt::null; +}; + + +struct Team +{ + int id; +}; + + +struct AIState +{ + Point destination; + entt::entity target = entt::null; +}; + + +struct FloatingMovement +{ + double max_speed; + double acceleration; + double deceleration; + double destination_boundary; + Point speed = {0.0, 0.0}; +}; + + +struct HitPoints +{ + double health; +}; + + +struct TurretControl +{ + double reload; + entt::entity owner; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/events.h b/sim/include/kurator/sim/events.h new file mode 100644 index 0000000..0e4af14 --- /dev/null +++ b/sim/include/kurator/sim/events.h @@ -0,0 +1,21 @@ +#pragma once + +#include + + +namespace kurator +{ +namespace battles +{ + + +struct Hit +{ + double damage; + entt::entity source; + entt::entity victim; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/include/kurator/sim/scenarios.h b/sim/include/kurator/sim/scenarios.h new file mode 100644 index 0000000..3d7e697 --- /dev/null +++ b/sim/include/kurator/sim/scenarios.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Scenario.h" + + +namespace kurator +{ +namespace battles +{ +namespace scenarios +{ + + +Scenario example(); + + +} // namespace scenarios +} // namespace battles +} // namespace kurator diff --git a/sim/src/BaseBattle.cpp b/sim/src/BaseBattle.cpp new file mode 100644 index 0000000..33e0957 --- /dev/null +++ b/sim/src/BaseBattle.cpp @@ -0,0 +1,162 @@ +#include "BaseBattle.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "Builder.h" + + +namespace kurator +{ +namespace battles +{ + + +BaseBattle::BaseBattle(const Scenario& scenario) : + _registry {}, + spawner {scenario.total_teams(), 12000, 0.1} +{ + const auto repo = universe::load_sample(); + Builder build {_registry, spawner}; + for (const auto& ship : scenario.ships) { + const auto entity = build(repo->ship_type(ship.type), ship.team); + for (const auto& turret_type : ship.turrets) + build(repo->turret_type(turret_type), entity); + manager.add(ship.team, entity); // registry supports on construction events + } +} + + +entt::registry& +BaseBattle::registry() +{ + return _registry; +} + + +entt::dispatcher& +BaseBattle::dispatcher() +{ + return _dispatcher; +} + + +void +BaseBattle::update(const float dt) +{ + pick_random_targets(); + keep_at_range(); + floating_movement(dt); + turrets(dt); + kill_off_dead(); + manager.clear(_registry); // registry supports on destructions events +} + + +void +BaseBattle::pick_random_targets() +{ + auto view = _registry.view(); + for (auto&& [entity, team, ai] : view.each()) { + if (!_registry.valid(ai.target)) + ai.target = manager.random((team.id + 1) % 2); // FIXME + } +} + + +void +BaseBattle::keep_at_range() +{ + auto view = _registry.view(); + for (auto&& [entity, self, ai] : view.each()) { + if (!_registry.valid(ai.target)) + continue; + const auto target = _registry.get(ai.target); + const Point offset = target.position - self.position; + ai.destination = target.position - offset.normalized().scale(6000.0); + } +} + + +void +BaseBattle::floating_movement(const float dt) +{ + auto view = _registry.view(); + for (auto&& [entity, transform, movement, ai] : view.each()) { + const auto offset = ai.destination - transform.position; + const auto at_destination = offset.magnitude() > movement.destination_boundary; + const auto acceleration = + at_destination ? + offset.normalized().scale(movement.acceleration * dt) : + offset.normalized().scale(-1 * movement.deceleration * dt); + movement.speed.x += acceleration.x; + movement.speed.y += acceleration.y; + if (movement.speed.magnitude() > movement.max_speed) + movement.speed = movement.speed.normalized().scale(movement.max_speed); + const auto speed = movement.speed.scale(dt); + transform.position.x += speed.x; + transform.position.y += speed.y; + transform.angle = speed.angle(); + } +} + + +double +effective_damage(const universe::TurretType& def, const double distance) +{ + const auto overflow = distance - def.optimal_range; + const auto falloff = std::max(0.0, overflow / def.optimal_range / def.falloff_modifier); + return def.base_damage * std::round(std::pow(def.falloff_intensity, std::pow(falloff, 2)) * 1000) / 1000; +} + + +void +BaseBattle::turrets(const float dt) +{ + auto view = _registry.view(); + for (auto&& [entity, control, def] : view.each()) { + if (!_registry.valid(control.owner)) { + _registry.destroy(entity); + continue; + } + const auto& [state, transform] = _registry.get(control.owner); // no checks + if (!_registry.valid(state.target)) + continue; + if (control.reload > 0.0) + control.reload -= dt; + if (control.reload <= 0.0) { + auto& target_points = _registry.get(state.target); + const auto& target = _registry.get(state.target); + const auto distance = transform.position - target.position; + const auto damage = effective_damage(def, distance.magnitude()); + if (damage > 0.0) { + target_points.health -= damage; + _dispatcher.trigger(Hit{damage, entity, state.target}); + } + control.reload = def.rate_of_fire; + } + } +} + + +void +BaseBattle::kill_off_dead() +{ + auto view = _registry.view(); + for (auto&& [entity, points] : view.each()) { + if (points.health <= 0.0) + _registry.destroy(entity); + } +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/BaseBattle.h b/sim/src/BaseBattle.h new file mode 100644 index 0000000..8240fd2 --- /dev/null +++ b/sim/src/BaseBattle.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include +#include + +#include "RandomSpawner.h" +#include "TeamManager.h" + + +namespace kurator +{ +namespace battles +{ + + +class BaseBattle : public Battle +{ +public: + BaseBattle(const Scenario& scenario); + entt::registry& registry() override; + entt::dispatcher& dispatcher() override; + void update(float dt) override; +private: + entt::registry _registry; + entt::dispatcher _dispatcher; + RandomSpawner spawner; + TeamManager manager; + void pick_random_targets(); + void keep_at_range(); + void floating_movement(float dt); + void turrets(float dt); + void kill_off_dead(); +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Battle.cpp b/sim/src/Battle.cpp new file mode 100644 index 0000000..f44a88e --- /dev/null +++ b/sim/src/Battle.cpp @@ -0,0 +1,24 @@ +#include + +#include + +#include + +#include "BaseBattle.h" + + +namespace kurator +{ +namespace battles +{ + + +std::unique_ptr +prepare(const Scenario& scenario) +{ + return std::make_unique(scenario); +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Builder.cpp b/sim/src/Builder.cpp new file mode 100644 index 0000000..d7cc893 --- /dev/null +++ b/sim/src/Builder.cpp @@ -0,0 +1,57 @@ +#include "Builder.h" + +#include + +#include +#include +#include +#include + +#include "Spawner.h" + + +namespace kurator +{ +namespace battles +{ + + +Builder::Builder(entt::registry& _registry, Spawner& _spawner) : + registry {_registry}, + spawner {_spawner} +{ +} + + +entt::entity +Builder::operator()(const universe::ShipType& ship_type, const int team) const +{ + const auto entity = registry.create(); + registry.emplace(entity, ship_type); + registry.emplace(entity, team); + registry.emplace(entity, spawner.get(team)); + registry.emplace( + entity, + ship_type.max_speed, + ship_type.max_speed * 2.0, + ship_type.max_speed * 3.0, + 100.0); + registry.emplace(entity, Point{0.0, 0.0}); + registry.emplace(entity, ship_type.base_health_points); + return entity; +} + + +entt::entity +Builder::operator()(const universe::TurretType& turret_type, const entt::entity& owner) const +{ + const auto entity = registry.create(); + registry.emplace(entity, turret_type); + registry.emplace(entity, 0.0, owner); + registry.emplace(entity, Point{0.0, 0.0}, 0.0, owner); + return entity; +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Builder.h b/sim/src/Builder.h new file mode 100644 index 0000000..df50c4e --- /dev/null +++ b/sim/src/Builder.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include + +#include "Spawner.h" + + +namespace kurator +{ +namespace battles +{ + + +class Builder +{ +public: + Builder(entt::registry& _registry, Spawner& _spawner); + ~Builder() = default; + Builder(Builder&&) = delete; + Builder(const Builder&) = delete; + Builder& operator=(Builder&&) = delete; + Builder& operator=(const Builder&) = delete; + entt::entity operator()(const universe::ShipType& ship_type, int team) const; + entt::entity operator()(const universe::TurretType& turret_type, const entt::entity& owner) const; +private: + entt::registry& registry; + Spawner& spawner; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Point.cpp b/sim/src/Point.cpp new file mode 100644 index 0000000..31aecae --- /dev/null +++ b/sim/src/Point.cpp @@ -0,0 +1,72 @@ +#include + +#include + + +namespace kurator +{ +namespace battles +{ + + +double +Point::magnitude() const +{ + return std::sqrt(std::pow(x, 2) + std::pow(y, 2)); +} + + +double +Point::distance(const Point& other) const +{ + return std::sqrt(std::pow(other.x - x, 2) + std::pow(other.y - y, 2)); +} + + +double +Point::angle() const +{ + return std::atan2(y, x); // (+x, _) is 0 +} + + +Point +Point::rotate(const double angle) const +{ + return { + x * std::cos(angle) - y * std::sin(angle), + x * std::sin(angle) + y * std::cos(angle), + }; +} + + +Point +Point::scale(const double _scale) const +{ + return {x * _scale, y * _scale}; +} + + +Point +Point::normalized() const +{ + return scale(1.0 / magnitude()); +} + + +Point +Point::operator-(const Point& other) const +{ + return {x - other.x, y - other.y}; +} + + +Point +Point::operator+(const Point& other) const +{ + return {x + other.x, y + other.y}; +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/RandomSpawner.cpp b/sim/src/RandomSpawner.cpp new file mode 100644 index 0000000..b85d140 --- /dev/null +++ b/sim/src/RandomSpawner.cpp @@ -0,0 +1,42 @@ +#include "RandomSpawner.h" + +#include + +#include +#include + + +namespace kurator +{ +namespace battles +{ + + +RandomSpawner::RandomSpawner(const int total_teams, const double distance, const double variation) : + angle_step {2.0 * M_PI / total_teams}, + device {}, + distribution_d {distance - distance * variation, distance + distance * variation}, + distribution_a {-variation * M_PI, variation * M_PI} +{ +} + + +Transform +RandomSpawner::get(const int team) +{ + const double distance = distribution_d(device); + const double clean_angle = angle_step * team; + const double angle = clean_angle + distribution_a(device); + double facing = clean_angle + M_PI; + if (facing > 2 * M_PI) + facing -= 2 * M_PI; + const Point position { + distance * std::cos(angle), + distance * std::sin(angle), + }; + return {position, facing}; +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/RandomSpawner.h b/sim/src/RandomSpawner.h new file mode 100644 index 0000000..1ef2f20 --- /dev/null +++ b/sim/src/RandomSpawner.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "Spawner.h" + + +namespace kurator +{ +namespace battles +{ + + +class RandomSpawner : public Spawner +{ +public: + RandomSpawner(int total_teams, double distance, double variation); + Transform get(int team) override; +private: + const double angle_step; + std::random_device device; + std::uniform_real_distribution distribution_d; + std::uniform_real_distribution distribution_a; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Scenario.cpp b/sim/src/Scenario.cpp new file mode 100644 index 0000000..49a9c7c --- /dev/null +++ b/sim/src/Scenario.cpp @@ -0,0 +1,23 @@ +#include + + +namespace kurator +{ +namespace battles +{ + + +int +Scenario::total_teams() const +{ + int last_team = 0; + for (const auto& ship : ships) { + if (ship.team > last_team) + last_team = ship.team; + } + return last_team + 1; +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/Spawner.h b/sim/src/Spawner.h new file mode 100644 index 0000000..ebae699 --- /dev/null +++ b/sim/src/Spawner.h @@ -0,0 +1,21 @@ +#pragma once + +#include + + +namespace kurator +{ +namespace battles +{ + + +class Spawner +{ +public: + virtual ~Spawner() = default; + virtual Transform get(int team) = 0; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/TeamManager.cpp b/sim/src/TeamManager.cpp new file mode 100644 index 0000000..9dc90e3 --- /dev/null +++ b/sim/src/TeamManager.cpp @@ -0,0 +1,62 @@ +#include "TeamManager.h" + +#include +#include +#include +#include + +#include + + +namespace kurator +{ +namespace battles +{ + + +TeamManager::TeamManager() : + teams {}, + generator {std::random_device{}()} +{ +} + + +void +TeamManager::add(int team, entt::entity entity) +{ + for (int i = teams.size(); i < team + 1; ++i) + teams.emplace_back(); + teams.at(team).push_back(std::move(entity)); +} + + +TeamManager::Team +TeamManager::get(int team) const +{ + return teams.at(team); +} + + +entt::entity +TeamManager::random(int team) +{ + auto& members = teams.at(team); + if (members.size() == 0) + return entt::null; + std::uniform_int_distribution uniform{0, members.size() - 1}; + return members.at(uniform(generator)); +} + + +void +TeamManager::clear(entt::registry& registry) +{ + for (auto& members : teams) { + auto is_valid = [®istry](entt::entity entity){ return !registry.valid(entity); }; + members.erase(std::remove_if(members.begin(), members.end(), is_valid), members.end()); + } +} + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/TeamManager.h b/sim/src/TeamManager.h new file mode 100644 index 0000000..384d9ee --- /dev/null +++ b/sim/src/TeamManager.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include + + +namespace kurator +{ +namespace battles +{ + + +class TeamManager +{ +public: + using Team = std::vector; + TeamManager(); + void add(int team, entt::entity entity); + Team get(int team) const; + entt::entity random(int team); + void clear(entt::registry& registry); +private: + std::vector teams; + std::mt19937 generator; +}; + + +} // namespace battles +} // namespace kurator diff --git a/sim/src/scenarios.cpp b/sim/src/scenarios.cpp new file mode 100644 index 0000000..34b4a24 --- /dev/null +++ b/sim/src/scenarios.cpp @@ -0,0 +1,43 @@ +#include + +#include + + +namespace kurator +{ +namespace battles +{ +namespace scenarios +{ + + +Scenario +example() +{ + return { + "Example", + { + {0, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {0, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {0, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {0, "Warbringer", {"ChargeLaser"}}, + {0, "Warbringer", {"ChargeLaser"}}, + {0, "Eclipse", {"ChargeLaser"}}, + {0, "Eclipse", {"ChargeLaser"}}, + {0, "Eclipse", {"ChargeLaser"}}, + {1, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {1, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {1, "Anvil", {"ChargeLaser", "ChargeLaser"}}, + {1, "Warbringer", {"ChargeLaser"}}, + {1, "Warbringer", {"ChargeLaser"}}, + {1, "Eclipse", {"ChargeLaser"}}, + {1, "Eclipse", {"ChargeLaser"}}, + {1, "Eclipse", {"ChargeLaser"}}, + }, + }; +} + + +} // namespace scenarios +} // namespace battles +} // namespace kurator -- cgit v1.1