#ifndef TACTICAL_SHOOTER_HIT_DETECTION_H #define TACTICAL_SHOOTER_HIT_DETECTION_H #include "entity.h" #include #include namespace tactical_shooter { /** * Hit detection system that operates on the simulation entities directly. * * Because the simulation core runs in GDExtension (not GDScript), we avoid * crossing the script→engine boundary for every raycast during the hot loop. * Instead, this component provides simplified geometric checks that can be * resolved locally, or queues world-space queries for the Godot PhysicsServer3D * if precise scene geometry is needed. * * Phase 1 uses sphere/box overlap against entity positions — fast, no physics * dependency. Phase 2+ may add proper PhysicsServer3D raycasts against the * world geometry. */ /** * Result of a single hit query. */ struct HitResult { bool hit = false; uint16_t entity_id = 0xFFFF; // 0xFFFF = world geometry / no entity float damage = 0.0f; float distance = 0.0f; godot::Vector3 point; godot::Vector3 normal; uint8_t hitbox_id = 0; // 0=body, 1=head, 2=arms, 3=legs }; /** * Weapon damage profile. */ struct WeaponDamage { float base_damage = 30.0f; float head_multiplier = 4.0f; float body_multiplier = 1.0f; float arm_multiplier = 0.75f; float leg_multiplier = 0.6f; float max_range = 500.0f; // units float spread_degrees = 0.5f; // random spread per shot }; class HitDetection { public: HitDetection(); /** * Set which entities are currently in play (reference list). * Must be called before any hit queries each frame. */ void set_entities(const std::vector &entities); /** * Raycast against entity bounding spheres. * No PhysicsServer3D dependency — pure geometric check. * * @param origin Ray origin * @param direction Normalized ray direction * @param max_distance Maximum trace distance * @param exclude_id Entity ID to exclude (shooter) * @return First entity hit (if any) */ HitResult raycast_entity( const godot::Vector3 &origin, const godot::Vector3 &direction, float max_distance, uint16_t exclude_id = 0xFFFF ) const; /** * Process a weapon fire: apply damage to hit entity. * Combines raycast + damage application + amortization. * * @param origin Fire origin * @param direction Fire direction (with spread already applied) * @param shooter_id Entity that fired * @param weapon Weapon damage profile * @return Hit result with damage */ HitResult process_shot( const godot::Vector3 &origin, const godot::Vector3 &direction, uint16_t shooter_id, const WeaponDamage &weapon ); /** * Apply damage to an entity and update its state. * Respects armor reduction. * * @param entity Target entity * @param damage Raw damage before armor * @param mult Hitbox multiplier * @return Actual health removed */ static float apply_damage(Entity &entity, float damage, float mult); /** * Sphere overlap detection — find all entities within radius. * Useful for explosive damage. */ std::vector sphere_overlap( const godot::Vector3 ¢er, float radius, uint16_t exclude_id = 0xFFFF ) const; private: /** * Get the hitbox multiplier for a hit location. */ float get_hitbox_multiplier(uint8_t hitbox_id, const WeaponDamage &weapon) const; /** * Determine which hitbox a ray hit based on local-space intersection. * Simple head/body/leg classification by vertical offset. */ uint8_t classify_hitbox(const Entity &entity, const godot::Vector3 &hit_point) const; const std::vector *entities_ = nullptr; // Bounding sphere radius for entity hit detection static constexpr float kEntityRadius = 0.4f; // ~arm span / 2 static constexpr float kHeadRadius = 0.2f; static constexpr float kEntityHeight = 1.8f; // standing height }; } // namespace tactical_shooter #endif // TACTICAL_SHOOTER_HIT_DETECTION_H