1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12 #include "asteroid/asteroid.h"
13 #include "debugconsole/console.h"
14 #include "fireball/fireballs.h"
15 #include "freespace.h"
16 #include "gamesnd/gamesnd.h"
17 #include "globalincs/linklist.h"
18 #include "globalincs/systemvars.h"
19 #include "hud/hud.h"
20 #include "hud/hudescort.h"
21 #include "hud/hudgauges.h"
22 #include "hud/hudtarget.h"
23 #include "iff_defs/iff_defs.h"
24 #include "io/timer.h"
25 #include "localization/localize.h"
26 #include "math/staticrand.h"
27 #include "math/vecmat.h"
28 #include "model/model.h"
29 #include "network/multi.h"
30 #include "network/multimsgs.h"
31 #include "network/multiutil.h"
32 #include "object/objcollide.h"
33 #include "object/object.h"
34 #include "parse/parselo.h"
35 #include "scripting/scripting.h"
36 #include "particle/particle.h"
37 #include "render/3d.h"
38 #include "ship/ship.h"
39 #include "ship/shipfx.h"
40 #include "ship/shiphit.h"
41 #include "stats/scoring.h"
42 #include "weapon/weapon.h"
43
44 #include <algorithm>
45
46 #define ASTEROID_OBJ_USED (1<<0) // flag used in asteroid_obj struct
47 #define MAX_ASTEROID_OBJS MAX_ASTEROIDS // max number of asteroids tracked in asteroid list
48 asteroid_obj Asteroid_objs[MAX_ASTEROID_OBJS]; // array used to store asteroid object indexes
49 asteroid_obj Asteroid_obj_list; // head of linked list of asteroid_obj structs
50
51 // used for randomly generating debris type when there are multiple sizes.
52 const float SMALL_DEBRIS_WEIGHT = 8.0f;
53 const float MEDIUM_DEBRIS_WEIGHT = 4.0f;
54 const float LARGE_DEBRIS_WEIGHT = 1.0f;
55
56 int Asteroids_enabled = 1;
57 int Num_asteroids = 0;
58 int Asteroid_throw_objnum = -1; // Object index of ship to throw asteroids at.
59 int Next_asteroid_throw;
60
61 SCP_vector< asteroid_info > Asteroid_info;
62 asteroid Asteroids[MAX_ASTEROIDS];
63 asteroid_field Asteroid_field;
64
65
66 static int Asteroid_impact_explosion_ani;
67 static float Asteroid_impact_explosion_radius;
68 char Asteroid_icon_closeup_model[NAME_LENGTH];
69 vec3d Asteroid_icon_closeup_position;
70 float Asteroid_icon_closeup_zoom;
71
72 #define ASTEROID_CHECK_WRAP_TIMESTAMP 2000 // how often an asteroid gets checked for wrapping
73 #define ASTEROID_UPDATE_COLLIDE_TIMESTAMP 2000 // how often asteroid is checked for impending collisions with escort ships
74 #define ASTEROID_MIN_COLLIDE_TIME 24 // time in seconds to check for asteroid colliding
75
76 /**
77 * Force updating of pair stuff for asteroid *objp.
78 */
asteroid_update_collide(object * objp)79 static void asteroid_update_collide(object *objp)
80 {
81 // Asteroid has wrapped, update collide objnum and flags
82 Asteroids[objp->instance].collide_objnum = -1;
83 Asteroids[objp->instance].collide_objsig = -1;
84 OBJ_RECALC_PAIRS(objp);
85 }
86
87 /**
88 * Clear out the ::Asteroid_obj_list
89 */
asteroid_obj_list_init()90 static void asteroid_obj_list_init()
91 {
92 int i;
93
94 list_init(&Asteroid_obj_list);
95 for ( i = 0; i < MAX_ASTEROID_OBJS; i++ ) {
96 Asteroid_objs[i].flags = 0;
97 }
98 }
99
100 /**
101 * Add a node from the Asteroid_obj_list.
102 * Only called from ::weapon_create()
103 */
asteroid_obj_list_add(int objnum)104 static int asteroid_obj_list_add(int objnum)
105 {
106 int index;
107
108 asteroid *cur_asteroid = &Asteroids[Objects[objnum].instance];
109 index = (int)(cur_asteroid - Asteroids);
110
111 Assert(index >= 0 && index < MAX_ASTEROID_OBJS);
112 Assert(!(Asteroid_objs[index].flags & ASTEROID_OBJ_USED));
113
114 Asteroid_objs[index].flags = 0;
115 Asteroid_objs[index].objnum = objnum;
116 list_append(&Asteroid_obj_list, &Asteroid_objs[index]);
117 Asteroid_objs[index].flags |= ASTEROID_OBJ_USED;
118
119 return index;
120 }
121
122 /**
123 * Remove a node from the Asteroid_obj_list.
124 * Only called from ::weapon_delete()
125 */
asteroid_obj_list_remove(object * obj)126 static void asteroid_obj_list_remove(object * obj)
127 {
128 int index = obj->instance;
129
130 Assert(index >= 0 && index < MAX_ASTEROID_OBJS);
131 Assert(Asteroid_objs[index].flags & ASTEROID_OBJ_USED);
132
133 list_remove(&Asteroid_obj_list, &Asteroid_objs[index]);
134 Asteroid_objs[index].flags = 0;
135 }
136
137
138 /**
139 * Prevent speed from getting too huge so it's hard to catch up to an asteroid.
140 */
asteroid_cap_speed(int asteroid_info_index,float speed)141 static float asteroid_cap_speed(int asteroid_info_index, float speed)
142 {
143 float max, double_max;
144
145 Assert( asteroid_info_index < (int)Asteroid_info.size() );
146
147 max = Asteroid_info[asteroid_info_index].max_speed;
148 double_max = max * 2;
149
150 while (speed > double_max){
151 speed *= 0.5f;
152 }
153
154 if (speed > max){
155 speed *= 0.75f;
156 }
157
158 return speed;
159 }
160
161 /**
162 * Returns whether position is inside inner bounding volume
163 *
164 * Sum together the following: 1 inside x, 2 inside y, 4 inside z
165 * inside only when sum = 7
166 */
asteroid_in_inner_bound_with_axes(asteroid_field * asfieldp,vec3d * pos,float delta)167 static int asteroid_in_inner_bound_with_axes(asteroid_field *asfieldp, vec3d *pos, float delta)
168 {
169 Assert(asfieldp->has_inner_bound);
170
171 int rval = 0;
172 if ( (pos->xyz.x > asfieldp->inner_min_bound.xyz.x - delta) && (pos->xyz.x < asfieldp->inner_max_bound.xyz.x + delta) ) {
173 rval += 1;
174 }
175
176 if ( (pos->xyz.y > asfieldp->inner_min_bound.xyz.y - delta) && (pos->xyz.y < asfieldp->inner_max_bound.xyz.y + delta) ) {
177 rval += 2;
178 }
179
180 if ( (pos->xyz.z > asfieldp->inner_min_bound.xyz.z - delta) && (pos->xyz.z < asfieldp->inner_max_bound.xyz.z + delta) ) {
181 rval += 4;
182 }
183
184 return rval;
185 }
186
187 /**
188 * Check if asteroid is within inner bound
189 * @return 0 if not inside or no inner bound, 1 if inside inner bound
190 */
asteroid_in_inner_bound(asteroid_field * asfieldp,vec3d * pos,float delta)191 static int asteroid_in_inner_bound(asteroid_field *asfieldp, vec3d *pos, float delta) {
192
193 if (!asfieldp->has_inner_bound) {
194 return 0;
195 }
196
197 return (asteroid_in_inner_bound_with_axes(asfieldp, pos, delta) == 7);
198 }
199
200 /**
201 * Repositions asteroid outside the inner box on all 3 axes
202 *
203 * Moves to the other side of the inner box a distance delta from edge of box
204 */
inner_bound_pos_fixup(asteroid_field * asfieldp,vec3d * pos)205 static void inner_bound_pos_fixup(asteroid_field *asfieldp, vec3d *pos)
206 {
207 if (!asteroid_in_inner_bound(asfieldp, pos, 0)) {
208 return;
209 }
210
211 float dist1, dist2;
212 int axis;
213
214 for (axis=0; axis<3; axis++) {
215 dist1 = pos->a1d[axis] - asfieldp->inner_min_bound.a1d[axis];
216 dist2 = asfieldp->inner_max_bound.a1d[axis] - pos->a1d[axis];
217 Assert(dist1 >= 0 && dist2 >= 0);
218
219 if (dist1 < dist2) {
220 pos->a1d[axis] = asfieldp->inner_max_bound.a1d[axis] + dist1;
221 } else {
222 pos->a1d[axis] = asfieldp->inner_min_bound.a1d[axis] - dist2;
223 }
224 }
225 }
226
227 /**
228 * Create a single asteroid
229 */
asteroid_create(asteroid_field * asfieldp,int asteroid_type,int asteroid_subtype)230 object *asteroid_create(asteroid_field *asfieldp, int asteroid_type, int asteroid_subtype)
231 {
232 int n, objnum;
233 matrix orient;
234 object *objp;
235 asteroid *asp;
236 asteroid_info *asip;
237 vec3d pos, delta_bound;
238 angles angs;
239 float radius;
240 ushort signature;
241 int rand_base;
242
243 // bogus
244 if(asfieldp == NULL) {
245 return NULL;
246 }
247
248 for (n=0; n<MAX_ASTEROIDS; n++) {
249 if (!(Asteroids[n].flags & AF_USED)) {
250 break;
251 }
252 }
253
254 if (n >= MAX_ASTEROIDS) {
255 nprintf(("Warning","Could not create asteroid, no more slots left\n"));
256 return NULL;
257 }
258
259 if((asteroid_type < 0) || (asteroid_type >= (int)Asteroid_info.size())) {
260 return NULL;
261 }
262
263 if((asteroid_subtype < 0) || (asteroid_subtype >= NUM_DEBRIS_POFS)) {
264 return NULL;
265 }
266
267 // HACK: multiplayer asteroid subtype always 0 to keep subtype in sync
268 if ( Game_mode & GM_MULTIPLAYER) {
269 asteroid_subtype = 0;
270 }
271
272 asip = &Asteroid_info[asteroid_type];
273
274 // bogus
275 if(asip->modelp[asteroid_subtype] == NULL) {
276 return NULL;
277 }
278
279 asp = &Asteroids[n];
280 asp->asteroid_type = asteroid_type;
281 asp->asteroid_subtype = asteroid_subtype;
282 asp->flags = 0;
283 asp->flags |= AF_USED;
284 asp->check_for_wrap = timestamp_rand(0, ASTEROID_CHECK_WRAP_TIMESTAMP);
285 asp->check_for_collide = timestamp_rand(0, ASTEROID_UPDATE_COLLIDE_TIMESTAMP);
286 asp->final_death_time = timestamp(-1);
287 asp->collide_objnum = -1;
288 asp->collide_objsig = -1;
289 asp->target_objnum = -1;
290
291 radius = model_get_radius(asip->model_num[asteroid_subtype]);
292
293 vm_vec_sub(&delta_bound, &asfieldp->max_bound, &asfieldp->min_bound);
294
295 // for multiplayer, we want to do a static_rand so that everything behaves the same on all machines
296 signature = 0;
297 rand_base = 0;
298 if ( Game_mode & GM_NORMAL ) {
299 pos.xyz.x = asfieldp->min_bound.xyz.x + delta_bound.xyz.x * frand();
300 pos.xyz.y = asfieldp->min_bound.xyz.y + delta_bound.xyz.y * frand();
301 pos.xyz.z = asfieldp->min_bound.xyz.z + delta_bound.xyz.z * frand();
302
303 inner_bound_pos_fixup(asfieldp, &pos);
304 angs.p = frand() * PI2;
305 angs.b = frand() * PI2;
306 angs.h = frand() * PI2;
307 } else {
308 signature = multi_assign_network_signature( MULTI_SIG_ASTEROID );
309 rand_base = signature;
310
311 pos.xyz.x = asfieldp->min_bound.xyz.x + delta_bound.xyz.x * static_randf( rand_base++ );
312 pos.xyz.y = asfieldp->min_bound.xyz.y + delta_bound.xyz.y * static_randf( rand_base++ );
313 pos.xyz.z = asfieldp->min_bound.xyz.z + delta_bound.xyz.z * static_randf( rand_base++ );
314
315 inner_bound_pos_fixup(asfieldp, &pos);
316 angs.p = static_randf( rand_base++ ) * PI2;
317 angs.b = static_randf( rand_base++ ) * PI2;
318 angs.h = static_randf( rand_base++ ) * PI2;
319 }
320
321 vm_angles_2_matrix(&orient, &angs);
322 flagset<Object::Object_Flags> asteroid_default_flagset;
323 asteroid_default_flagset += Object::Object_Flags::Renders;
324 asteroid_default_flagset += Object::Object_Flags::Physics;
325 asteroid_default_flagset += Object::Object_Flags::Collides;
326
327 objnum = obj_create(OBJ_ASTEROID, -1, n, &orient, &pos, radius, asteroid_default_flagset, false);
328
329 if ( (objnum == -1) || (objnum >= MAX_OBJECTS) ) {
330 mprintf(("Couldn't create asteroid -- out of object slots\n"));
331 return NULL;
332 }
333
334 asp->objnum = objnum;
335 asp->model_instance_num = -1;
336
337 if (model_get(asip->model_num[asteroid_subtype])->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
338 asp->model_instance_num = model_create_instance(false, asip->model_num[asteroid_subtype]);
339 }
340
341 // Add to Asteroid_used_list
342 asteroid_obj_list_add(objnum);
343
344 objp = &Objects[objnum];
345
346 if ( Game_mode & GM_MULTIPLAYER ){
347 objp->net_signature = signature;
348 }
349
350 Num_asteroids++;
351
352 if (radius < 1.0) {
353 radius = 1.0f;
354 }
355
356 vec3d rotvel;
357 if ( Game_mode & GM_NORMAL ) {
358 vm_vec_rand_vec_quick(&rotvel);
359 vm_vec_scale(&rotvel, frand()/4.0f + 0.1f);
360 objp->phys_info.rotvel = rotvel;
361 vm_vec_rand_vec_quick(&objp->phys_info.vel);
362 } else {
363 static_randvec( rand_base++, &rotvel );
364 vm_vec_scale(&rotvel, static_randf(rand_base++)/4.0f + 0.1f);
365 objp->phys_info.rotvel = rotvel;
366 static_randvec( rand_base++, &objp->phys_info.vel );
367 }
368
369
370 float speed;
371
372 if ( Game_mode & GM_NORMAL ) {
373 speed = asteroid_cap_speed(asteroid_type, asfieldp->speed*frand_range(0.5f + (float) Game_skill_level/NUM_SKILL_LEVELS, 2.0f + (float) (2*Game_skill_level)/NUM_SKILL_LEVELS));
374 } else {
375 speed = asteroid_cap_speed(asteroid_type, asfieldp->speed*static_randf_range(rand_base++, 0.5f + (float) Game_skill_level/NUM_SKILL_LEVELS, 2.0f + (float) (2*Game_skill_level)/NUM_SKILL_LEVELS));
376 }
377
378 vm_vec_scale(&objp->phys_info.vel, speed);
379 objp->phys_info.desired_vel = objp->phys_info.vel;
380
381 // blow out his reverse thrusters. Or drag, same thing.
382 objp->phys_info.rotdamp = 10000.0f;
383 objp->phys_info.side_slip_time_const = 10000.0f;
384 objp->phys_info.flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP); // set damping equal for all axis and not changable
385
386 // Fill in the max_vel field, so the collision pair stuff knows
387 // how fast this can move maximum in order to throw out collisions.
388 // This is in local coordinates, so Z is forward velocity.
389 objp->phys_info.max_vel.xyz.x = 0.0f;
390 objp->phys_info.max_vel.xyz.y = 0.0f;
391 objp->phys_info.max_vel.xyz.z = vm_vec_mag(&objp->phys_info.desired_vel);
392
393 objp->phys_info.mass = asip->modelp[asteroid_subtype]->rad * 700.0f;
394 objp->phys_info.I_body_inv.vec.rvec.xyz.x = 1.0f / (objp->phys_info.mass*asip->modelp[asteroid_subtype]->rad);
395 objp->phys_info.I_body_inv.vec.uvec.xyz.y = objp->phys_info.I_body_inv.vec.rvec.xyz.x;
396 objp->phys_info.I_body_inv.vec.fvec.xyz.z = objp->phys_info.I_body_inv.vec.rvec.xyz.x;
397 objp->hull_strength = asip->initial_asteroid_strength * (0.8f + (float)Game_skill_level/NUM_SKILL_LEVELS)/2.0f;
398
399 // ensure vel is valid
400 Assert( !vm_is_vec_nan(&objp->phys_info.vel) );
401
402 return objp;
403 }
404
405 /**
406 * Create asteroids when parent_objp blows up.
407 */
asteroid_sub_create(object * parent_objp,int asteroid_type,vec3d * relvec)408 void asteroid_sub_create(object *parent_objp, int asteroid_type, vec3d *relvec)
409 {
410 object *new_objp;
411 float speed;
412
413 Assert(parent_objp->type == OBJ_ASTEROID);
414 int subtype = Asteroids[parent_objp->instance].asteroid_subtype;
415 new_objp = asteroid_create(&Asteroid_field, asteroid_type, subtype);
416
417 if (new_objp == NULL)
418 return;
419
420 if ( MULTIPLAYER_MASTER ){
421 send_asteroid_create( new_objp, parent_objp, asteroid_type, relvec );
422 }
423
424 // Now, bash some values.
425 vm_vec_scale_add(&new_objp->pos, &parent_objp->pos, relvec, 0.5f * parent_objp->radius);
426 float parent_speed = vm_vec_mag_quick(&parent_objp->phys_info.vel);
427
428 if ( parent_speed < 0.1f ) {
429 parent_speed = vm_vec_mag_quick(&Asteroid_field.vel);
430 }
431
432 new_objp->phys_info.vel = parent_objp->phys_info.vel;
433 if ( Game_mode & GM_NORMAL )
434 speed = asteroid_cap_speed(asteroid_type, (frand() + 2.0f) * parent_speed);
435 else
436 speed = asteroid_cap_speed(asteroid_type, (static_randf(new_objp->net_signature)+2.0f) * parent_speed);
437
438 vm_vec_scale_add2(&new_objp->phys_info.vel, relvec, speed);
439 if (vm_vec_mag_quick(&new_objp->phys_info.vel) > 80.0f)
440 vm_vec_scale(&new_objp->phys_info.vel, 0.5f);
441
442 new_objp->phys_info.desired_vel = new_objp->phys_info.vel;
443 vm_vec_scale_add(&new_objp->last_pos, &new_objp->pos, &new_objp->phys_info.vel, -flFrametime);
444 }
445
446 /**
447 * Load in an asteroid model
448 */
asteroid_load(int asteroid_info_index,int asteroid_subtype)449 static void asteroid_load(int asteroid_info_index, int asteroid_subtype)
450 {
451 int i;
452 asteroid_info *asip;
453
454 Assert( asteroid_info_index < (int)Asteroid_info.size() );
455 Assert( asteroid_subtype < NUM_DEBRIS_POFS );
456
457 if ( (asteroid_info_index >= (int)Asteroid_info.size()) || (asteroid_subtype >= NUM_DEBRIS_POFS) ) {
458 return;
459 }
460
461 asip = &Asteroid_info[asteroid_info_index];
462
463 if ( !VALID_FNAME(asip->pof_files[asteroid_subtype]) )
464 return;
465
466 asip->model_num[asteroid_subtype] = model_load( asip->pof_files[asteroid_subtype], 0, NULL );
467
468 if (asip->model_num[asteroid_subtype] >= 0)
469 {
470 polymodel *pm = asip->modelp[asteroid_subtype] = model_get(asip->model_num[asteroid_subtype]);
471
472 if ( asip->num_detail_levels != pm->n_detail_levels )
473 {
474 if ( !Is_standalone )
475 {
476 // just log to file for standalone servers
477 Warning(LOCATION, "For asteroid '%s', detail level\nmismatch (POF needs %d)", asip->name, pm->n_detail_levels );
478 }
479 else
480 {
481 nprintf(("Warning", "For asteroid '%s', detail level mismatch (POF needs %d)\n", asip->name, pm->n_detail_levels));
482 }
483 }
484 // Stuff detail level distances.
485 for ( i=0; i<pm->n_detail_levels; i++ )
486 pm->detail_depth[i] = (i < asip->num_detail_levels) ? i2fl(asip->detail_distance[i]) : 0.0f;
487 }
488 }
489
490 /**
491 * Create all the asteroids for the mission
492 */
asteroid_create_all()493 void asteroid_create_all()
494 {
495 int i, idx;
496
497 // ship_debris_odds_table keeps track of debris type of the next debris piece
498 // each different type (size) of debris piece has a diffenent weight, smaller weighted more heavily than larger.
499 // choose next type from table ship_debris_odds_table by Random::next()%max_weighted_range,
500 // the threshold *below* which the debris type is selected.
501 struct {
502 float random_threshold;
503 int debris_type;
504 } ship_debris_odds_table[MAX_ACTIVE_DEBRIS_TYPES];
505
506 float max_weighted_range = 0.0f;
507
508 if (!Asteroids_enabled)
509 return;
510
511 if (Asteroid_field.num_initial_asteroids <= 0 ) {
512 return;
513 }
514
515 int max_asteroids = Asteroid_field.num_initial_asteroids; // * (1.0f - 0.1f*(MAX_DETAIL_LEVEL-Detail.asteroid_density)));
516
517 int num_debris_types = 0;
518
519 // get number of ship debris types
520 if (Asteroid_field.debris_genre == DG_SHIP) {
521 for (idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; idx++) {
522 if (Asteroid_field.field_debris_type[idx] != -1) {
523 num_debris_types++;
524 }
525 }
526
527 // Calculate the odds table
528 for (idx=0; idx<num_debris_types; idx++) {
529 float debris_weight = Asteroid_info[Asteroid_field.field_debris_type[idx]].spawn_weight;
530 ship_debris_odds_table[idx].random_threshold = max_weighted_range + debris_weight;
531 ship_debris_odds_table[idx].debris_type = Asteroid_field.field_debris_type[idx];
532 max_weighted_range += debris_weight;
533 }
534 }
535
536 // Load Asteroid/ship models
537 if (Asteroid_field.debris_genre == DG_SHIP) {
538 for (idx=0; idx<num_debris_types; idx++) {
539 asteroid_load(Asteroid_field.field_debris_type[idx], 0);
540 }
541 } else {
542 if (Asteroid_field.field_debris_type[0] != -1) {
543 asteroid_load(ASTEROID_TYPE_SMALL, 0);
544 asteroid_load(ASTEROID_TYPE_MEDIUM, 0);
545 asteroid_load(ASTEROID_TYPE_LARGE, 0);
546 }
547
548 if (Asteroid_field.field_debris_type[1] != -1) {
549 asteroid_load(ASTEROID_TYPE_SMALL, 1);
550 asteroid_load(ASTEROID_TYPE_MEDIUM, 1);
551 asteroid_load(ASTEROID_TYPE_LARGE, 1);
552 }
553
554 if (Asteroid_field.field_debris_type[2] != -1) {
555 asteroid_load(ASTEROID_TYPE_SMALL, 2);
556 asteroid_load(ASTEROID_TYPE_MEDIUM, 2);
557 asteroid_load(ASTEROID_TYPE_LARGE, 2);
558 }
559 }
560
561 // load all the asteroid/debris pieces
562 for (i=0; i<max_asteroids; i++) {
563 if (Asteroid_field.debris_genre == DG_ASTEROID) {
564 // For asteroid, load only large asteroids
565
566 // get a valid subtype
567 int subtype = Random::next(NUM_DEBRIS_POFS);
568 while (Asteroid_field.field_debris_type[subtype] == -1) {
569 subtype = (subtype + 1) % NUM_DEBRIS_POFS;
570 }
571
572 asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
573 } else {
574 Assert(num_debris_types > 0);
575
576 float rand_choice = frand() * max_weighted_range;
577
578 for (idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; idx++) {
579 // for ship debris, choose type according to odds table
580 if (rand_choice < ship_debris_odds_table[idx].random_threshold) {
581 asteroid_create(&Asteroid_field, ship_debris_odds_table[idx].debris_type, 0);
582 break;
583 }
584 }
585 }
586 }
587 }
588
589 /**
590 * Init asteroid system for the level, called from ::game_level_init()
591 */
asteroid_level_init()592 void asteroid_level_init()
593 {
594 Asteroid_field.num_initial_asteroids=0;
595 Num_asteroids = 0;
596 Next_asteroid_throw = timestamp(1);
597 asteroid_obj_list_init();
598 SCP_vector<asteroid_info>::iterator ast;
599 for (ast = Asteroid_info.begin(); ast != Asteroid_info.end(); ++ast)
600 ast->damage_type_idx = ast->damage_type_idx_sav;
601 }
602
603 /**
604 * Should asteroid wrap from one end of the asteroid field to the other.
605 * Multiplayer clients will always return 0 from this function. We will force a wrap on the clients when server tells us
606 *
607 * @return !0 if asteroid should be wrapped, 0 otherwise.
608 */
asteroid_should_wrap(object * objp,asteroid_field * asfieldp)609 static int asteroid_should_wrap(object *objp, asteroid_field *asfieldp)
610 {
611 if ( MULTIPLAYER_CLIENT )
612 return 0;
613
614 if (objp->pos.xyz.x < asfieldp->min_bound.xyz.x) {
615 return 1;
616 }
617
618 if (objp->pos.xyz.y < asfieldp->min_bound.xyz.y) {
619 return 1;
620 }
621
622 if (objp->pos.xyz.z < asfieldp->min_bound.xyz.z) {
623 return 1;
624 }
625
626 if (objp->pos.xyz.x > asfieldp->max_bound.xyz.x) {
627 return 1;
628 }
629
630 if (objp->pos.xyz.y > asfieldp->max_bound.xyz.y) {
631 return 1;
632 }
633
634 if (objp->pos.xyz.z > asfieldp->max_bound.xyz.z) {
635 return 1;
636 }
637
638 // check against inner bound
639 if (asfieldp->has_inner_bound) {
640 if ( (objp->pos.xyz.x > asfieldp->inner_min_bound.xyz.x) && (objp->pos.xyz.x < asfieldp->inner_max_bound.xyz.x)
641 && (objp->pos.xyz.y > asfieldp->inner_min_bound.xyz.y) && (objp->pos.xyz.y < asfieldp->inner_max_bound.xyz.y)
642 && (objp->pos.xyz.z > asfieldp->inner_min_bound.xyz.z) && (objp->pos.xyz.z < asfieldp->inner_max_bound.xyz.z) ) {
643
644 return 1;
645 }
646 }
647
648 return 0;
649 }
650
651 /**
652 * Wrap an asteroid from one end of the asteroid field to the other
653 */
asteroid_wrap_pos(object * objp,asteroid_field * asfieldp)654 static void asteroid_wrap_pos(object *objp, asteroid_field *asfieldp)
655 {
656 if (objp->pos.xyz.x < asfieldp->min_bound.xyz.x) {
657 objp->pos.xyz.x = asfieldp->max_bound.xyz.x + (objp->pos.xyz.x - asfieldp->min_bound.xyz.x);
658 }
659
660 if (objp->pos.xyz.y < asfieldp->min_bound.xyz.y) {
661 objp->pos.xyz.y = asfieldp->max_bound.xyz.y + (objp->pos.xyz.y - asfieldp->min_bound.xyz.y);
662 }
663
664 if (objp->pos.xyz.z < asfieldp->min_bound.xyz.z) {
665 objp->pos.xyz.z = asfieldp->max_bound.xyz.z + (objp->pos.xyz.z - asfieldp->min_bound.xyz.z);
666 }
667
668 if (objp->pos.xyz.x > asfieldp->max_bound.xyz.x) {
669 objp->pos.xyz.x = asfieldp->min_bound.xyz.x + (objp->pos.xyz.x - asfieldp->max_bound.xyz.x);
670 }
671
672 if (objp->pos.xyz.y > asfieldp->max_bound.xyz.y) {
673 objp->pos.xyz.y = asfieldp->min_bound.xyz.y + (objp->pos.xyz.y - asfieldp->max_bound.xyz.y);
674 }
675
676 if (objp->pos.xyz.z > asfieldp->max_bound.xyz.z) {
677 objp->pos.xyz.z = asfieldp->min_bound.xyz.z + (objp->pos.xyz.z - asfieldp->max_bound.xyz.z);
678 }
679
680 // wrap on inner bound, check all 3 axes as needed, use of rand ok for multiplayer with send_asteroid_throw()
681 inner_bound_pos_fixup(asfieldp, &objp->pos);
682
683 }
684
685
686 /**
687 * Is asteroid targeted?
688 *
689 * @return !0 if this asteroid is a target for any ship, otherwise return 0
690 */
asteroid_is_targeted(object * objp)691 static int asteroid_is_targeted(object *objp)
692 {
693 ship_obj *so;
694 object *ship_objp;
695 int asteroid_obj_index;
696
697 asteroid_obj_index=OBJ_INDEX(objp);
698
699 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
700 ship_objp = &Objects[so->objnum];
701 if ( Ai_info[Ships[ship_objp->instance].ai_index].target_objnum == asteroid_obj_index ) {
702 return 1;
703 }
704 }
705
706 return 0;
707 }
708
709 /**
710 * Create an asteroid that will hit object *objp in delta_time seconds
711 */
asteroid_aim_at_target(object * objp,object * asteroid_objp,float delta_time)712 static void asteroid_aim_at_target(object *objp, object *asteroid_objp, float delta_time)
713 {
714 vec3d predicted_center_pos;
715 vec3d rand_vec;
716 float speed;
717
718 vm_vec_scale_add(&predicted_center_pos, &objp->pos, &objp->phys_info.vel, delta_time);
719 vm_vec_rand_vec_quick(&rand_vec);
720 vm_vec_scale_add2(&predicted_center_pos, &rand_vec, objp->radius/2.0f);
721
722 vm_vec_add2(&rand_vec, &objp->orient.vec.fvec);
723 if (vm_vec_mag_quick(&rand_vec) < 0.1f)
724 vm_vec_add2(&rand_vec, &objp->orient.vec.rvec);
725 vm_vec_normalize(&rand_vec);
726
727 speed = Asteroid_info[0].max_speed * (frand()/2.0f + 0.5f);
728
729 vm_vec_copy_scale(&asteroid_objp->phys_info.vel, &rand_vec, -speed);
730 asteroid_objp->phys_info.desired_vel = asteroid_objp->phys_info.vel;
731 vm_vec_scale_add(&asteroid_objp->pos, &predicted_center_pos, &asteroid_objp->phys_info.vel, -delta_time);
732 vm_vec_scale_add(&asteroid_objp->last_pos, &asteroid_objp->pos, &asteroid_objp->phys_info.vel, -flFrametime);
733 }
734
735 /**
736 * Call once per frame to maybe throw an asteroid at a ship.
737 *
738 * @param count asteroids already targeted on
739 */
maybe_throw_asteroid(int count)740 static void maybe_throw_asteroid(int count)
741 {
742 if (!timestamp_elapsed(Next_asteroid_throw)) {
743 return;
744 }
745
746 if (Asteroid_throw_objnum == -1) {
747 return;
748 }
749
750 nprintf(("AI", "Incoming asteroids: %i\n", count));
751
752 if (count > The_mission.ai_profile->max_incoming_asteroids[Game_skill_level])
753 return;
754
755 Next_asteroid_throw = timestamp(1000 + 1200 * count/(Game_skill_level+1));
756
757 ship_obj *so;
758 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
759 object *A = &Objects[so->objnum];
760 if (so->objnum == Asteroid_throw_objnum) {
761 int subtype = Random::next(NUM_DEBRIS_POFS);
762 while (Asteroid_field.field_debris_type[subtype] == -1) {
763 subtype = (subtype + 1) % NUM_DEBRIS_POFS;
764 }
765 object *objp = asteroid_create(&Asteroid_field, ASTEROID_TYPE_LARGE, subtype);
766 if (objp != NULL) {
767 asteroid_aim_at_target(A, objp, ASTEROID_MIN_COLLIDE_TIME + frand() * 20.0f);
768
769 // if asteroid is inside inner bound, kill it
770 if (asteroid_in_inner_bound(&Asteroid_field, &objp->pos, 0.0f)) {
771 objp->flags.set(Object::Object_Flags::Should_be_dead);
772 } else {
773 Asteroids[objp->instance].target_objnum = so->objnum;
774
775 if ( MULTIPLAYER_MASTER ) {
776 send_asteroid_throw( objp );
777 }
778 }
779 }
780
781 return;
782 }
783 }
784
785 }
786
787 /**
788 * Delete asteroid from Asteroid_used_list
789 */
asteroid_delete(object * obj)790 void asteroid_delete( object * obj )
791 {
792 int num;
793 asteroid *asp;
794
795 num = obj->instance;
796 Assert( Asteroids[num].objnum == OBJ_INDEX(obj));
797
798 asp = &Asteroids[num];
799
800 if (asp->model_instance_num >= 0)
801 model_delete_instance(asp->model_instance_num);
802
803 asp->flags = 0;
804 Num_asteroids--;
805 Assert(Num_asteroids >= 0);
806
807 asteroid_obj_list_remove( obj );
808 }
809
810 /**
811 * See if we should reposition the asteroid.
812 * Only reposition if oustide the bounding volume and the player isn't looking towards the asteroid.
813 */
asteroid_maybe_reposition(object * objp,asteroid_field * asfieldp)814 static void asteroid_maybe_reposition(object *objp, asteroid_field *asfieldp)
815 {
816 // passive field does not wrap
817 if (asfieldp->field_type == FT_PASSIVE) {
818 return;
819 }
820
821 if ( asteroid_should_wrap(objp, asfieldp) ) {
822 vec3d vec_to_asteroid, old_asteroid_pos, old_vel;
823 float dot, dist;
824
825 old_asteroid_pos = objp->pos;
826 old_vel = objp->phys_info.vel;
827
828 // don't wrap asteroid if it is a target of some ship
829 if ( !asteroid_is_targeted(objp) ) {
830
831 // only wrap if player won't see asteroid disappear/reverse direction
832 dist = vm_vec_normalized_dir(&vec_to_asteroid, &objp->pos, &Eye_position);
833 dot = vm_vec_dot(&Eye_matrix.vec.fvec, &vec_to_asteroid);
834
835 if ( (dot < 0.7f) || (dist > asfieldp->bound_rad) ) {
836 if (Num_asteroids > MAX_ASTEROIDS-10) {
837 objp->flags.set(Object::Object_Flags::Should_be_dead);
838 } else {
839 // check to ensure player won't see asteroid appear either
840 asteroid_wrap_pos(objp, asfieldp);
841 Asteroids[objp->instance].target_objnum = -1;
842
843 dist = vm_vec_normalized_dir(&vec_to_asteroid, &objp->pos, &Eye_position);
844 dot = vm_vec_dot(&Eye_matrix.vec.fvec, &vec_to_asteroid);
845
846 if ( (dot > 0.7f) && (dist < (asfieldp->bound_rad * 1.3f)) ) {
847 // player would see asteroid pop out other side, so reverse velocity instead of wrapping
848 objp->pos = old_asteroid_pos;
849 vm_vec_copy_scale(&objp->phys_info.vel, &old_vel, -1.0f);
850 objp->phys_info.desired_vel = objp->phys_info.vel;
851 Asteroids[objp->instance].target_objnum = -1;
852 }
853
854 // update last pos (after vel is known)
855 vm_vec_scale_add(&objp->last_pos, &objp->pos, &objp->phys_info.vel, -flFrametime);
856
857 asteroid_update_collide(objp);
858
859 if ( MULTIPLAYER_MASTER )
860 send_asteroid_throw( objp );
861 }
862 }
863 }
864 }
865 }
866
lerp(float * goal,float f1,float f2,float scale)867 static void lerp(float *goal, float f1, float f2, float scale)
868 {
869 *goal = (f2 - f1) * scale + f1;
870 }
871
asteroid_process_pre(object * objp)872 void asteroid_process_pre( object *objp )
873 {
874 if (Asteroids_enabled) {
875 // Make vel chase desired_vel
876 lerp(&objp->phys_info.vel.xyz.x, objp->phys_info.vel.xyz.x, objp->phys_info.desired_vel.xyz.x, flFrametime);
877 lerp(&objp->phys_info.vel.xyz.y, objp->phys_info.vel.xyz.y, objp->phys_info.desired_vel.xyz.y, flFrametime);
878 lerp(&objp->phys_info.vel.xyz.z, objp->phys_info.vel.xyz.z, objp->phys_info.desired_vel.xyz.z, flFrametime);
879 }
880 }
881
asteroid_check_collision(object * pasteroid,object * other_obj,vec3d * hitpos,collision_info_struct * asteroid_hit_info,vec3d * hitnormal)882 int asteroid_check_collision(object *pasteroid, object *other_obj, vec3d *hitpos, collision_info_struct *asteroid_hit_info, vec3d* hitnormal)
883 {
884 if (!Asteroids_enabled) {
885 return 0;
886 }
887
888 mc_info mc;
889 mc_info_init(&mc);
890 int num, asteroid_subtype;
891
892 Assert( pasteroid->type == OBJ_ASTEROID );
893
894 num = pasteroid->instance;
895 Assert( num >= 0 );
896
897 Assert( Asteroids[num].objnum == OBJ_INDEX(pasteroid));
898 asteroid_subtype = Asteroids[num].asteroid_subtype;
899
900 // asteroid_hit_info NULL -- asteroid-weapon collision
901 if ( asteroid_hit_info == NULL ) {
902 // asteroid weapon collision
903 Assert( other_obj->type == OBJ_WEAPON );
904 mc.model_instance_num = Asteroids[num].model_instance_num;
905 mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check
906 mc.orient = &pasteroid->orient; // The object's orient
907 mc.pos = &pasteroid->pos; // The object's position
908 mc.p0 = &other_obj->last_pos; // Point 1 of ray to check
909 mc.p1 = &other_obj->pos; // Point 2 of ray to check
910 mc.flags = (MC_CHECK_MODEL);
911
912 if (model_collide(&mc))
913 {
914 *hitpos = mc.hit_point_world;
915
916 if (hitnormal)
917 {
918 vec3d normal;
919
920 if (mc.model_instance_num >= 0)
921 model_instance_find_world_dir(&normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, mc.orient);
922 else
923 model_find_world_dir(&normal, &mc.hit_normal, mc.model_num, mc.hit_submodel, mc.orient);
924
925 *hitnormal = normal;
926 }
927 }
928
929 return mc.num_hits;
930 }
931
932 // asteroid ship collision -- use asteroid_hit_info to calculate physics
933 object *pship_obj = other_obj;
934 Assert( pship_obj->type == OBJ_SHIP );
935
936 object* heavy = asteroid_hit_info->heavy;
937 object* lighter = asteroid_hit_info->light;
938 object *heavy_obj = heavy;
939 object *light_obj = lighter;
940
941 vec3d zero, p0, p1;
942 vm_vec_zero( &zero );
943 vm_vec_sub( &p0, &lighter->last_pos, &heavy->last_pos );
944 vm_vec_sub( &p1, &lighter->pos, &heavy->pos );
945
946 mc.pos = &zero; // The object's position
947 mc.p0 = &p0; // Point 1 of ray to check
948 mc.p1 = &p1; // Point 2 of ray to check
949
950 // find the light object's position in the heavy object's reference frame at last frame and also in this frame.
951 vec3d p0_temp, p0_rotated;
952
953 // Collision detection from rotation enabled if at rotaion is less than 30 degree in frame
954 // This should account for all ships
955 if ( (vm_vec_mag_squared( &heavy->phys_info.rotvel ) * flFrametime*flFrametime) < (PI*PI/36) ) {
956 // collide_rotate calculate (1) start position and (2) relative velocity
957 asteroid_hit_info->collide_rotate = 1;
958 vm_vec_rotate( &p0_temp, &p0, &heavy->last_orient );
959 vm_vec_unrotate( &p0_rotated, &p0_temp, &heavy->orient );
960 mc.p0 = &p0_rotated; // Point 1 of ray to check
961 vm_vec_sub( &asteroid_hit_info->light_rel_vel, &p1, &p0_rotated );
962 vm_vec_scale( &asteroid_hit_info->light_rel_vel, 1/flFrametime );
963 // HACK - this applies to big ships warping in/out of asteroid fields - not sure what it does
964 if (vm_vec_mag(&asteroid_hit_info->light_rel_vel) > 300) {
965 asteroid_hit_info->collide_rotate = 0;
966 vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
967 }
968 } else {
969 asteroid_hit_info->collide_rotate = 0;
970 vm_vec_sub( &asteroid_hit_info->light_rel_vel, &lighter->phys_info.vel, &heavy->phys_info.vel );
971 }
972
973 int mc_ret_val = 0;
974
975 if ( asteroid_hit_info->heavy == pship_obj ) { // ship is heavier, so asteroid is sphere. Check sphere collision against ship poly model
976 mc.model_instance_num = Ships[pship_obj->instance].model_instance_num;
977 mc.model_num = Ship_info[Ships[pship_obj->instance].ship_info_index].model_num; // Fill in the model to check
978 mc.orient = &pship_obj->orient; // The object's orient
979 mc.radius = pasteroid->radius;
980 mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE);
981
982 // copy important data
983 int copy_flags = mc.flags; // make a copy of start end positions of sphere in big ship RF
984 vec3d copy_p0, copy_p1;
985 copy_p0 = *mc.p0;
986 copy_p1 = *mc.p1;
987
988 // first test against the sphere - if this fails then don't do any submodel tests
989 mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE;
990
991 if (model_collide(&mc)) {
992
993 // Set earliest hit time
994 asteroid_hit_info->hit_time = FLT_MAX;
995
996 auto pmi = model_get_instance(Ships[heavy_obj->instance].model_instance_num);
997 auto pm = model_get(pmi->model_num);
998
999 // Do collision the cool new way
1000 if ( asteroid_hit_info->collide_rotate ) {
1001 // We collide with the sphere, find the list of rotating submodels and test one at a time
1002 SCP_vector<int> submodel_vector;
1003 model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
1004
1005 // turn off all rotating submodels, collide against only 1 at a time.
1006 // turn off collision detection for all rotating submodels
1007 for (auto submodel: submodel_vector) {
1008 pmi->submodel[submodel].collision_checked = true;
1009 }
1010
1011 // reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
1012 mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
1013
1014 // check each submodel in turn
1015 for (auto submodel: submodel_vector) {
1016 auto smi = &pmi->submodel[submodel];
1017
1018 // turn on just one submodel for collision test
1019 smi->collision_checked = false;
1020
1021 // set angles for last frame (need to set to prev to get p0)
1022 matrix copy_matrix = smi->canonical_orient;
1023
1024 // find the start and end positions of the sphere in submodel RF
1025 smi->canonical_orient = smi->canonical_prev_orient;
1026 world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos);
1027
1028 smi->canonical_orient = copy_matrix;
1029 world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
1030
1031 mc.p0 = &p0;
1032 mc.p1 = &p1;
1033
1034 mc.orient = &vmd_identity_matrix;
1035 mc.submodel_num = submodel;
1036
1037 if ( model_collide(&mc) ) {
1038 if ( mc.hit_dist < asteroid_hit_info->hit_time ) {
1039 mc_ret_val = 1;
1040
1041 // set up asteroid_hit_info common
1042 set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_ROT_HIT);
1043
1044 // set up asteroid_hit_info for rotating submodel
1045 if (asteroid_hit_info->edge_hit == 0) {
1046 model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
1047 }
1048
1049 // find position in submodel RF of light object at collison
1050 vec3d int_light_pos, diff;
1051 vm_vec_sub(&diff, mc.p1, mc.p0);
1052 vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
1053 model_instance_find_world_point(&asteroid_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
1054 }
1055 }
1056 // Don't look at this submodel again
1057 smi->collision_checked = true;
1058 }
1059
1060 }
1061
1062 // Now complete base model collision checks that do not take into account rotating submodels.
1063 mc.flags = copy_flags;
1064 *mc.p0 = copy_p0;
1065 *mc.p1 = copy_p1;
1066 mc.orient = &heavy_obj->orient;
1067
1068 // usual ship_ship collision test
1069 if ( model_collide(&mc) ) {
1070 // check if this is the earliest hit
1071 if (mc.hit_dist < asteroid_hit_info->hit_time) {
1072 mc_ret_val = 1;
1073
1074 set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
1075
1076 // get collision normal if not edge hit
1077 if (asteroid_hit_info->edge_hit == 0) {
1078 model_instance_find_world_dir(&asteroid_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
1079 }
1080
1081 // find position in submodel RF of light object at collison
1082 vec3d diff;
1083 vm_vec_sub(&diff, mc.p1, mc.p0);
1084 vm_vec_scale_add(&asteroid_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
1085
1086 }
1087 }
1088 }
1089
1090 } else {
1091 // Asteroid is heavier obj
1092 mc.model_instance_num = Asteroids[num].model_instance_num;
1093 mc.model_num = Asteroid_info[Asteroids[num].asteroid_type].model_num[asteroid_subtype]; // Fill in the model to check
1094 mc.orient = &pasteroid->orient; // The object's orient
1095 mc.radius = model_get_core_radius(Ship_info[Ships[pship_obj->instance].ship_info_index].model_num);
1096
1097 // check for collision between asteroid model and ship sphere
1098 mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE);
1099
1100 mc_ret_val = model_collide(&mc);
1101
1102 if (mc_ret_val) {
1103 set_hit_struct_info(asteroid_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
1104
1105 // set normal if not edge hit
1106 if ( !asteroid_hit_info->edge_hit ) {
1107 vm_vec_unrotate(&asteroid_hit_info->collision_normal, &mc.hit_normal, &heavy->orient);
1108 }
1109
1110 // find position in submodel RF of light object at collison
1111 vec3d diff;
1112 vm_vec_sub(&diff, mc.p1, mc.p0);
1113 vm_vec_scale_add(&asteroid_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
1114
1115 }
1116 }
1117
1118 // check if the hit point is beyond the clip plane if the ship is warping.
1119 if (mc_ret_val) {
1120 WarpEffect* warp_effect = nullptr;
1121 ship* shipp = &Ships[pship_obj->instance];
1122
1123 // this is extremely confusing but mc.hit_point_world isn't actually in world coords
1124 // everything above was calculated relative to the heavy's position
1125 vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos;
1126 if ((shipp->is_arriving()) && (shipp->warpin_effect != nullptr))
1127 warp_effect = shipp->warpin_effect;
1128 else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect != nullptr))
1129 warp_effect = shipp->warpout_effect;
1130
1131 if (warp_effect != nullptr && point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect))
1132 mc_ret_val = 0;
1133 }
1134
1135
1136 if ( mc_ret_val ) {
1137
1138 // SET PHYSICS PARAMETERS
1139 // already have (hitpos - heavy) and light_cm_pos
1140
1141 // get r_heavy and r_light
1142 asteroid_hit_info->r_heavy = asteroid_hit_info->hit_pos;
1143 vm_vec_sub(&asteroid_hit_info->r_light, &asteroid_hit_info->hit_pos, &asteroid_hit_info->light_collision_cm_pos);
1144
1145 // set normal for edge hit
1146 if ( asteroid_hit_info->edge_hit ) {
1147 vm_vec_copy_normalize(&asteroid_hit_info->collision_normal, &asteroid_hit_info->r_light);
1148 vm_vec_negate(&asteroid_hit_info->collision_normal);
1149 }
1150
1151 // get world hitpos
1152 vm_vec_add(hitpos, &asteroid_hit_info->heavy->pos, &asteroid_hit_info->r_heavy);
1153
1154 return 1;
1155 } else {
1156 // no hit
1157 return 0;
1158 }
1159 }
1160
asteroid_render(object * obj,model_draw_list * scene)1161 void asteroid_render(object * obj, model_draw_list *scene)
1162 {
1163 if (Asteroids_enabled) {
1164 int num;
1165 asteroid *asp;
1166
1167 num = obj->instance;
1168
1169 Assert((num >= 0) && (num < MAX_ASTEROIDS));
1170 asp = &Asteroids[num];
1171
1172 Assert( asp->flags & AF_USED );
1173
1174 model_clear_instance( Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype]);
1175
1176 model_render_params render_info;
1177
1178 render_info.set_object_number( OBJ_INDEX(obj) );
1179 render_info.set_flags(MR_IS_ASTEROID);
1180
1181 model_render_queue(&render_info, scene, Asteroid_info[asp->asteroid_type].model_num[asp->asteroid_subtype], &obj->orient, &obj->pos); // Replace MR_NORMAL with 0x07 for big yellow blobs
1182 }
1183 }
1184
1185 /**
1186 * Create a normalized vector generally in the direction from *hitpos to other_obj->pos
1187 */
asc_get_relvec(vec3d * relvec,object * other_obj,vec3d * hitpos)1188 static void asc_get_relvec(vec3d *relvec, object *other_obj, vec3d *hitpos)
1189 {
1190 vec3d tvec, rand_vec;
1191 int count = 0;
1192
1193 vm_vec_normalized_dir(&tvec, &other_obj->pos, hitpos);
1194
1195 // Try up to three times to get a good vector.
1196 while (count++ < 3) {
1197 vm_vec_rand_vec_quick(&rand_vec);
1198 vm_vec_add(relvec, &tvec, &rand_vec);
1199 float mag = vm_vec_mag_quick(relvec);
1200 if ((mag > 0.2f) && (mag < 1.7f))
1201 break;
1202 }
1203
1204 vm_vec_normalize_quick(relvec);
1205 }
1206
1207 /**
1208 * Return multiplier on asteroid radius for fireball
1209 */
asteroid_get_fireball_scale_multiplier(int num)1210 static float asteroid_get_fireball_scale_multiplier(int num)
1211 {
1212 if (Asteroids[num].flags & AF_USED) {
1213 asteroid_info *asip = &Asteroid_info[Asteroids[num].asteroid_type];
1214
1215 if (asip->fireball_radius_multiplier >= 0) {
1216 return asip->fireball_radius_multiplier;
1217 } else {
1218 switch(Asteroids[num].asteroid_type) {
1219 case ASTEROID_TYPE_LARGE:
1220 return 1.5f;
1221 break;
1222
1223 default:
1224 return 1.0f;
1225 break;
1226 }
1227 }
1228 }
1229
1230 Int3(); // this should not happen. asteroid should be used.
1231 return 1.0f;
1232 }
1233
1234
1235 /**
1236 * Create asteroid explosion
1237 * @return expected time for explosion anim to last, in seconds
1238 */
asteroid_create_explosion(object * objp)1239 static float asteroid_create_explosion(object *objp)
1240 {
1241 int fireball_objnum;
1242 float explosion_life, fireball_scale_multiplier;
1243 asteroid_info *asip = &Asteroid_info[Asteroids[objp->instance].asteroid_type];
1244
1245 int fireball_type = fireball_asteroid_explosion_type(asip);
1246 if (fireball_type < 0) {
1247 fireball_type = FIREBALL_ASTEROID;
1248 }
1249
1250 if (fireball_type >= Num_fireball_types) {
1251 Warning(LOCATION, "Invalid fireball type %i specified for an asteroid, only %i fireball types are defined.", fireball_type, Num_fireball_types);
1252
1253 return 0;
1254 }
1255
1256 fireball_scale_multiplier = asteroid_get_fireball_scale_multiplier(objp->instance);
1257
1258 fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), objp->radius*fireball_scale_multiplier, false, &objp->phys_info.vel );
1259 if ( fireball_objnum > -1 ) {
1260 explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
1261 } else {
1262 explosion_life = 0.0f;
1263 }
1264
1265 return explosion_life;
1266 }
1267
1268 /**
1269 * Play sound when asteroid explodes
1270 */
asteroid_explode_sound(object * objp,int type,int play_loud)1271 static void asteroid_explode_sound(object *objp, int type, int play_loud)
1272 {
1273 gamesnd_id sound_index;
1274 float range_factor = 1.0f; // how many times sound should traver farther than normal
1275
1276 if (type % NUM_DEBRIS_SIZES <= 1)
1277 {
1278 sound_index = gamesnd_id(GameSounds::ASTEROID_EXPLODE_SMALL);
1279 range_factor = 5.0;
1280 }
1281 else
1282 {
1283 sound_index = gamesnd_id(GameSounds::ASTEROID_EXPLODE_LARGE);
1284 range_factor = 10.0f;
1285 }
1286
1287 Assert(sound_index.isValid());
1288
1289 if ( !play_loud ) {
1290 range_factor = 1.0f;
1291 }
1292
1293 snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &Eye_position, objp->radius, NULL, 0, 1.0f, SND_PRIORITY_MUST_PLAY, NULL, range_factor );
1294 }
1295
1296 /**
1297 * Do the area effect for an asteroid exploding
1298 *
1299 * @param asteroid_objp object pointer to asteroid causing explosion
1300 */
asteroid_do_area_effect(object * asteroid_objp)1301 static void asteroid_do_area_effect(object *asteroid_objp)
1302 {
1303 object *ship_objp;
1304 float damage, blast;
1305 ship_obj *so;
1306 asteroid *asp;
1307 asteroid_info *asip;
1308
1309 asp = &Asteroids[asteroid_objp->instance];
1310 asip = &Asteroid_info[asp->asteroid_type];
1311
1312 if ( asip->damage <= 0 ) { // do a quick out if there is no damage to apply
1313 return;
1314 }
1315
1316 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1317 ship_objp = &Objects[so->objnum];
1318
1319 // don't blast navbuoys
1320 if ( ship_get_SIF(ship_objp->instance)[Ship::Info_Flags::Navbuoy] ) {
1321 continue;
1322 }
1323
1324 if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
1325 continue;
1326
1327 ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
1328 weapon_area_apply_blast(NULL, ship_objp, &asteroid_objp->pos, blast, 0);
1329 } // end for
1330 }
1331
1332 /**
1333 * Upon asteroid asteroid_obj being hit. Apply damage and maybe make it break into smaller asteroids.
1334 *
1335 * @param pasteroid_obj pointer to asteroid object getting hit
1336 * @param other_obj object that hit asteroid, can be NULL if asteroid hit by area effect
1337 * @param hitpos world position asteroid was hit, can be NULL if hit by area effect
1338 * @param damage amount of damage to apply to asteroid
1339 */
asteroid_hit(object * pasteroid_obj,object * other_obj,vec3d * hitpos,float damage)1340 void asteroid_hit( object * pasteroid_obj, object * other_obj, vec3d * hitpos, float damage )
1341 {
1342 float explosion_life;
1343 asteroid *asp;
1344
1345 asp = &Asteroids[pasteroid_obj->instance];
1346
1347 if (pasteroid_obj->flags[Object::Object_Flags::Should_be_dead]){
1348 return;
1349 }
1350
1351 if ( MULTIPLAYER_MASTER ){
1352 send_asteroid_hit( pasteroid_obj, other_obj, hitpos, damage );
1353 }
1354
1355 pasteroid_obj->hull_strength -= damage;
1356
1357 if (pasteroid_obj->hull_strength < 0.0f) {
1358 if ( asp->final_death_time <= 0 ) {
1359 int play_loud_collision = 0;
1360
1361 explosion_life = asteroid_create_explosion(pasteroid_obj);
1362
1363 asteroid_explode_sound(pasteroid_obj, asp->asteroid_type, play_loud_collision);
1364 asteroid_do_area_effect(pasteroid_obj);
1365
1366 asp->final_death_time = timestamp( fl2i(explosion_life*1000.0f)/5 ); // Wait till 30% of vclip time before breaking the asteroid up.
1367 if ( hitpos ) {
1368 asp->death_hit_pos = *hitpos;
1369 } else {
1370 asp->death_hit_pos = pasteroid_obj->pos;
1371 // randomize hit pos a bit, otherwise we will get a NULL vector when trying to find direction to toss child asteroids
1372 vec3d rand_vec;
1373 vm_vec_rand_vec_quick(&rand_vec);
1374 vm_vec_add2(&asp->death_hit_pos, &rand_vec);
1375 }
1376 }
1377 } else if ( other_obj ) {
1378 if ( other_obj->type == OBJ_WEAPON ) {
1379 weapon_info *wip;
1380 wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
1381 // If the weapon didn't play any impact animation, play custom asteroid impact animation
1382 if (!wip->impact_weapon_expl_effect.isValid()) {
1383 particle::create( hitpos, &vmd_zero_vector, 0.0f, Asteroid_impact_explosion_radius, particle::PARTICLE_BITMAP, Asteroid_impact_explosion_ani );
1384 }
1385 }
1386 }
1387
1388 // evaluate any relevant player scoring implications
1389 scoring_eval_hit(pasteroid_obj,other_obj);
1390 }
1391
1392 /**
1393 * De-init asteroids, called from ::game_level_close()
1394 */
asteroid_level_close()1395 void asteroid_level_close()
1396 {
1397 int i;
1398
1399 for (i=0; i<MAX_ASTEROIDS; i++) {
1400 if (Asteroids[i].flags & AF_USED) {
1401 Asteroids[i].flags &= ~AF_USED;
1402 Assert(Asteroids[i].objnum >=0 && Asteroids[i].objnum < MAX_OBJECTS);
1403 Objects[Asteroids[i].objnum].flags.set(Object::Object_Flags::Should_be_dead);
1404 }
1405 }
1406
1407 Asteroid_field.num_initial_asteroids=0;
1408 }
1409
1410 DCF_BOOL2(asteroids, Asteroids_enabled, "enables or disables asteroids", "Usage: asteroids [bool]\nTurns asteroid system on/off. If nothing passed, then toggles it.\n");
1411
1412
1413 /**
1414 * Return the number of active asteroids
1415 */
asteroid_count()1416 int asteroid_count()
1417 {
1418 return Num_asteroids;
1419 }
1420
1421 /**
1422 * See if asteroid should split up.
1423 * We delay splitting up to allow the explosion animation to play for a bit.
1424 */
asteroid_maybe_break_up(object * pasteroid_obj)1425 static void asteroid_maybe_break_up(object *pasteroid_obj)
1426 {
1427 asteroid *asp;
1428 asteroid_info *asip;
1429
1430 asp = &Asteroids[pasteroid_obj->instance];
1431 asip = &Asteroid_info[asp->asteroid_type];
1432
1433 if ( timestamp_elapsed(asp->final_death_time) ) {
1434 vec3d relvec, vfh, tvec;
1435 bool skip = false;
1436 bool hooked = Script_system.IsActiveAction(CHA_DEATH);
1437
1438 if (hooked) {
1439 Script_system.SetHookObject("Self", pasteroid_obj);
1440 skip = Script_system.IsConditionOverride(CHA_DEATH, pasteroid_obj);
1441 }
1442 if (!skip)
1443 {
1444 pasteroid_obj->flags.set(Object::Object_Flags::Should_be_dead);
1445
1446 // multiplayer clients won't go through the following code. asteroid_sub_create will send
1447 // a create packet to the client in the above named function
1448 // if the second condition isn't true it's just debris, and debris doesn't break up
1449 if ( !MULTIPLAYER_CLIENT && (asp->asteroid_type <= ASTEROID_TYPE_LARGE)) {
1450 if (asip->split_info.empty()) {
1451 switch (asp->asteroid_type) {
1452 case ASTEROID_TYPE_SMALL:
1453 break;
1454 case ASTEROID_TYPE_MEDIUM:
1455 asc_get_relvec(&relvec, pasteroid_obj, &asp->death_hit_pos);
1456 asteroid_sub_create(pasteroid_obj, ASTEROID_TYPE_SMALL, &relvec);
1457
1458 vm_vec_normalized_dir(&vfh, &pasteroid_obj->pos, &asp->death_hit_pos);
1459 vm_vec_copy_scale(&tvec, &vfh, 2.0f);
1460 vm_vec_sub2(&tvec, &relvec);
1461 asteroid_sub_create(pasteroid_obj, ASTEROID_TYPE_SMALL, &tvec);
1462
1463 break;
1464 case ASTEROID_TYPE_LARGE:
1465 asc_get_relvec(&relvec, pasteroid_obj, &asp->death_hit_pos);
1466 asteroid_sub_create(pasteroid_obj, ASTEROID_TYPE_MEDIUM, &relvec);
1467
1468 vm_vec_normalized_dir(&vfh, &pasteroid_obj->pos, &asp->death_hit_pos);
1469 vm_vec_copy_scale(&tvec, &vfh, 2.0f);
1470 vm_vec_sub2(&tvec, &relvec);
1471 asteroid_sub_create(pasteroid_obj, ASTEROID_TYPE_MEDIUM, &tvec);
1472
1473 while (frand() > 0.6f) {
1474 vec3d rvec, tvec2;
1475 vm_vec_rand_vec_quick(&rvec);
1476 vm_vec_scale_add(&tvec2, &vfh, &rvec, 0.75f);
1477 asteroid_sub_create(pasteroid_obj, ASTEROID_TYPE_SMALL, &tvec2);
1478 }
1479 break;
1480
1481 default: // this isn't going to happen.. really
1482 break;
1483 }
1484 } else {
1485 SCP_vector<int> roids_to_create;
1486 SCP_vector<asteroid_split_info>::iterator split;
1487 for (split = asip->split_info.begin(); split != asip->split_info.end(); ++split) {
1488 int num_roids = split->min;
1489 int num_roids_var = split->max - split->min;
1490
1491 if (num_roids_var > 0)
1492 num_roids += Random::next(num_roids_var);
1493
1494 if (num_roids > 0)
1495 for (int i=0; i<num_roids; i++)
1496 roids_to_create.push_back(split->asteroid_type);
1497 }
1498
1499 random_shuffle(roids_to_create.begin(), roids_to_create.end());
1500
1501 size_t total_roids = roids_to_create.size();
1502 for (size_t i = 0; i < total_roids; i++) {
1503 vec3d dir_vec,final_vec;
1504 vec3d parent_vel,hit_rel_vec;
1505
1506 // The roid directions are picked from the so-called
1507 // "golden section spiral" to prevent them from
1508 // clustering and thus clipping
1509
1510 float inc = PI * (3.0f - sqrt(5.0f));
1511 float offset = 2.0f / total_roids;
1512
1513 float y = i * offset - 1 + (offset / 2);
1514 float r = sqrt(1.0f - y*y);
1515 float phi = i * inc;
1516
1517 dir_vec.xyz.x = cosf(phi)*r;
1518 dir_vec.xyz.y = sinf(phi)*r;
1519 dir_vec.xyz.z = y;
1520
1521 // Randomize the direction a bit
1522 vec3d tempv = dir_vec;
1523 vm_vec_random_cone(&dir_vec, &tempv, (360.0f / total_roids / 2));
1524
1525 // Make the roid inherit half of parent's directional movement
1526 if (!IS_VEC_NULL(&pasteroid_obj->phys_info.vel)) {
1527 vm_vec_copy_normalize(&parent_vel, &pasteroid_obj->phys_info.vel);
1528 } else {
1529 vm_vec_rand_vec_quick(&parent_vel);
1530 }
1531 vm_vec_scale(&parent_vel, 0.5f);
1532
1533 // Make the hit position affect the direction, but only a little
1534 vm_vec_sub(&hit_rel_vec, &pasteroid_obj->pos, &asp->death_hit_pos);
1535 vm_vec_normalize(&hit_rel_vec);
1536 vm_vec_scale(&hit_rel_vec, 0.25f);
1537
1538 vm_vec_avg3(&final_vec, &parent_vel, &hit_rel_vec, &dir_vec);
1539 vm_vec_normalize(&final_vec);
1540
1541 asteroid_sub_create(pasteroid_obj, roids_to_create[i], &final_vec);
1542 }
1543 }
1544 }
1545 asp->final_death_time = timestamp(-1);
1546 }
1547 if (hooked) {
1548 Script_system.RunCondition(CHA_DEATH, pasteroid_obj);
1549 Script_system.RemHookVar("Self");
1550 }
1551 }
1552 }
1553
asteroid_test_collide(object * pasteroid_obj,object * pship_obj,mc_info * mc,bool lazy=false)1554 static void asteroid_test_collide(object *pasteroid_obj, object *pship_obj, mc_info *mc, bool lazy = false)
1555 {
1556 float asteroid_ray_dist;
1557 vec3d asteroid_fvec, terminus;
1558
1559 // See if ray from asteroid intersects bounding box of escort ship
1560 asteroid_ray_dist = vm_vec_mag_quick(&pasteroid_obj->phys_info.desired_vel) * ASTEROID_MIN_COLLIDE_TIME;
1561 asteroid_fvec = pasteroid_obj->phys_info.desired_vel;
1562
1563 if(IS_VEC_NULL_SQ_SAFE(&asteroid_fvec)){
1564 terminus = pasteroid_obj->pos;
1565 } else {
1566 vm_vec_normalize(&asteroid_fvec);
1567 vm_vec_scale_add(&terminus, &pasteroid_obj->pos, &asteroid_fvec, asteroid_ray_dist);
1568 }
1569
1570 Assert(pship_obj->type == OBJ_SHIP);
1571
1572 mc->model_instance_num = Ships[pship_obj->instance].model_instance_num;
1573 mc->model_num = Ship_info[Ships[pship_obj->instance].ship_info_index].model_num; // Fill in the model to check
1574 mc->orient = &pship_obj->orient; // The object's orientation
1575 mc->pos = &pship_obj->pos; // The object's position
1576 mc->p0 = &pasteroid_obj->pos; // Point 1 of ray to check
1577 mc->p1 = &terminus; // Point 2 of ray to check
1578 if (lazy) {
1579 mc->flags = MC_CHECK_MODEL | MC_ONLY_BOUND_BOX;
1580 } else {
1581 mc->flags = MC_CHECK_MODEL | MC_CHECK_SPHERELINE;
1582 }
1583 mc->radius = pasteroid_obj->radius;
1584
1585 model_collide(mc);
1586
1587 // PVS-Studio says that mc->p1 will become invalid... on the one hand, it will; on the other hand, we won't use it again; on the *other* other hand, we should keep things proper in case of future changes
1588 mc->p1 = NULL;
1589 }
1590
1591 /**
1592 * Test if asteroid will collide with escort ship within ASTEROID_MIN_COLLIDE_TIME seconds
1593 *
1594 * @return !0 if the asteroid will collide with the escort
1595 */
asteroid_will_collide(object * pasteroid_obj,object * escort_objp)1596 static int asteroid_will_collide(object *pasteroid_obj, object *escort_objp)
1597 {
1598 mc_info mc;
1599 mc_info_init(&mc);
1600
1601 asteroid_test_collide(pasteroid_obj, escort_objp, &mc);
1602
1603 if ( !mc.num_hits ) {
1604 return 0;
1605 }
1606
1607 return 1;
1608 }
1609
1610 /**
1611 * Warn if asteroid on collision path with ship
1612 * @return !0 if we should warn about asteroid hitting ship, otherwise return 0
1613 */
asteroid_valid_ship_to_warn_collide(ship * shipp)1614 static int asteroid_valid_ship_to_warn_collide(ship *shipp)
1615 {
1616 if ( !(Ship_info[shipp->ship_info_index].is_big_or_huge()) ) {
1617 return 0;
1618 }
1619
1620 if ( shipp->flags[Ship::Ship_Flags::Dying] || shipp->flags[Ship::Ship_Flags::Depart_warp] ) {
1621 return 0;
1622 }
1623
1624 // Goober5000 used to be if teams were unequal and player was not traitor, but this works for allies not on your team
1625 if ( iff_x_attacks_y(Player_ship->team, shipp->team) ) {
1626 return 0;
1627 }
1628
1629 return 1;
1630 }
1631
1632 /**
1633 * See if asteroid will collide with a large ship on the escort list in the next
1634 * ASTEROID_MIN_COLLIDE_TIME seconds.
1635 */
asteroid_update_collide_flag(object * asteroid_objp)1636 static void asteroid_update_collide_flag(object *asteroid_objp)
1637 {
1638 int i, num_escorts, escort_objnum, will_collide=0;
1639 ship *escort_shipp;
1640 asteroid *asp;
1641
1642 asp = &Asteroids[asteroid_objp->instance];
1643 asp->collide_objnum = -1;
1644 asp->collide_objsig = -1;
1645
1646 // multiplayer dogfight
1647 if(MULTI_DOGFIGHT){
1648 return;
1649 }
1650
1651 num_escorts = hud_escort_num_ships_on_list();
1652
1653 if ( num_escorts <= 0 ) {
1654 return;
1655 }
1656
1657 for ( i = 0; i < num_escorts; i++ ) {
1658 escort_objnum = hud_escort_return_objnum(i);
1659 if ( escort_objnum >= 0 ) {
1660 escort_shipp = &Ships[Objects[escort_objnum].instance];
1661 if ( asteroid_valid_ship_to_warn_collide(escort_shipp) ) {
1662 will_collide = asteroid_will_collide(asteroid_objp, &Objects[escort_objnum]);
1663 if ( will_collide ) {
1664 asp->collide_objnum = escort_objnum;
1665 asp->collide_objsig = Objects[escort_objnum].signature;
1666 }
1667 }
1668 }
1669 }
1670 }
1671
1672 /**
1673 * Ensure that the collide objnum for the asteroid is still valid
1674 */
asteroid_verify_collide_objnum(asteroid * asp)1675 static void asteroid_verify_collide_objnum(asteroid *asp)
1676 {
1677 if ( asp->collide_objnum >= 0 ) {
1678 if ( Objects[asp->collide_objnum].signature != asp->collide_objsig ) {
1679 asp->collide_objnum = -1;
1680 asp->collide_objsig = -1;
1681 }
1682 }
1683 }
1684
asteroid_process_post(object * obj)1685 void asteroid_process_post(object * obj)
1686 {
1687 if (Asteroids_enabled) {
1688 int num;
1689 num = obj->instance;
1690
1691 asteroid *asp = &Asteroids[num];
1692
1693 // Only wrap if active field
1694 if (Asteroid_field.field_type == FT_ACTIVE) {
1695 if ( timestamp_elapsed(asp->check_for_wrap) ) {
1696 asteroid_maybe_reposition(obj, &Asteroid_field);
1697 asp->check_for_wrap = timestamp(ASTEROID_CHECK_WRAP_TIMESTAMP);
1698 }
1699 }
1700
1701 asteroid_verify_collide_objnum(asp);
1702
1703 if ( timestamp_elapsed(asp->check_for_collide) ) {
1704 asteroid_update_collide_flag(obj);
1705 asp->check_for_collide = timestamp(ASTEROID_UPDATE_COLLIDE_TIMESTAMP);
1706 }
1707
1708 asteroid_maybe_break_up(obj);
1709 }
1710 }
1711
1712 /**
1713 * Find the object number of the object the asteroid is about to impact
1714 * @return the object number that the asteroid is about to impact
1715 */
asteroid_collide_objnum(object * asteroid_objp)1716 int asteroid_collide_objnum(object *asteroid_objp)
1717 {
1718 return Asteroids[asteroid_objp->instance].collide_objnum;
1719 }
1720
1721 /**
1722 * Find the time until asteroid will collide with object
1723 * @return the time until the asteroid will impact its collide_objnum
1724 */
asteroid_time_to_impact(object * asteroid_objp)1725 float asteroid_time_to_impact(object *asteroid_objp)
1726 {
1727 float time=-1.0f, total_dist, speed;
1728 asteroid *asp;
1729 mc_info mc;
1730 mc_info_init(&mc);
1731
1732 asp = &Asteroids[asteroid_objp->instance];
1733
1734 if ( asp->collide_objnum < 0 ) {
1735 return time;
1736 }
1737
1738 asteroid_test_collide(asteroid_objp, &Objects[asp->collide_objnum], &mc, true);
1739
1740 if ( mc.num_hits ) {
1741 total_dist = vm_vec_dist(&mc.hit_point_world, &asteroid_objp->pos) - asteroid_objp->radius;
1742 if ( total_dist < 0 ) {
1743 total_dist = 0.0f;
1744 }
1745 speed = vm_vec_mag(&asteroid_objp->phys_info.vel);
1746 time = total_dist/speed;
1747 }
1748
1749 return time;
1750 }
1751
1752 /**
1753 * Read in a single asteroid section from asteroid.tbl
1754 */
asteroid_parse_section(asteroid_info * asip)1755 static void asteroid_parse_section(asteroid_info *asip)
1756 {
1757 required_string("$Name:");
1758 stuff_string(asip->name, F_NAME, NAME_LENGTH);
1759
1760 required_string( "$POF file1:" );
1761 stuff_string(asip->pof_files[0], F_NAME, MAX_FILENAME_LEN);
1762
1763 required_string( "$POF file2:" );
1764 stuff_string(asip->pof_files[1], F_NAME, MAX_FILENAME_LEN);
1765
1766 if ( (stristr(asip->name, "Asteroid") != NULL) ) {
1767 required_string( "$POF file3:" );
1768 stuff_string(asip->pof_files[2], F_NAME, MAX_FILENAME_LEN);
1769 }
1770
1771 required_string("$Detail distance:");
1772 asip->num_detail_levels = (int)stuff_int_list(asip->detail_distance, MAX_ASTEROID_DETAIL_LEVELS, RAW_INTEGER_TYPE);
1773
1774 required_string("$Max Speed:");
1775 stuff_float(&asip->max_speed);
1776
1777 if(optional_string("$Damage Type:")) {
1778 char buf[NAME_LENGTH];
1779 stuff_string(buf, F_NAME, NAME_LENGTH);
1780 asip->damage_type_idx_sav = damage_type_add(buf);
1781 asip->damage_type_idx = asip->damage_type_idx_sav;
1782 }
1783
1784 if(optional_string("$Explosion Animations:")){
1785 int temp[MAX_FIREBALL_TYPES];
1786 auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
1787 asip->explosion_bitmap_anims.clear();
1788 asip->explosion_bitmap_anims.insert(asip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
1789 }
1790
1791 if(optional_string("$Explosion Radius Mult:"))
1792 stuff_float(&asip->fireball_radius_multiplier);
1793
1794 required_string("$Expl inner rad:");
1795 stuff_float(&asip->inner_rad);
1796
1797 required_string("$Expl outer rad:");
1798 stuff_float(&asip->outer_rad);
1799
1800 required_string("$Expl damage:");
1801 stuff_float(&asip->damage);
1802
1803 required_string("$Expl blast:");
1804 stuff_float(&asip->blast);
1805
1806 required_string("$Hitpoints:");
1807 stuff_float(&asip->initial_asteroid_strength);
1808
1809 while(optional_string("$Split:")) {
1810 int split_type;
1811
1812 stuff_int(&split_type);
1813
1814 if (split_type>=0 && split_type<NUM_DEBRIS_SIZES) {
1815 asteroid_split_info new_split;
1816
1817 new_split.asteroid_type = split_type;
1818
1819 if(optional_string("+Min:"))
1820 stuff_int(&new_split.min);
1821 else
1822 new_split.min = 0;
1823
1824 if(optional_string("+Max:"))
1825 stuff_int(&new_split.max);
1826 else
1827 new_split.max = 0;
1828
1829 asip->split_info.push_back( new_split );
1830 } else
1831 Warning(LOCATION, "Invalid asteroid reference %i used for $Split in asteroids table, ignoring.", split_type);
1832 }
1833
1834 if (optional_string("$Spawn Weight:")) {
1835 stuff_float(&asip->spawn_weight);
1836 if (asip->spawn_weight <= 0.0f) {
1837 Warning(LOCATION, "Spawn weight for asteroid '%s' must be greater than 0", asip->name);
1838 asip->spawn_weight = 1.0f;
1839 }
1840 } else {
1841 switch (Asteroid_info.size() % NUM_DEBRIS_SIZES)
1842 {
1843 case ASTEROID_TYPE_SMALL:
1844 asip->spawn_weight = SMALL_DEBRIS_WEIGHT;
1845 break;
1846 case ASTEROID_TYPE_MEDIUM:
1847 asip->spawn_weight = MEDIUM_DEBRIS_WEIGHT;
1848 break;
1849 case ASTEROID_TYPE_LARGE:
1850 asip->spawn_weight = LARGE_DEBRIS_WEIGHT;
1851 break;
1852 default:
1853 UNREACHABLE("Here be dragons");
1854 }
1855 }
1856 }
1857
1858 // changes the name to "[species] Debris" if it had a name like "[species] debris #"
maybe_change_asteroid_name(asteroid_info * asip)1859 void maybe_change_asteroid_name(asteroid_info* asip) {
1860
1861 SCP_string name = asip->name;
1862 size_t split = std::string::npos;
1863
1864 for (species_info species : Species_info) {
1865 if (name.compare(0, strlen(species.species_name), species.species_name) == 0) {
1866 split = strlen(species.species_name);
1867 break;
1868 }
1869 }
1870
1871 if (split == std::string::npos)
1872 return;
1873
1874 SCP_string remaining_name = name.substr(split + 1, name.length());
1875
1876 split = remaining_name.find(' ');
1877
1878 if (split == std::string::npos)
1879 return;
1880
1881 SCP_string debris = remaining_name.substr(0, split);
1882 SCP_string num = remaining_name.substr(split + 1, remaining_name.length());
1883
1884 if (stricmp(debris.c_str(), "Debris") != 0)
1885 return;
1886
1887 if (num.empty() || std::find_if(num.begin(), num.end(), [](char c) {
1888 return !std::isdigit(c, SCP_default_locale);
1889 }) != num.end())
1890 return;
1891
1892 // make sure this asteroid would correspond the 'species section' of the old style retail asteroids
1893 if (Asteroid_info.size() < NUM_DEBRIS_SIZES + Species_info.size() * NUM_DEBRIS_SIZES && Asteroid_info.size() >= NUM_DEBRIS_SIZES) {
1894 int idx = (int)(Asteroid_info.size()) / NUM_DEBRIS_SIZES - 1;
1895 strcpy_s(asip->name, Species_info[idx].species_name);
1896 strcat(asip->name, " ");
1897 strcat(asip->name, XSTR("debris", 348));
1898 }
1899 }
1900
1901 /**
1902 Read in data from asteroid.tbl into Asteroid_info[] array.
1903
1904 After this function is complete, the Asteroid_info[] array
1905 will have at least 3 generic asteroids plus whatever else
1906 special ones were added in the table. The generic asteroids are
1907 a set of small, medium, and large asteroids in that exact order.
1908
1909 Note that by saying "asteroid" this code is talking about
1910 the asteroids and the debris that make up asteroid fields
1911 and debris fields. Which means that these debris have nothing
1912 to do with the debris of ships that explode, however these
1913 are the same debris and asteroids that get flung at a ship
1914 that is being protected.
1915 */
asteroid_parse_tbl()1916 static void asteroid_parse_tbl()
1917 {
1918 char impact_ani_file[MAX_FILENAME_LEN];
1919
1920 try
1921 {
1922 read_file_text("asteroid.tbl", CF_TYPE_TABLES);
1923 reset_parse();
1924
1925 required_string("#Asteroid Types");
1926
1927 size_t tally = 0;
1928
1929 #ifndef NDEBUG
1930 SCP_vector<SCP_string> parsed_asteroids;
1931 #endif
1932
1933 // parse and tally each asteroid
1934 while (required_string_either("#End", "$Name:"))
1935 {
1936 asteroid_info new_asteroid;
1937
1938 asteroid_parse_section(&new_asteroid);
1939
1940 maybe_change_asteroid_name(&new_asteroid);
1941
1942 #ifndef NDEBUG
1943 SCP_string msg;
1944 msg.append("Parsing asteroid: '");
1945 msg.append(new_asteroid.name);
1946 msg.append("' as a '");
1947 msg.append((tally >= NUM_DEBRIS_SIZES) ? "generic" : "special");
1948 msg.append("'");
1949 switch (tally % NUM_DEBRIS_SIZES) {
1950 case ASTEROID_TYPE_SMALL:
1951 msg.append(" small\n");
1952 break;
1953 case ASTEROID_TYPE_MEDIUM:
1954 msg.append(" medium\n");
1955 break;
1956 case ASTEROID_TYPE_LARGE:
1957 msg.append(" large\n");
1958 break;
1959 default:
1960 Error(LOCATION, "Get a coder! Math has broken!\n"
1961 "Important numbers:\n"
1962 "\ttally: " SIZE_T_ARG "\n"
1963 "\tNUM_DEBRIS_SIZES: %d\n",
1964 tally, NUM_DEBRIS_SIZES
1965 );
1966 msg.append(" unknown\n");
1967 }
1968 parsed_asteroids.push_back(msg);
1969 #endif
1970 Asteroid_info.push_back(new_asteroid);
1971 tally++;
1972 }
1973 required_string("#End");
1974
1975 Asteroid_impact_explosion_ani = -1;
1976 required_string("$Impact Explosion:");
1977 stuff_string(impact_ani_file, F_NAME, MAX_FILENAME_LEN);
1978
1979 if (VALID_FNAME(impact_ani_file)) {
1980 int num_frames;
1981 Asteroid_impact_explosion_ani = bm_load_animation(impact_ani_file, &num_frames, nullptr, nullptr, nullptr, true);
1982 }
1983
1984 required_string("$Impact Explosion Radius:");
1985 stuff_float(&Asteroid_impact_explosion_radius);
1986
1987 if (optional_string("$Briefing Icon Closeup Model:")) {
1988 stuff_string(Asteroid_icon_closeup_model, F_NAME, NAME_LENGTH);
1989 }
1990 else {
1991 strcpy_s(Asteroid_icon_closeup_model, Asteroid_info[ASTEROID_TYPE_LARGE].pof_files[0]); // magic file from retail
1992 }
1993
1994 if (optional_string("$Briefing Icon Closeup Position:")) {
1995 stuff_vec3d(&Asteroid_icon_closeup_position);
1996 }
1997 else {
1998 vm_vec_make(&Asteroid_icon_closeup_position, 0.0f, 0.0f, -334.0f); // magic numbers from retail
1999 }
2000
2001 if (optional_string("$Briefing Icon Closeup Zoom:")) {
2002 stuff_float(&Asteroid_icon_closeup_zoom);
2003 }
2004 else {
2005 Asteroid_icon_closeup_zoom = 0.5f; // magic number from retail
2006 }
2007 }
2008 catch (const parse::ParseException& e)
2009 {
2010 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "asteroid.tbl", e.what()));
2011 return;
2012 }
2013 }
2014
2015 /**
2016 * Return number of asteroids expected to collide with a ship.
2017 */
count_incident_asteroids()2018 static int count_incident_asteroids()
2019 {
2020 object *asteroid_objp;
2021 int count;
2022
2023 count = 0;
2024
2025 for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp !=END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
2026 if (asteroid_objp->type == OBJ_ASTEROID ) {
2027 asteroid *asp = &Asteroids[asteroid_objp->instance];
2028
2029 if ( asp->target_objnum >= 0 ) {
2030 count++;
2031 }
2032 }
2033 }
2034
2035 return count;
2036 }
2037
2038 /**
2039 * Pick object to throw asteroids at.
2040 * Pick any capital or big ship inside the bounds of the asteroid field.
2041 */
set_asteroid_throw_objnum()2042 static int set_asteroid_throw_objnum()
2043 {
2044 if (Asteroid_field.num_initial_asteroids < 1)
2045 return -1;
2046
2047 ship_obj *so;
2048 object *ship_objp;
2049
2050 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
2051 ship_objp = &Objects[so->objnum];
2052 float radius = ship_objp->radius*2.0f;
2053
2054 if (Ship_info[Ships[ship_objp->instance].ship_info_index].is_big_or_huge()) {
2055 if (ship_objp->pos.xyz.x + radius > Asteroid_field.min_bound.xyz.x)
2056 if (ship_objp->pos.xyz.y + radius > Asteroid_field.min_bound.xyz.y)
2057 if (ship_objp->pos.xyz.z + radius > Asteroid_field.min_bound.xyz.z)
2058 if (ship_objp->pos.xyz.x - radius < Asteroid_field.max_bound.xyz.x)
2059 if (ship_objp->pos.xyz.y - radius < Asteroid_field.max_bound.xyz.y)
2060 if (ship_objp->pos.xyz.z - radius < Asteroid_field.max_bound.xyz.z)
2061 if (!asteroid_in_inner_bound(&Asteroid_field, &ship_objp->pos, radius))
2062 return so->objnum;
2063 }
2064 }
2065 return -1;
2066
2067 }
2068
asteroid_frame()2069 void asteroid_frame()
2070 {
2071 if (Num_asteroids < 1)
2072 return;
2073
2074 // Only throw if active field
2075 if (Asteroid_field.field_type == FT_PASSIVE) {
2076 return;
2077 }
2078
2079 Asteroid_throw_objnum = set_asteroid_throw_objnum();
2080
2081 maybe_throw_asteroid(count_incident_asteroids());
2082 }
2083
2084 /**
2085 * Called once, at game start. Do any one-time initializations here
2086 */
asteroid_init()2087 void asteroid_init()
2088 {
2089 asteroid_parse_tbl();
2090 }
2091
2092 extern int Cmdline_targetinfo;
2093
2094 /**
2095 * Draw brackets around on-screen asteroids that are about to collide, otherwise draw an offscreen indicator
2096 */
asteroid_show_brackets()2097 void asteroid_show_brackets()
2098 {
2099 vertex asteroid_vertex;
2100 object *asteroid_objp, *player_target;
2101 asteroid *asp;
2102
2103 // get pointer to player target, so we don't need to take OBJ_INDEX() of asteroid_objp to compare to target_objnum
2104 if ( Player_ai->target_objnum >= 0 ) {
2105 player_target = &Objects[Player_ai->target_objnum];
2106 } else {
2107 player_target = NULL;
2108 }
2109
2110 for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp !=END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
2111 if (asteroid_objp->type != OBJ_ASTEROID ) {
2112 continue;
2113 }
2114
2115 asp = &Asteroids[asteroid_objp->instance];
2116
2117 if ( asp->collide_objnum < 0 ) {
2118 continue;
2119 }
2120
2121 if ( asteroid_objp == player_target ) {
2122 continue;
2123 }
2124
2125 g3_rotate_vertex(&asteroid_vertex,&asteroid_objp->pos);
2126 g3_project_vertex(&asteroid_vertex);
2127
2128 if ( Cmdline_targetinfo ) {
2129 hud_target_add_display_list(asteroid_objp, &asteroid_vertex, &asteroid_objp->pos, 0, NULL, NULL, TARGET_DISPLAY_DIST | TARGET_DISPLAY_LEAD);
2130 } else {
2131 hud_target_add_display_list(asteroid_objp, &asteroid_vertex, &asteroid_objp->pos, 0, NULL, NULL, TARGET_DISPLAY_DIST);
2132 }
2133 }
2134 }
2135
2136 /**
2137 * Target the closest danger asteroid to the player
2138 */
asteroid_target_closest_danger()2139 void asteroid_target_closest_danger()
2140 {
2141 object *asteroid_objp, *closest_asteroid_objp = NULL;
2142 asteroid *asp;
2143 float dist, closest_dist = 999999.0f;
2144
2145 for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp !=END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
2146 if (asteroid_objp->type != OBJ_ASTEROID ) {
2147 continue;
2148 }
2149
2150 asp = &Asteroids[asteroid_objp->instance];
2151
2152 if ( asp->collide_objnum < 0 ) {
2153 continue;
2154 }
2155
2156 dist = vm_vec_dist_quick(&Player_obj->pos, &asteroid_objp->pos);
2157
2158 if ( dist < closest_dist ) {
2159 closest_dist = dist;
2160 closest_asteroid_objp = asteroid_objp;
2161 }
2162 }
2163
2164 if ( closest_asteroid_objp ) {
2165 set_target_objnum( Player_ai, OBJ_INDEX(closest_asteroid_objp) );
2166 }
2167 }
2168
asteroid_page_in()2169 void asteroid_page_in()
2170 {
2171 if (Asteroid_field.num_initial_asteroids > 0 ) {
2172 int i, j, k;
2173
2174 nprintf(( "Paging", "Paging in asteroids\n" ));
2175
2176
2177 // max of MAX_ACTIVE_DEBRIS_TYPES possible debris field models
2178 for (i=0; i<MAX_ACTIVE_DEBRIS_TYPES; i++) {
2179 asteroid_info *asip;
2180
2181 if (Asteroid_field.debris_genre == DG_ASTEROID) {
2182 // asteroid
2183 Assert(i < NUM_DEBRIS_SIZES);
2184 asip = &Asteroid_info[i];
2185 } else {
2186 // ship debris - always full until empty
2187 if (Asteroid_field.field_debris_type[i] != -1) {
2188 asip = &Asteroid_info[Asteroid_field.field_debris_type[i]];
2189 } else {
2190 break;
2191 }
2192 }
2193
2194
2195 for (k=0; k<NUM_DEBRIS_POFS; k++) {
2196
2197 // SHIP DEBRIS - use subtype 0
2198 if (Asteroid_field.debris_genre == DG_SHIP) {
2199 if (k > 0) {
2200 break;
2201 }
2202 } else {
2203 // ASTEROID DEBRIS - use subtype (Asteroid_field.field_debris_type[] != -1)
2204 if (Asteroid_field.field_debris_type[k] == -1) {
2205 continue;
2206 }
2207 }
2208
2209 if (asip->model_num[k] < 0)
2210 continue;
2211
2212 asip->modelp[k] = model_get(asip->model_num[k]);
2213
2214 // Page in textures
2215 for (j=0; j<asip->modelp[k]->n_textures; j++ ) {
2216 asip->modelp[k]->maps[j].PageIn();
2217 }
2218
2219 }
2220 }
2221 }
2222 }
2223