diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d3ea2fa..6bfbc0a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,7 +1,12 @@ cmake_minimum_required(VERSION 3.27) set(ISTD_CORE_SRC + src/devices/vehicle.cpp + src/device.cpp src/room.cpp + src/system.cpp + src/unit.cpp + src/world.cpp ) add_library(istd_core STATIC ${ISTD_CORE_SRC}) diff --git a/core/include/istd_core/device.h b/core/include/istd_core/device.h index c8371cd..c7f4e87 100644 --- a/core/include/istd_core/device.h +++ b/core/include/istd_core/device.h @@ -30,13 +30,14 @@ public: struct DevicePrototype { std::string_view name; - RegSetStrategy *reg_set_strategy; // life cycle: static + const RegSetStrategy *reg_set_strategy; // life cycle: static std::uint32_t mass; - ItemPort input_n, output_n; // number of input/output ports + ItemType item; // item type this device is built from + ItemPort input_n, output_n; // number of input/output ports }; struct DevicePrototypeComponent { - DevicePrototype *prototype; // life cycle: static + const DevicePrototype *prototype; // life cycle: static }; struct DeviceIdComponent { @@ -49,18 +50,27 @@ struct DeviceBuilder { World &world, entt::entity unit, DeviceId device_id ) const = 0; + + // No virtual destructor: static lifetime and no member variables is the + // intended use case }; -struct DeviceBuilderRegistry { - static DeviceBuilderRegistry &instance(); +class DeviceBuilderRegistry { +public: + static DeviceBuilderRegistry &instance() noexcept; + + void register_builder(ItemType item, const DeviceBuilder *builder); - void register_builder(Item item, DeviceBuilder *builder); entt::entity build( - World &world, Item item, entt::entity unit, DeviceId device_id + World &world, ItemType item, entt::entity unit, DeviceId device_id ) const; + struct Registar { + Registar(ItemType item, const DeviceBuilder *builder); + }; + private: - SmallMap builders_; + SmallMap builders_; }; } // namespace istd diff --git a/core/include/istd_core/devices/vehicle.h b/core/include/istd_core/devices/vehicle.h new file mode 100644 index 0000000..607549c --- /dev/null +++ b/core/include/istd_core/devices/vehicle.h @@ -0,0 +1,23 @@ +#ifndef ISTD_CORE_DEVICE_VIHICLE_H +#define ISTD_CORE_DEVICE_VIHICLE_H + +#include +#include + +namespace istd { + +struct VehiclePrototype { + std::string_view name; + std::uint32_t ideal_working_mass; + float max_speed; // in tile/tick +}; + +struct VehicleComponent { + const VehiclePrototype *prototype; // life cycle: static + float heading; // in radians, 0 is South, counter-clockwise + float speed; // percentage of max speed, 0.0 to 1.0 +}; + +} // namespace istd + +#endif diff --git a/core/include/istd_core/item.h b/core/include/istd_core/item.h index 3613fbd..c28a4d3 100644 --- a/core/include/istd_core/item.h +++ b/core/include/istd_core/item.h @@ -16,7 +16,7 @@ consteval std::uint32_t id_string(const char *str) { return id; } -enum class Item : std::uint32_t { +enum class ItemType : std::uint32_t { Null = 0, // Materials diff --git a/core/include/istd_core/unit.h b/core/include/istd_core/unit.h index 7d090da..0bdb3f9 100644 --- a/core/include/istd_core/unit.h +++ b/core/include/istd_core/unit.h @@ -35,6 +35,16 @@ struct DeviceStackComponent { std::vector devices; }; +/** + * @brief Flag component to indicate that the unit is on the ground. + */ +struct OnGroundFlag {}; + +/** + * @brief Flag component to indicate that the unit is in the air. + */ +struct AirborneFlag {}; + } // namespace istd #endif diff --git a/core/src/device.cpp b/core/src/device.cpp index 6e1c5cb..9665d17 100644 --- a/core/src/device.cpp +++ b/core/src/device.cpp @@ -15,22 +15,30 @@ bool RegSetStrategy::write( return false; } -DeviceBuilderRegistry &DeviceBuilderRegistry::instance() { +DeviceBuilderRegistry &DeviceBuilderRegistry::instance() noexcept { static DeviceBuilderRegistry registry; return registry; } void DeviceBuilderRegistry::register_builder( - Item item, DeviceBuilder *builder + ItemType item, const DeviceBuilder *builder ) { builders_.insert(item, builder); } entt::entity DeviceBuilderRegistry::build( - World &world, Item item, entt::entity unit, DeviceId device_id + World &world, ItemType item, entt::entity unit, DeviceId device_id ) const { auto builder = builders_[item]; - return builder->build(world, unit, device_id); + auto entity = builder->build(world, unit, device_id); + world.registry.emplace(entity, unit, device_id); + return entity; +} + +DeviceBuilderRegistry::Registar::Registar( + ItemType item, const DeviceBuilder *builder +) { + DeviceBuilderRegistry::instance().register_builder(item, builder); } } // namespace istd diff --git a/core/src/devices/vehicle.cpp b/core/src/devices/vehicle.cpp new file mode 100644 index 0000000..4dd6be5 --- /dev/null +++ b/core/src/devices/vehicle.cpp @@ -0,0 +1,159 @@ +#include "istd_core/devices/vehicle.h" +#include "istd_core/device.h" +#include "istd_core/system.h" +#include "istd_core/unit.h" + +namespace istd { +namespace { + +// Vehicle register allocations: +// 0: Vehicle speed (1 = 1 / 256 of max speed) +// 1: Vehicle heading (1 = 1 / 256 pi) +// 2: Vehicle x position (1 = 1 / 1024 tile) (read-only) +// 3: Vehicle y position (1 = 1 / 1024 tile) (read-only) +// 4: Device status / error code + +struct VehicleRegSetStrategy : public RegSetStrategy { + virtual bool read( + World &world, entt::entity entity, std::uint8_t reg_id, + std::uint32_t &value + ) const noexcept override { + auto ® = world.registry; + switch (reg_id) { + case 0: { // Vehicle speed + auto &vehicle = reg.get(entity); + value = static_cast(vehicle.speed * 256.0f); + break; + } + + case 1: { // Vehicle heading + auto &vehicle = reg.get(entity); + value = static_cast( + (vehicle.heading / (2.0f * M_PI)) * 256.0f + ); + break; + } + + case 2: { // Vehicle x position + auto owner = reg.get(entity).unit; + auto pos = reg.get(owner).position; + value = static_cast(pos.x * 1024); + break; + } + + case 3: { // Vehicle y position + auto owner = reg.get(entity).unit; + auto pos = reg.get(owner).position; + value = static_cast(pos.y * 1024); + break; + } + + case 4: + // No error code for now, just return 0 + value = 0; + break; + + default: + return false; // Invalid register ID + } + return true; // Read successful + } + + virtual bool write( + World &world, entt::entity entity, std::uint8_t reg_id, + std::uint32_t value + ) const noexcept override { + switch (reg_id) { + case 0: { // Vehicle speed + auto &vehicle = world.registry.get(entity); + vehicle.speed = static_cast(value) / 256.0f; + return true; + } + case 1: { // Vehicle heading + auto &vehicle = world.registry.get(entity); + vehicle.heading = (static_cast(value) / 256.0f) + * (2.0f * M_PI); + return true; + } + default: + return false; // Invalid register ID or read-only register + } + } +}; + +static const VehicleRegSetStrategy vehicle_reg_set_strategy; + +static const VehiclePrototype basic_vihicle_prototype = { + .name = "Basic Vehicle", + .ideal_working_mass = 7440, + .max_speed = 0.3f, // 1 tile per tick +}; + +static const DevicePrototype basic_vehicle_device_prototype = { + .name = "Basic Vehicle Device", + .reg_set_strategy = &vehicle_reg_set_strategy, + .mass = 3270, + .item = ItemType::BasicVehicleChassis, + .input_n = 0, + .output_n = 0, +}; + +struct BasicVehicleBuilder : public DeviceBuilder { + entt::entity build( + World &world, entt::entity unit, DeviceId device_id + ) const override { + auto entity = world.registry.create(); + + world.registry.emplace( + entity, &basic_vehicle_device_prototype + ); + + world.registry.emplace( + entity, &basic_vihicle_prototype, 0.0f, 0.0f + ); + + return entity; + } +}; + +static const BasicVehicleBuilder basic_vehicle_builder; +static const DeviceBuilderRegistry::Registar basic_vehicle_registrar( + ItemType::BasicVehicleChassis, &basic_vehicle_builder +); + +struct VehicleVelocitySystem : public System { + void tick(World &world) const noexcept override { + auto ® = world.registry; + reg.view().each( + [®]( + entt::entity entity, const VehicleComponent &vehicle, + const DeviceIdComponent &device_id + ) { + // Update vehicle position based on speed and heading + auto owner = device_id.unit; + if (!reg.all_of(owner)) { + // If not on ground, do not update position + return; + } + + auto &kinematics = reg.get(owner); + kinematics.velocity += Vec2::rotated( + vehicle.heading, vehicle.speed * vehicle.prototype->max_speed + ); + } + ); + } + + std::string_view name() const noexcept override { + return "Vehicle Device System"; + } +}; + +static const VehicleVelocitySystem vehicle_velocity_system; +static const SystemRegistry::Registar vehicle_velocity_registrar( + System::Precedence::DeviceAccumulateVelocity, &vehicle_velocity_system +); + +} // namespace + +} // namespace istd \ No newline at end of file diff --git a/core/src/unit.cpp b/core/src/unit.cpp new file mode 100644 index 0000000..f0af2d4 --- /dev/null +++ b/core/src/unit.cpp @@ -0,0 +1,74 @@ +#include "istd_core/unit.h" +#include "istd_core/system.h" +#include "istd_util/tile_geometry.h" +#include "tilemap/tile.h" + +namespace istd { + +namespace { + +struct ResetVelocitySystem : public System { + void tick(World &world) const noexcept override { + auto ® = world.registry; + reg.view().each( + [](entt::entity, KinematicsComponent &kinematics) { + kinematics.velocity = Vec2::zero(); + } + ); + } + + std::string_view name() const noexcept override { + return "Reset Velocity System"; + } +}; + +static const ResetVelocitySystem reset_velocity_system; +static const SystemRegistry::Registar reset_velocity_registrar( + System::Precedence::ResetVelocity, &reset_velocity_system +); + +bool is_passible_tile(Tile tile) { + return tile.base != BaseTileType::Mountain; +} + +void update_pos(const TileMap &tilemap, KinematicsComponent &kinematics) { + auto next_pos = kinematics.position + kinematics.velocity; + auto ray_cast = tiles_on_segment(kinematics.position, next_pos); + for (auto [i, j] : ray_cast) { + auto tile = tilemap.get_tile(TilePos::from_global(i, j)); + if (!is_passible_tile(tile)) { + // Hit an impassable tile + // Calculate intersection point + auto inter = tile_segment_intersection( + kinematics.position, next_pos, {i, j} + ); + next_pos = inter; + break; + } + } + kinematics.position = next_pos; +} + +struct KinematicsSystem : public System { + void tick(World &world) const noexcept override { + auto ® = world.registry; + reg.view().each( + std::bind( + update_pos, std::ref(world.tilemap), std::placeholders::_1 + ) + ); + } + + std::string_view name() const noexcept override { + return "Kinematics System"; + } +}; + +static const KinematicsSystem kinematics_system; +static const SystemRegistry::Registar kinematics_registrar( + System::Precedence::UpdateKinematics, &kinematics_system +); + +} // namespace + +} // namespace istd \ No newline at end of file