Conversation
fire shots in in a round circle with small randomization
There was a problem hiding this comment.
Pull request overview
This PR reworks the ShFL_Hail firing logic so that hailstorm shots are emitted in concentric rings around the firing direction with small randomization, rather than simple random spread around a point.
Changes:
- Replaces the previous
ShFL_Hailloop with ring-based geometry: computing ring count, shots per ring, and per-shot azimuth/polar deviations aroundangle_xy. - Offsets the starting position forward along the firing direction before spawning each projectile, to create a circular “hail” pattern in front of the caster.
- Adds detailed
JUSTLOGdebugging for hailstorm setup and each projectile (angles, velocities, and creation status).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| long ring_count = effect_amount / 6; // Dynamically calculate the number of rings (minimum 1 ring) | ||
| if (ring_count < 1) ring_count = 1; | ||
| long shots_per_ring = effect_amount / ring_count; // Number of projectiles per ring | ||
|
|
||
| shot_set_start_pos(firing, shotst, &pos1); | ||
|
|
||
| // Debugging: Log basic values | ||
| JUSTLOG("EffectAmount: %ld, RingCount: %ld, ShotsPerRing: %ld", | ||
| effect_amount, ring_count, shots_per_ring); | ||
|
|
||
| // Calculate offset in the firing direction | ||
| struct ComponentVector start_offset; | ||
| long forward_distance = 256; // Distance in the firing direction | ||
| angles_to_vector(angle_xy, 0, forward_distance, &start_offset); | ||
|
|
||
| // Adjust start position by adding the calculated offset | ||
| struct Coord3d offset_pos = pos1; | ||
| offset_pos.x.val += start_offset.x; | ||
| offset_pos.y.val += start_offset.y; | ||
| offset_pos.z.val += start_offset.z; | ||
|
|
||
| for (i = 1; i <= ring_count; i++) // Loop through the rings | ||
| { | ||
| long i; | ||
| shot_set_start_pos(firing, shotst, &pos1); | ||
| for (i = 0; i < shotst->effect_amount; i++) | ||
| long current_radius = (max_radius * i) / ring_count; // Radius of the current ring | ||
| JUSTLOG("Ring %ld: Current Radius: %ld", i, current_radius); | ||
|
|
||
| long azimuth_offset = 1024 / (2 * shots_per_ring); | ||
| // Add rotation for every second ring for more randomness |
There was a problem hiding this comment.
shots_per_ring can become zero when effect_amount is zero (since ring_count is forced to at least 1), but it is then used as a divisor when computing azimuth_offset (1024 / (2 * shots_per_ring)), which will cause a division-by-zero crash if a Hail shot is ever configured with EffectAmount = 0. Consider guarding against effect_amount <= 0 up front (e.g. early-return or forcing at least one shot) or ensuring shots_per_ring is clamped to at least 1 before it is used in any division.
| // Debugging: Log basic values | ||
| JUSTLOG("EffectAmount: %ld, RingCount: %ld, ShotsPerRing: %ld", | ||
| effect_amount, ring_count, shots_per_ring); | ||
|
|
||
| // Calculate offset in the firing direction | ||
| struct ComponentVector start_offset; | ||
| long forward_distance = 256; // Distance in the firing direction | ||
| angles_to_vector(angle_xy, 0, forward_distance, &start_offset); | ||
|
|
||
| // Adjust start position by adding the calculated offset | ||
| struct Coord3d offset_pos = pos1; | ||
| offset_pos.x.val += start_offset.x; | ||
| offset_pos.y.val += start_offset.y; | ||
| offset_pos.z.val += start_offset.z; | ||
|
|
||
| for (i = 1; i <= ring_count; i++) // Loop through the rings | ||
| { | ||
| long i; | ||
| shot_set_start_pos(firing, shotst, &pos1); | ||
| for (i = 0; i < shotst->effect_amount; i++) | ||
| long current_radius = (max_radius * i) / ring_count; // Radius of the current ring | ||
| JUSTLOG("Ring %ld: Current Radius: %ld", i, current_radius); | ||
|
|
||
| long azimuth_offset = 1024 / (2 * shots_per_ring); | ||
| // Add rotation for every second ring for more randomness | ||
| long ring_rotation = (i % 2 == 0) ? (azimuth_offset) : 0; | ||
|
|
||
| for (j = 0; j < shots_per_ring; j++) // Loop through the angles | ||
| { | ||
| if (shotst->speed_deviation) | ||
| { | ||
| speed = (short)(shotst->speed - (shotst->speed_deviation/2) + (CREATURE_RANDOM(firing, shotst->speed_deviation))); | ||
| // Calculate the azimuth angle with optional rotation for the ring | ||
| long azimuth_angle = ((1024 * j) / shots_per_ring) + ring_rotation; | ||
| azimuth_angle %= 1024; // Ensure the angle stays within 0–1024 | ||
|
|
||
| long max_deviation = current_radius / 10; // Maximum deviation for spread | ||
|
|
||
| // Symmetrical polar angle calculation | ||
| long adjusted_angle_xy = angle_xy + ((LbCosL(azimuth_angle) * max_deviation) >> 16); | ||
| long adjusted_angle_z = ((LbSinL(azimuth_angle) * max_deviation) >> 16); | ||
|
|
||
| // Add random deviation | ||
| long random_azimuth_deviation = CREATURE_RANDOM(firing, 20) - 10; // Range: -10 to +10 | ||
| long random_polar_deviation = CREATURE_RANDOM(firing, 20) - 10; // Range: -10 to +10 | ||
|
|
||
| adjusted_angle_xy += random_azimuth_deviation; | ||
| adjusted_angle_z += random_polar_deviation; | ||
|
|
||
| // Ensure symmetry: Invert polar angle for lower half | ||
| if (j % 2 == 1) { | ||
| adjusted_angle_z = -adjusted_angle_z; | ||
| } | ||
| else | ||
|
|
||
| // Debugging: Log the calculated angles | ||
| JUSTLOG("Ring %ld, Shot %ld: Azimuth=%ld, Adjusted XY=%ld, Adjusted Z=%ld, Random Azimuth=%ld, Random Polar=%ld", | ||
| i, j, azimuth_angle, adjusted_angle_xy, adjusted_angle_z, random_azimuth_deviation, random_polar_deviation); | ||
|
|
||
| // Create a new projectile | ||
| struct Coord3d shot_pos = offset_pos; | ||
|
|
||
| tmptng = create_thing(&shot_pos, TCls_Shot, shot_model, firing->owner, -1); | ||
| if (thing_is_invalid(tmptng)) | ||
| { | ||
| speed = shotst->speed; | ||
| JUSTLOG("Failed to create shot %ld,%ld", i, j); | ||
| break; | ||
| } | ||
| tmptng = create_thing(&pos1, TCls_Shot, shot_model, firing->owner, -1); | ||
| if (thing_is_invalid(tmptng)) | ||
| break; | ||
|
|
||
| shotng = tmptng; | ||
| shotng->shot.hit_type = hit_type; | ||
| shotng->move_angle_xy = (short)((angle_xy + CREATURE_RANDOM(firing, 2 * shotst->spread_xy + 1) - shotst->spread_xy) & LbFPMath_AngleMask); | ||
| shotng->move_angle_z = (short)((angle_yz + CREATURE_RANDOM(firing, 2 * shotst->spread_z + 1) - shotst->spread_z) & LbFPMath_AngleMask); | ||
| angles_to_vector(shotng->move_angle_xy, shotng->move_angle_z, speed, &cvect); | ||
|
|
||
| // Set the projectile's direction based on calculated angles | ||
| angles_to_vector(adjusted_angle_xy, adjusted_angle_z, shotst->speed, &cvect); | ||
| JUSTLOG("Shot %ld,%ld: Adding velocity x=%d, y=%d, z=%d", | ||
| i, j, cvect.x, cvect.y, cvect.z); | ||
| shotng->veloc_push_add.x.val += cvect.x; | ||
| shotng->veloc_push_add.y.val += cvect.y; | ||
| shotng->veloc_push_add.z.val += cvect.z; | ||
|
|
||
| // Set additional projectile properties | ||
| shotng->state_flags |= TF1_PushAdd; | ||
| shotng->shot.damage = damage; | ||
| shotng->health = shotst->health; | ||
| shotng->parent_idx = firing->index; | ||
| JUSTLOG("Shot %ld,%ld: Created", i, j); |
There was a problem hiding this comment.
There are multiple JUSTLOG calls inside the nested Hail loops (for each ring and for each shot), which will generate a very large amount of logging every time this attack is used and can noticeably impact performance and log readability. Since the comments mark these as debugging logs, it would be better to either remove them, guard them with a higher debug level, or wrap them in a conditional so they can be disabled in normal gameplay builds.
| // Set additional projectile properties | ||
| shotng->state_flags |= TF1_PushAdd; | ||
| shotng->shot.damage = damage; | ||
| shotng->health = shotst->health; | ||
| shotng->parent_idx = firing->index; | ||
| JUSTLOG("Shot %ld,%ld: Created", i, j); |
There was a problem hiding this comment.
The Hail branch creates multiple shots but never sets shotng->parent_idx for them, unlike the Beam/Breathe cases and the default/Volley/Lizard path which all assign the shooter as parent. For Hail shots this leaves parent_idx equal to the shot’s own index, which will break shooter-based logic in thing_shots.c (kill attribution, effects, etc.). To keep Hail consistent and avoid misattribution, set shotng->parent_idx = firing->index for each created projectile inside this loop (or factor out a helper so all fire_logic branches initialize shot parent consistently).
fire shots in in a round circle with small randomization