#pragma once // Flying projectiles of the player's wand: a shot (gun/gun.h) becomes projectile entities that move, collide with the world // and creatures, bounce, time out, fire their triggers and explode. Simulated on the CPU against a callback view of the world. #include #include #include #include #include #include #include #include "entity/entity.h" #include "gun/effects.h" #include "gun/gun.h" namespace ng { // Values read from a projectile entity file (per-second values converted to cells per frame). struct ProjDef { float speed_min = 10, speed_max = 10; // cells per frame float damage = 0.1f; // hp units (x25 on screen): the projectile channel (damage + damage_by_type.projectile) float dmg_type[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // damage_by_type: melee, electricity, fire, ice, slice, drill, radioactive, poison, healing, curse, holy, explosion (each hurts through its own multiplier of the victim) int lifetime = 60; float gravity = 0.05f; // cells per frame^2 (VelocityComponent gravity_y / 3600) float air_friction = 0; // fraction of the speed lost per second bool on_collision_die = true; int bounces_left = 0; // ProjectileComponent bounces_left: grenades bounce this often before they explode bool friendly_fire = false; // ProjectileComponent.friendly_fire: hurts the herd of the shooter too bool orbiting = false; // orbit_projectile: attached hazards can hit their caster after launch grace bool dont_hit_player = false; // dont_hit_player: never hurts the player, whatever the herds int collide_with_shooter_frames = 0; // the shooter itself is not hit during the first frames // HitEffectComponent (of the projectile and of its extra entities): what a hit does to the victim struct HitEffect { std::string effect, value_string, condition_effect, condition_status; float value = 0; }; std::vector hit_effects; std::string file; // the entity file it was built from std::string extras; // the shot's extra entities (+ "|ge=" game effects) applied on top of it bool needs_twin = false; int gpu_lua_every = 0; // reviewed stateless script; automatic GPU cadence when no host components remain bool query_projectile = false, query_velocity = false, query_unsupported = false; bool collide_with_world = true, collide_with_entities = true, penetrate_entities = false; // ProjectileComponent int damage_every_x_frames = 2; // a penetrating projectile hurts an entity it overlaps every so many frames (ProjectileComponent, default 2) // the projectile has components the GPU does not simulate (CellEater, MagicConvertMaterial, scripts, area effects...): the host keeps a runtime entity (twin) for it struct Homing { // HomingComponent (constructor 00a0b1d0, update 00b323f0) bool on = false; float detect = 150, mult = 0.9f, coeff = 160, max_turn = 0.05f; // detect_distance, homing_velocity_multiplier, homing_targeting_coeff, max_turn_rate bool rotate = false, who_shot = false; // just_rotate_towards_target, target_who_shot int target = 0; // predefined_target (an entity id; scripts set it on the twin) int target_enc = 0; // its GPU slot: entity slot + 1, or -(projectile slot + 1), 0 none (set by the host when it retypes the twin) int tag_bits = 2048; // target_tag (default "homing_target") as the entity tag bit: 512 mortal, 1024 prey, 2048 homing_target, 4096 player_unit } homing; bool particle = false; // an entity without ProjectileComponent that only moves and emits (the entities an explosion loads): no damage, no impact sparks float die_speed = 0; // DieIfSpeedBelowComponent min_speed (cells per frame) std::string load_files; // config_explosion.load_this_entity (comma separated entity files): the host spawns them as real entities when the explosion happens bool invisible = false; // an entity with no ProjectileComponent and no sprite (the gold pickup effect: only emitters): nothing is drawn for its body bool die_on_liquid = false; // die_on_liquid_collision float liquid_drag = 1.0f; // VelocityComponent liquid_drag (constructor default 1.0): the share of the speed lost per liquid cell around, per second bool displace_liquid = true; // ProjectileComponent displace_liquid: the liquid around is pushed aside (the constructor default is not recovered, the explicit values of the data are honoured) bool physics = false; // a thrown physics body (bombs): bounces and rolls; fuse expiry or damage can detonate it bool creature = false; // a cast creature (SUMMON_WANDGHOST's wand_ghost_player.xml: no ProjectileComponent, an AI): the host spawns it as a whole entity, the GPU projectile only carries it out of the wand float break_speed = 0; // a breakable thrown body (SUMMON_EGG's egg_monster.xml: PhysicsBodyCollisionDamage + DamageModel + ExplodeOnDamage): an impact faster than this (px/s) kills it and it explodes with the ExplodeOnDamage config bool explode_on_death = false; float explosion_radius = 0, explosion_damage = 0, max_durability = 8, ray_energy = 20000; bool stains = true; float stains_radius = 0; // config_explosion stains_enabled (default on, 004b2780 +0xe8), stains_radius (+0xec, 0 = 0.4 x the radius): the burnt ring // ConfigExplosion constructor 004b2780: ray_energy 20000, max_durability_to_destroy 10, knockback_force 1, damage_mortals on float knockback_force = 1.0f; // ConfigExplosion.knockback_force bool damage_mortals = true; bool dont_damage_shooter = false; // ProjectileComponent explosion_dont_damage_shooter (+38a, default false): only then the explosion ignores the shooter (00c0a930) bool not_scaled_by_gamefx = false; // ConfigExplosion not_scaled_by_gamefx (+154): the perks' Shot-time scaling leaves the explosion alone float hit_knockback_force = 0.0f; // ProjectileComponent knockback_force (+5d0, default 0): a hit knocks back by knockback_force x speed x the projectile's VelocityComponent mass (00c091f0) float ragdoll_explosion_mult = 1.0f; // config_explosion physics_multiplier_ragdoll_force (ConfigExplosion +0x108, default 1): scales the impulse an explosion gives the entities int ragdoll_fx = 1; // ProjectileComponent ragdoll_fx_on_collision (+53c): RAGDOLL_FX NONE 0, NORMAL 1, BLOOD_EXPLOSION 2, BLOOD_SPRAY 3, FROZEN 4, CONVERT_TO_MATERIAL 5, CUSTOM_RAGDOLL_ENTITY 6, DISINTEGRATED 7, NO_RAGDOLL_FILE 8, PLAYER_RAGDOLL_CAMERA 9 float blood_count_multiplier = 1.0f; // ProjectileComponent +0x5dc float ragdoll_force_multiplier = 0.025f; // ProjectileComponent (+5d4, default 0.025): scales the force vector of the hit float velocity_mass = 0.05f; // VelocityComponent mass (+50, default 0.05) float physics_min = 0, physics_max = 0.2f; // config_explosion.physics_explosion_power.min / .max: the force on rigid bodies (00657960) std::string explosion_material; // config_explosion.create_cell_material (e.g. fire) bool explosion_audio = true; // config_explosion.audio_enabled (constructor 004b2780: on) std::string explosion_audio_event; // config_explosion.audio_event_name (empty: explosion / explosion_small by the radius, 00657960) float explosion_audio_liquid = 0; // config_explosion.audio_liquid_amount_normalized float explosion_cell_probability = 0; // percent of the cells in the blast that become that material bool hole = false; // the explosion digs float spread_rad = 0; // direction_random_rad uint32_t color = 0xFFFFD040u; std::vector sprite; // all animation frames one after the other, ARGB int sprite_w = 0, sprite_h = 0, sprite_frames = 1, sprite_wait = 6; ProjFx fx; // emitters, light, muzzle flash, explosion visuals // ProjectileComponent world collision (00c073b0; defaults from component_documentation.txt, offsets in analysis/projcomp/projectile_fields.txt) bool penetrate_world = false; // +504: moves through the world, slowed to penetrate_world_velocity_coeff of the step inside it float penetrate_world_velocity_coeff = 0.6f; // +508 float ground_penetration_coeff = 0; // +338: with VelocityComponent mass, the kinetic energy spent on the cells it digs through (00c08a30) int ground_penetration_max_durability = 0; // +33c: cells of a material more durable than this stop it (0: no limit) float bounce_energy = 0.5f; // +304 bool bounce_always = false, bounce_at_any_angle = false; // +308, +309 bool on_collision_remove_projectile = false; // +50e: a world hit leaves the entity without its ProjectileComponent (it stays where it stuck) bool die_on_low_velocity = false; // +369 float die_on_low_velocity_limit = 50; // +36c, pixels per second float physics_impulse_coeff = 300; // +52c: the push on a physics body it hits (impulse = coeff x velocity) bool damage_scaled_by_speed = false; // +534 float damage_scale_max_speed = 0; // +538 int lifetime_randomness = 0; // +50: lifetime + random(-r, r) std::string spawn_entity; // +510: on_collision_spawn_entity (+50f, default 1): this is loaded (or shot, spawn_entity_is_projectile +528) where it dies of a hit (00c08b90) bool spawn_entity_is_projectile = false; std::string bounce_fx_file; // +30c: loaded where the projectile bounces (00c073b0, at most 10 a frame); Explosive bounce sets it int projectile_type = 0; // +48: PROJECTILE 0, LIGHTNING 1 (00c0d290: the aim x 5400 px/s), VERLET 2, MATERIAL_PARTICLE 3 struct Verlet { // projectile_type VERLET (the tentacles): the chain of the child entity's VerletPhysicsComponent and the root's VerletWeaponComponent (proj_step.comp verlet_step) bool on = false; int points = 16, fade = 10; float rest = 4, stiff = 1, damp = 0.99f, liquid_damp = 0.7f; bool cells = true; float dmg_radius = 5, dmg_min = 0.01f, dmg_max = 1, dmg_coeff = 1, impulse = 1, force_radius = 3, phys_impulse = 1; // (physics_force_radius / physics_impulse_coeff: the push on the bodies) struct Part { std::vector tex; int w = 0, h = 0; float ox = 0, oy = 0; }; std::vector parts; // the sprites of the points, in order (each child Base file's SpriteComponent) } verlet; std::string death_particle; // on_death_emit_particle (+362): the material emitted on death, on_death_emit_particle_count (+364) of them int death_particle_count = 0; // VelocityComponent (00c370c0) float gravity_x = 0; // cells per frame^2 float terminal_velocity = 1000; // pixels per second, applied when apply_terminal_velocity (default on) bool apply_terminal_velocity = true; }; struct SimProjectile { double x = 0, y = 0, vx = 0, vy = 0; int age = 0, life = 60; float damage = 0.1f; double gravity = 0, friction = 0; int bounces = 0; const ProjDef* def = nullptr; float explosion_radius = 0, explosion_damage = 0; std::shared_ptr owner; // keeps the shot tree (triggers) alive const ShotProjectile* spec = nullptr; std::vector fired; // per trigger: already fired bool alive = true; }; class ProjectileSim { public: ProjectileSim(const Wak& wak, EntityDb& db) : wak_(wak), db_(db) {} struct Hooks { std::function solid; // cell blocks projectiles std::function hit_creature; // direct hit: true if a creature was hit std::function area_damage; // explosion damage and knockback to creatures (00657960) std::function dig; // explosion digs a hole std::function spawn_material; // explosion fills air with a material }; // Casts a shot (or a trigger's sub-shot) from a point along a direction. void cast(const std::shared_ptr& owner, const Shot& shot, double x, double y, double dirx, double diry, double child_mult = 1.0); void step(const Hooks& h); const ProjDef* def(const std::string& file); // A definition built from an entity description that is not a file (the runtime copy of a projectile after the scripts changed it), stored under `key`. const ProjDef* def_from(const std::string& key, const EntityDef& entity, const std::string& extras = ""); // + the extra entities of the shot (emitters, light, hit effects, homing) // Stable identity for a script's projectile definition; host scheduling state is per instance. static std::string script_definition_key(const EntityDef& entity); EntityDb& db() { return db_; } // The projectile with the extra entities a shot carries (c.extra_entities, comma separated): their particle emitters and light are added. const ProjDef* def(const std::string& file, const std::string& extras); void apply_extras(ProjDef& d, const std::string& extras); // 00c06460: a cast that adds explosion_radius (c.explosion_radius > 0, 00c0b440) sets the explosion up for its new radius by size class - the least damage // (13 from 32, 15 from 64, 20 from 128), the sprite, the sparks, ray_energy, camera_shake, stains_radius 1.5 x R, create_cell_probability at least 5 from 10. // The variant keeps the file's explosion_radius (the shot adds its own) and is made so that its damage + add_damage is the engine's value. const ProjDef* explosion_variant(const ProjDef* base, float add_radius, float add_damage); std::vector projectiles; EffectLib* fxlib = nullptr; // optional: loads emitters etc. from the projectile files bool gpu_lua_enabled = false; // set before building definitions, only after native startup compilation succeeds void disable_gpu_lua(); private: void kill(SimProjectile& p, const Hooks& h, bool hit_world); // the triggers as proj_step.comp fires them (mode 1 countdown, 2 world hit, 3 creature impact, 4 lifetime end, 5 removal); dx/dy: the direction to cast in void fire_triggers(SimProjectile& p, int mode, double dx, double dy); void fire_trigger(SimProjectile& p, int index, double dx, double dy, bool keep = false, bool world_hit = false); const Wak& wak_; EntityDb& db_; std::map defs_; std::map, ProjDef> xvars_; // (explosion_variant: map nodes keep their addresses) std::mt19937 rng_{12345}; std::vector> deferred_; }; } // namespace ng