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