1 /*
2 PROJECTILES.C
3 
4 	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5 	and the "Aleph One" developers.
6 
7 	This program is free software; you can redistribute it and/or modify
8 	it under the terms of the GNU General Public License as published by
9 	the Free Software Foundation; either version 3 of the License, or
10 	(at your option) any later version.
11 
12 	This program is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU General Public License for more details.
16 
17 	This license is contained in the file "COPYING",
18 	which is included with this source code; it is available online at
19 	http://www.gnu.org/licenses/gpl.html
20 
21 Friday, May 27, 1994 10:54:44 AM
22 
23 Friday, July 15, 1994 12:28:36 PM
24 	added maximum range.
25 Monday, February 6, 1995 2:46:08 AM  (Jason')
26 	persistent/virulent projectiles; media detonation effects.
27 Tuesday, June 13, 1995 12:07:00 PM  (Jason)
28 	non-melee projectiles must start above media.
29 Monday, June 26, 1995 8:52:32 AM  (Jason)
30 	bouncing projectiles
31 Tuesday, August 1, 1995 3:31:08 PM  (Jason)
32 	guided projectiles bite on low levels
33 Thursday, August 17, 1995 9:35:13 AM  (Jason)
34 	wandering projectiles
35 Thursday, October 5, 1995 10:19:48 AM  (Jason)
36 	until we fix it, calling translate_projectile() is too time consuming on high levels.
37 Friday, October 6, 1995 8:35:04 AM  (Jason)
38 	simpler guided projectile model.
39 
40 Feb 4, 2000 (Loren Petrich):
41 	Added effects of "penetrates media boundary" flag;
42 	assuming it to be like "penetrates media" flag until I can figure out
43 	the difference between the two.
44 
45 	Changed halt() to assert(false) for better debugging
46 
47 	Determined that "penetrates media boundary" means
48 	making a splash but nevertheless continuing
49 
50 Feb 6, 2000 (Loren Petrich):
51 	Added access to size of projectile-definition structure.
52 
53 Feb 9, 2000 (Loren Petrich):
54 	Put in handling of "penetrates media boundary" flag
55 
56 Feb 13, 2000 (Loren Petrich):
57 	Fixed bug in setting will_go_through when hitting a media boundary;
58 	this banishes the floating-mine effect.
59 
60 Feb 14, 2000 (Loren Petrich):
61 	Added workaround for Pfhorte bug: if there is no polygon on the other side
62 	of a non-solid line, then treat the line as if it was solid.
63 
64 Feb 16, 2000 (Loren Petrich):
65 	Improved the handling of "penetrates media boundary" -- if the rocket has that,
66 	it will explode on the surface, and then afterward on something it hits.
67 
68 Feb 17, 2000 (Loren Petrich):
69 	Fixed stuff near GUESS_HYPOTENUSE() to be long-distance-friendly
70 
71 Feb 19, 2000 (Loren Petrich):
72 	Added growable lists of indices of objects to be checked for collisions
73 
74 Jul 1, 2000 (Loren Petrich):
75 	Added Benad's changes
76 
77 Aug 30, 2000 (Loren Petrich):
78 	Added stuff for unpacking and packing
79 
80 Oct 13, 2000 (Loren Petrich)
81 	Converted the intersected-objects list into a Standard Template Library vector
82 */
83 
84 #include "cseries.h"
85 #include "map.h"
86 #include "interface.h"
87 #include "effects.h"
88 #include "monsters.h"
89 #include "projectiles.h"
90 #include "player.h"
91 #include "scenery.h"
92 #include "media.h"
93 #include "SoundManager.h"
94 #include "items.h"
95 
96 // LP additions
97 #include "dynamic_limits.h"
98 #include "Packing.h"
99 
100 #include "lua_script.h"
101 
102 /*
103 //translate_projectile() must set _projectile_hit_landscape bit
104 */
105 
106 /* ---------- constants */
107 
108 enum
109 {
110 	GRAVITATIONAL_ACCELERATION= 1, // per tick
111 
112 	WANDER_MAGNITUDE= WORLD_ONE/TICKS_PER_SECOND,
113 
114 	MINIMUM_REBOUND_VELOCITY= GRAVITATIONAL_ACCELERATION*TICKS_PER_SECOND/3
115 };
116 
117 enum /* things the projectile can hit in detonate_projectile() */
118 {
119 	_hit_nothing,
120 	_hit_floor,
121 	_hit_media,
122 	_hit_ceiling,
123 	_hit_wall,
124 	_hit_monster,
125 	_hit_scenery
126 };
127 
128 #define MAXIMUM_PROJECTILE_ELEVATION (QUARTER_CIRCLE/2)
129 
130 /* ---------- structures */
131 
132 /* ---------- private prototypes */
133 
134 /* ---------- globals */
135 
136 /* import projectile definition structures, constants and globals */
137 #include "projectile_definitions.h"
138 
139 /* if copy-protection fails, these are replaced externally with the rocket and the rifle bullet, respectively */
140 short alien_projectile_override= NONE;
141 short human_projectile_override= NONE;
142 
143 // LP addition: growable list of intersected objects
144 static vector<short> IntersectedObjects;
145 
146 /* ---------- private prototypes */
147 
148 static short adjust_projectile_type(world_point3d *origin, short polygon_index, short type,
149 	short owner_index, short owner_type, short intended_target_index, _fixed damage_scale);
150 
151 static void update_guided_projectile(short projectile_index);
152 
153 /*static*/ projectile_definition *get_projectile_definition(
154 	short type);
155 
156 /* ---------- code */
157 
get_projectile_data(const short projectile_index)158 projectile_data *get_projectile_data(
159 	const short projectile_index)
160 {
161 	struct projectile_data *projectile =  GetMemberWithBounds(projectiles,projectile_index,MAXIMUM_PROJECTILES_PER_MAP);
162 
163 	vassert(projectile, csprintf(temporary, "projectile index #%d is out of range", projectile_index));
164 	vassert(SLOT_IS_USED(projectile), csprintf(temporary, "projectile index #%d (%p) is unused", projectile_index, (void*)projectile));
165 
166 	return projectile;
167 }
168 
169 // LP change: moved down here to use the projectile definitions
get_projectile_definition(short type)170 projectile_definition *get_projectile_definition(
171 	short type)
172 {
173 	projectile_definition *definition = GetMemberWithBounds(projectile_definitions,type,NUMBER_OF_PROJECTILE_TYPES);
174 	vassert(definition, csprintf(temporary, "projectile type #%d is out of range", type));
175 
176 	return definition;
177 }
178 
179 /* false means don�t fire this (it�s in a floor or ceiling or outside of the map), otherwise
180 	the monster that was intersected first (or NONE) is returned in target_index */
preflight_projectile(world_point3d * origin,short origin_polygon_index,world_point3d * destination,angle delta_theta,short type,short owner,short owner_type,short * obstruction_index)181 bool preflight_projectile(
182 	world_point3d *origin,
183 	short origin_polygon_index,
184 	world_point3d *destination,
185 	angle delta_theta,
186 	short type,
187 	short owner,
188 	short owner_type,
189 	short *obstruction_index)
190 {
191 	bool legal_projectile= false;
192 	struct projectile_definition *definition= get_projectile_definition(type);
193 
194 	(void) (delta_theta);
195 
196 	/* will be used when we truly preflight projectiles */
197 	(void) (owner_type);
198 
199 	if (origin_polygon_index!=NONE)
200 	{
201 		world_distance dx= destination->x-origin->x, dy= destination->y-origin->y;
202 		angle elevation= arctangent(isqrt(dx*dx + dy*dy), destination->z-origin->z);
203 
204 		if (elevation<MAXIMUM_PROJECTILE_ELEVATION || elevation>FULL_CIRCLE-MAXIMUM_PROJECTILE_ELEVATION)
205 		{
206 			struct polygon_data *origin_polygon= get_polygon_data(origin_polygon_index);
207 
208 			// LP note: "penetrates media boundary" means "hit media surface and continue";
209 			// it will act like "penetrates_media" here
210 			// Added idiot-proofing to media data
211 			media_data *media = get_media_data(origin_polygon->media_index);
212 			if (origin->z>origin_polygon->floor_height && origin->z<origin_polygon->ceiling_height &&
213 				(origin_polygon->media_index==NONE || definition->flags&(_penetrates_media) || (media ? origin->z>media->height : true)))
214 			{
215 				/* make sure it hits something */
216 				uint16 flags= translate_projectile(type, origin, origin_polygon_index, destination, (short *) NULL, owner, obstruction_index, 0, true, NONE);
217 
218 				*obstruction_index= (flags&_projectile_hit_monster) ? get_object_data(*obstruction_index)->permutation : NONE;
219 				legal_projectile= true;
220 			}
221 		}
222 	}
223 
224 	return legal_projectile;
225 }
226 
227 // pointless if not an area-of-effect weapon
detonate_projectile(world_point3d * origin,short polygon_index,short type,short owner_index,short owner_type,_fixed damage_scale)228 void detonate_projectile(
229 	world_point3d *origin,
230 	short polygon_index,
231 	short type,
232 	short owner_index,
233 	short owner_type,
234 	_fixed damage_scale)
235 {
236 	struct projectile_definition *definition= get_projectile_definition(type);
237 	struct damage_definition *damage= &definition->damage;
238 
239 	damage->scale= damage_scale;
240 	damage_monsters_in_radius(NONE, owner_index, owner_type, origin, polygon_index,
241 		definition->area_of_effect, damage, NONE);
242 	if (definition->detonation_effect!=NONE) new_effect(origin, polygon_index, definition->detonation_effect, 0);
243 	L_Call_Projectile_Detonated(type, owner_index, polygon_index, *origin);
244 }
245 
new_projectile(world_point3d * origin,short polygon_index,world_point3d * _vector,angle delta_theta,short type,short owner_index,short owner_type,short intended_target_index,_fixed damage_scale)246 short new_projectile(
247 	world_point3d *origin,
248 	short polygon_index,
249 	world_point3d *_vector,
250 	angle delta_theta, /* ��theta is added (in a circle) to the angle before firing */
251 	short type,
252 	short owner_index,
253 	short owner_type,
254 	short intended_target_index, /* can be NONE */
255 	_fixed damage_scale)
256 {
257 	struct projectile_definition *definition;
258 	struct projectile_data *projectile;
259 	short projectile_index;
260 
261 	type= adjust_projectile_type(origin, polygon_index, type, owner_index, owner_type, intended_target_index, damage_scale);
262 	definition= get_projectile_definition(type);
263 
264 	for (projectile_index= 0, projectile= projectiles; projectile_index<MAXIMUM_PROJECTILES_PER_MAP;
265 		++projectile_index, ++projectile)
266 	{
267 		if (SLOT_IS_FREE(projectile))
268 		{
269 			angle facing, elevation;
270 			short object_index;
271 			struct object_data *object;
272 
273 			facing= arctangent(_vector->x, _vector->y);
274 			elevation= arctangent(isqrt(_vector->x*_vector->x+_vector->y*_vector->y), _vector->z);
275 			if (delta_theta)
276 			{
277 				if (!(definition->flags&_no_horizontal_error)) facing= normalize_angle(facing+global_random()%(2*delta_theta)-delta_theta);
278 				if (!(definition->flags&_no_vertical_error)) elevation= (definition->flags&_positive_vertical_error) ? normalize_angle(elevation+global_random()%delta_theta) :
279 					normalize_angle(elevation+global_random()%(2*delta_theta)-delta_theta);
280 			}
281 
282 			object_index= new_map_object3d(origin, polygon_index, definition->collection==NONE ? NONE : BUILD_DESCRIPTOR(definition->collection, definition->shape), facing);
283 			if (object_index!=NONE)
284 			{
285 				object= get_object_data(object_index);
286 
287 				projectile->type= (definition->flags&_alien_projectile) ?
288 					(alien_projectile_override==NONE ? type : alien_projectile_override) :
289 					(human_projectile_override==NONE ? type : human_projectile_override);
290 				projectile->object_index= object_index;
291 				projectile->owner_index= owner_index;
292 				projectile->target_index= intended_target_index;
293 				projectile->owner_type= owner_type;
294 				projectile->flags= 0;
295 				projectile->gravity= 0;
296 				projectile->ticks_since_last_contrail= projectile->contrail_count= 0;
297 				projectile->elevation= elevation;
298 				projectile->distance_travelled= 0;
299 				projectile->damage_scale= damage_scale;
300 				MARK_SLOT_AS_USED(projectile);
301 
302 				SET_OBJECT_OWNER(object, _object_is_projectile);
303 				object->sound_pitch= definition->sound_pitch;
304 				L_Call_Projectile_Created(projectile_index);
305 			}
306 			else
307 			{
308 				projectile_index= NONE;
309 			}
310 
311 			break;
312 		}
313 	}
314 	if (projectile_index==MAXIMUM_PROJECTILES_PER_MAP) projectile_index= NONE;
315 
316 	return projectile_index;
317 }
318 
319 /* assumes �t==1 tick */
move_projectiles(void)320 void move_projectiles(
321 	void)
322 {
323 	struct projectile_data *projectile;
324 	short projectile_index;
325 
326 	for (projectile_index=0,projectile=projectiles;projectile_index<MAXIMUM_PROJECTILES_PER_MAP;++projectile_index,++projectile)
327 	{
328 		if (SLOT_IS_USED(projectile))
329 		{
330 			struct object_data *object= get_object_data(projectile->object_index);
331 
332 //			if (!OBJECT_IS_INVISIBLE(object))
333 			{
334 				struct projectile_definition *definition= get_projectile_definition(projectile->type);
335 				short old_polygon_index= object->polygon;
336 				world_point3d new_location, old_location;
337 				short obstruction_index, new_polygon_index;
338 
339 				new_location= old_location= object->location;
340 
341 				/* update our object�s animation */
342 				animate_object(projectile->object_index);
343 
344 				/* if we�re supposed to end when our animation loops, check this condition */
345 				if ((definition->flags&_stop_when_animation_loops) && (GET_OBJECT_ANIMATION_FLAGS(object)&_obj_last_frame_animated))
346 				{
347 					remove_projectile(projectile_index);
348 				}
349 				else
350 				{
351 					world_distance speed= definition->speed;
352 					uint32 adjusted_definition_flags = 0;
353 					uint16 flags;
354 
355 					/* base alien projectile speed on difficulty level */
356 					if (definition->flags&_alien_projectile)
357 					{
358 						switch (dynamic_world->game_information.difficulty_level)
359 						{
360 							case _wuss_level: speed-= speed>>3; break;
361 							case _easy_level: speed-= speed>>4; break;
362 							case _major_damage_level: speed+= speed>>3; break;
363 							case _total_carnage_level: speed+= speed>>2; break;
364 						}
365 					}
366 
367 					/* if this is a guided projectile with a valid target, update guidance system */
368 					if ((definition->flags&_guided) && projectile->target_index!=NONE && (dynamic_world->tick_count&1)) update_guided_projectile(projectile_index);
369 
370 					if (PROJECTILE_HAS_CROSSED_MEDIA_BOUNDARY(projectile)) adjusted_definition_flags= _penetrates_media;
371 
372 					/* move the projectile and check for collisions; if we didn�t detonate move the
373 						projectile and check to see if we need to leave a contrail */
374 					if ((definition->flags&_affected_by_half_gravity) && (dynamic_world->tick_count&1)) projectile->gravity-= GRAVITATIONAL_ACCELERATION;
375 					if (definition->flags&_affected_by_gravity) projectile->gravity-= GRAVITATIONAL_ACCELERATION;
376 					if (definition->flags&_doubly_affected_by_gravity) projectile->gravity-= 2*GRAVITATIONAL_ACCELERATION;
377 					if (film_profile.m1_low_gravity_projectiles && static_world->environment_flags&_environment_low_gravity && static_world->environment_flags&_environment_m1_weapons)
378 					{
379 						projectile->gravity /= 2;
380 					}
381 					new_location.z+= projectile->gravity;
382 					translate_point3d(&new_location, speed, object->facing, projectile->elevation);
383 					if (definition->flags&_vertical_wander) new_location.z+= (global_random()&1) ? WANDER_MAGNITUDE : -WANDER_MAGNITUDE;
384 					if (definition->flags&_horizontal_wander) translate_point3d(&new_location, (global_random()&1) ? WANDER_MAGNITUDE : -WANDER_MAGNITUDE, NORMALIZE_ANGLE(object->facing+QUARTER_CIRCLE), 0);
385 					if (film_profile.infinity_smg)
386 					{
387 						definition->flags ^= adjusted_definition_flags;
388 					}
389 					flags= translate_projectile(projectile->type, &old_location, object->polygon, &new_location, &new_polygon_index, projectile->owner_index, &obstruction_index, 0, false, projectile_index);
390 					if (film_profile.infinity_smg)
391 					{
392 						definition->flags ^= adjusted_definition_flags;
393 					}
394 
395 					// LP change: set up for penetrating media boundary
396 					bool will_go_through = false;
397 
398 					if (flags&_projectile_hit)
399 					{
400 						if ((flags&_projectile_hit_floor) && (definition->flags&_rebounds_from_floor) &&
401 							projectile->gravity<-MINIMUM_REBOUND_VELOCITY)
402 						{
403 							play_object_sound(projectile->object_index, definition->rebound_sound);
404 							projectile->gravity= - projectile->gravity + (projectile->gravity>>2); /* 0.75 */
405 						}
406 						else
407 						{
408  							short monster_obstruction_index= (flags&_projectile_hit_monster) ? get_object_data(obstruction_index)->permutation : NONE;
409 							bool destroy_persistent_projectile= false;
410 
411 							if (flags&_projectile_hit_scenery) damage_scenery(obstruction_index);
412 
413 							/* cause damage, if we can */
414 							if (!PROJECTILE_HAS_CAUSED_DAMAGE(projectile))
415 							{
416 								struct damage_definition *damage= &definition->damage;
417 
418 								damage->scale= projectile->damage_scale;
419 								if (definition->flags&_becomes_item_on_detonation)
420 								{
421 									if (monster_obstruction_index==NONE)
422 									{
423 										struct object_location location;
424 
425 										location.p= object->location, location.p.z= 0;
426 										location.polygon_index= object->polygon;
427 										location.yaw= location.pitch= 0;
428 										location.flags= 0;
429 										// START Benad
430 										// Found it!
431 										// With new_item(), current_item_count[item] increases, but not
432 										// with try_and_add_player_item(). So reverse the effect of new_item in advance.
433 										dynamic_world->current_item_count[projectile->permutation]--;
434 										// END Benad
435 										new_item(&location, projectile->permutation);
436 
437 										destroy_persistent_projectile= true;
438 									}
439 									else
440 									{
441 										if(MONSTER_IS_PLAYER(get_monster_data(monster_obstruction_index)))
442 										{
443 											short player_obstruction_index= monster_index_to_player_index(monster_obstruction_index);
444 											destroy_persistent_projectile= try_and_add_player_item(player_obstruction_index, projectile->permutation);
445 										}
446 									}
447 								}
448 								else
449 								{
450 									if (definition->area_of_effect)
451 									{
452 										damage_monsters_in_radius(monster_obstruction_index, projectile->owner_index, projectile->owner_type, &old_location, object->polygon, definition->area_of_effect, damage, projectile_index);
453 									}
454 									else
455 									{
456 										if (monster_obstruction_index!=NONE) damage_monster(monster_obstruction_index, projectile->owner_index, projectile->owner_type, &old_location, damage, projectile_index);
457 									}
458 								}
459 							}
460 
461 							if ((definition->flags&_persistent) && !destroy_persistent_projectile)
462 							{
463 								SET_PROJECTILE_DAMAGE_STATUS(projectile, true);
464 							}
465 							else
466 							{
467 								short detonation_effect= definition->detonation_effect;
468 
469 								if (monster_obstruction_index!=NONE)
470 								{
471 									if (definition->flags&_bleeding_projectile)
472 									{
473 										detonation_effect= get_monster_impact_effect(monster_obstruction_index);
474 									}
475 									if (definition->flags&_melee_projectile)
476 									{
477 										short new_detonation_effect= get_monster_melee_impact_effect(monster_obstruction_index);
478 										if (new_detonation_effect!=NONE) detonation_effect= new_detonation_effect;
479 									}
480 								}
481 								if (flags&_projectile_hit_media)
482 								{
483 									get_media_detonation_effect(get_polygon_data(obstruction_index)->media_index, definition->media_detonation_effect, &detonation_effect);
484 									// LP addition: check if projectile will hit media and continue (PMB flag)
485 									// set will_go_through for later processing
486 									if (film_profile.a1_smg)
487 									{
488 										// Be careful about parentheses here!
489 										will_go_through = (definition->flags&_penetrates_media_boundary) != 0;
490 										// Push the projectile upward or downward, if necessary
491 										if (will_go_through) {
492 											if (projectile->elevation == 0) {}
493 											else if (projectile->elevation < HALF_CIRCLE) new_location.z++;
494 											else if (projectile->elevation > HALF_CIRCLE) new_location.z--;
495 										}
496 									}
497 								}
498 								if (film_profile.a1_smg)
499 								{
500 									// LP addition: don't detonate if going through media
501 									// if PMB is set; otherwise, detonate if doing so.
502 									// Some of the later routines may set both "hit landscape" and "hit media",
503 									// so be careful.
504 									if (flags&_projectile_hit_landscape && !(flags&_projectile_hit_media)) detonation_effect= NONE;
505 								}
506 								else
507 								{
508 									if (flags&_projectile_hit_landscape) detonation_effect = NONE;
509 								}
510 
511 								if (detonation_effect!=NONE) new_effect(&new_location, new_polygon_index, detonation_effect, object->facing);
512 								L_Call_Projectile_Detonated(projectile->type, projectile->owner_index, new_polygon_index, new_location);
513 
514 								if (!film_profile.infinity_smg || (!(definition->flags&_penetrates_media_boundary) || !(flags&_projectile_hit_media)))
515 								{
516 									if ((definition->flags&_persistent_and_virulent) && !destroy_persistent_projectile && monster_obstruction_index!=NONE)
517 									{
518 										bool reassign_projectile = true;
519 										if (film_profile.prevent_dead_projectile_owners)
520 										{
521 											monster_data *monster = get_monster_data(monster_obstruction_index);
522 											reassign_projectile = MONSTER_IS_PLAYER(monster) || !MONSTER_IS_DYING(monster);
523 										}
524 										if (reassign_projectile)
525 											projectile->owner_index= monster_obstruction_index; /* keep going, but don�t hit this target again */
526 									}
527 									// LP addition: don't remove a projectile that will hit media and continue (PMB flag)
528 									else if (!will_go_through)
529 									{
530 										remove_projectile(projectile_index);
531 									}
532 								}
533 								else if (film_profile.infinity_smg)
534 								{
535 									SET_PROJECTILE_CROSSED_MEDIA_BOUNDARY_STATUS(projectile, true);
536 								}
537 							}
538 						}
539 					}
540 					// Move the projectile if it hit nothing or it will go through media surface
541 					if (!(flags&_projectile_hit) || will_go_through)
542 					// else
543 					{
544 						/* move to the new_polygon_index */
545 						translate_map_object(projectile->object_index, &new_location, new_polygon_index);
546 
547 						/* should we leave a contrail at our old location? */
548 						if ((projectile->ticks_since_last_contrail+=1)>=definition->ticks_between_contrails)
549 						{
550 							if (definition->maximum_contrails==NONE || projectile->contrail_count<definition->maximum_contrails)
551 							{
552 								projectile->contrail_count+= 1;
553 								projectile->ticks_since_last_contrail= 0;
554 								if (definition->contrail_effect!=NONE) new_effect(&old_location, old_polygon_index, definition->contrail_effect, object->facing);
555 							}
556 						}
557 
558 						if ((flags&_flyby_of_current_player) && !PROJECTILE_HAS_MADE_A_FLYBY(projectile))
559 						{
560 							SET_PROJECTILE_FLYBY_STATUS(projectile, true);
561 							play_object_sound(projectile->object_index, definition->flyby_sound);
562 						}
563 
564 						/* if we have a maximum range and we have exceeded it then remove the projectile */
565 						if (definition->maximum_range!=NONE)
566 						{
567 							if ((projectile->distance_travelled+= speed)>=definition->maximum_range)
568 							{
569 								remove_projectile(projectile_index);
570 							}
571 						}
572 					}
573 				}
574 			}
575 		}
576 	}
577 }
578 
remove_projectile(short projectile_index)579 void remove_projectile(
580 	short projectile_index)
581 {
582 	struct projectile_data *projectile= get_projectile_data(projectile_index);
583 	L_Invalidate_Projectile(projectile_index);
584 	remove_map_object(projectile->object_index);
585 	MARK_SLOT_AS_FREE(projectile);
586 }
587 
remove_all_projectiles(void)588 void remove_all_projectiles(
589 	void)
590 {
591 	struct projectile_data *projectile;
592 	short projectile_index;
593 
594 	for (projectile_index=0,projectile=projectiles;projectile_index<MAXIMUM_PROJECTILES_PER_MAP;++projectile_index,++projectile)
595 	{
596 		if (SLOT_IS_USED(projectile)) remove_projectile(projectile_index);
597 	}
598 }
599 
600 /* when a given monster is deactivated (or killed), all his active projectiles should become
601 	ownerless (or all sorts of neat little problems can occur) */
orphan_projectiles(short monster_index)602 void orphan_projectiles(
603 	short monster_index)
604 {
605 	struct projectile_data *projectile;
606 	short projectile_index;
607 
608 	/* first, adjust all current projectile's .owner fields */
609 	for (projectile_index=0,projectile=projectiles;projectile_index<MAXIMUM_PROJECTILES_PER_MAP;++projectile_index,++projectile)
610 	{
611 		if (projectile->owner_index==monster_index) projectile->owner_index= NONE;
612 		if (projectile->target_index==monster_index) projectile->target_index= NONE;
613 	}
614 }
615 
load_projectile_sounds(short projectile_type)616 void load_projectile_sounds(
617 	short projectile_type)
618 {
619 	if (projectile_type!=NONE)
620 	{
621 		struct projectile_definition *definition= get_projectile_definition(projectile_type);
622 
623 		SoundManager::instance()->LoadSound(definition->flyby_sound);
624 		SoundManager::instance()->LoadSound(definition->rebound_sound);
625 	}
626 }
627 
mark_projectile_collections(short projectile_type,bool loading)628 void mark_projectile_collections(
629 	short projectile_type,
630 	bool loading)
631 {
632 	if (projectile_type!=NONE)
633 	{
634 		struct projectile_definition *definition= get_projectile_definition(projectile_type);
635 
636 		/* If the projectile is not invisible */
637 		if (definition->collection!=NONE)
638 		{
639 			/* mark the projectile collection */
640 			loading ? mark_collection_for_loading(definition->collection) : mark_collection_for_unloading(definition->collection);
641 		}
642 
643 		/* mark the projectile�s effect�s collection */
644 		mark_effect_collections(definition->detonation_effect, loading);
645 		mark_effect_collections(definition->contrail_effect, loading);
646 	}
647 }
648 
649 
drop_the_ball(world_point3d * origin,short polygon_index,short owner_index,short owner_type,short item_type)650 void drop_the_ball(
651 	world_point3d *origin,
652 	short polygon_index,
653 	short owner_index,
654 	short owner_type,
655 	short item_type)
656 {
657 	struct world_point3d _vector;
658 	short projectile_index;
659 
660 	_vector.x= _vector.y= _vector.z= 0;
661 	projectile_index= new_projectile(origin, polygon_index, &_vector, 0, _projectile_ball,
662 		owner_index, owner_type, NONE, FIXED_ONE);
663 	if (projectile_index!=NONE)
664 	{
665 		struct projectile_data *projectile= get_projectile_data(projectile_index);
666 		struct object_data *object= get_object_data(projectile->object_index);
667 
668 		projectile->permutation= item_type;
669 
670 		object->shape= get_item_shape(item_type);
671 	}
672 }
673 
674 /* ---------- private code */
675 
676 
adjust_projectile_type(world_point3d * origin,short polygon_index,short type,short owner_index,short owner_type,short intended_target_index,_fixed damage_scale)677 static short adjust_projectile_type(
678 	world_point3d *origin,
679 	short polygon_index,
680 	short type,
681 	short owner_index,
682 	short owner_type,
683 	short intended_target_index,
684 	_fixed damage_scale)
685 {
686 	struct projectile_definition *definition= get_projectile_definition(type);
687 	short media_index= get_polygon_data(polygon_index)->media_index;
688 
689 	(void) (owner_index);
690 	(void) (owner_type);
691 	(void) (intended_target_index);
692 	(void) (damage_scale);
693 
694 	if (media_index!=NONE)
695 	{
696 		// LP change: idiot-proofing
697 		media_data *media = get_media_data(media_index);
698 		if (media)
699 		{
700 			if (media->height>origin->z)
701 			{
702 				if (definition->media_projectile_promotion!=NONE) type= definition->media_projectile_promotion;
703 			}
704 		}
705 	}
706 
707 	return type;
708 }
709 
710 #define MAXIMUM_GUIDED_DELTA_YAW 8
711 #define MAXIMUM_GUIDED_DELTA_PITCH 6
712 
713 /* changes are at a rate of �1 angular unit per tick */
update_guided_projectile(short projectile_index)714 static void update_guided_projectile(
715 	short projectile_index)
716 {
717 	struct projectile_data *projectile= get_projectile_data(projectile_index);
718 	struct monster_data *target= get_monster_data(projectile->target_index);
719 	struct object_data *projectile_object= get_object_data(projectile->object_index);
720 	struct object_data *target_object= get_object_data(target->object_index);
721 	world_distance target_radius, target_height;
722 	world_point3d target_location;
723 
724 	get_monster_dimensions(projectile->target_index, &target_radius, &target_height);
725 	target_location= target_object->location;
726 	target_location.z+= target_height>>1;
727 
728 	switch (target_object->transfer_mode)
729 	{
730 		case _xfer_invisibility:
731 		case _xfer_subtle_invisibility:
732 			/* can�t hold lock on invisible targets unless on _total_carnage_level */
733 			if (dynamic_world->game_information.difficulty_level!=_total_carnage_level) break;
734 		default:
735 		{
736 			// LP change: made this long-distance-friendly
737 			int32 dx= int32(target_location.x) - int32(projectile_object->location.x);
738 			int32 dy= int32(target_location.y) - int32(projectile_object->location.y);
739 			world_distance dz= target_location.z - projectile_object->location.z;
740 			short delta_yaw= MAXIMUM_GUIDED_DELTA_YAW+_normal_level-dynamic_world->game_information.difficulty_level;
741 			short delta_pitch= MAXIMUM_GUIDED_DELTA_PITCH+_normal_level-dynamic_world->game_information.difficulty_level;
742 
743 			if (dx*sine_table[projectile_object->facing] - dy*cosine_table[projectile_object->facing] > 0)
744 			{
745 				// turn left
746 				delta_yaw= -delta_yaw;
747 			}
748 
749 			dx= ABS(dx), dy= ABS(dy);
750 			if (GUESS_HYPOTENUSE(dx, dy)*sine_table[projectile->elevation] - dz*cosine_table[projectile->elevation] > 0)
751 			{
752 				// turn down
753 				delta_pitch= -delta_pitch;
754 			}
755 
756 			projectile_object->facing= NORMALIZE_ANGLE(projectile_object->facing+delta_yaw);
757 			projectile->elevation= NORMALIZE_ANGLE(projectile->elevation+delta_pitch);
758 		}
759 	}
760 }
761 
translate_projectile(short type,world_point3d * old_location,short old_polygon_index,world_point3d * new_location,short * new_polygon_index,short owner_index,short * obstruction_index,short * last_line_index,bool preflight,short projectile_index)762 uint16 translate_projectile(
763 	short type,
764 	world_point3d *old_location,
765 	short old_polygon_index,
766 	world_point3d *new_location,
767 	short *new_polygon_index,
768 	short owner_index,
769 	short *obstruction_index,
770 	short *last_line_index,
771 	bool preflight,
772 	short projectile_index)
773 {
774 	struct projectile_definition *definition= get_projectile_definition(type);
775 	struct polygon_data *old_polygon;
776 	world_point3d intersection;
777 	world_distance media_height;
778 	short line_index;
779 	size_t intersected_object_count;
780 	short contact;
781 	uint16 flags= 0;
782 
783 	*obstruction_index= NONE;
784 
785 	contact= _hit_nothing;
786 	IntersectedObjects.clear();
787 	old_polygon= get_polygon_data(old_polygon_index);
788 	if (new_polygon_index) *new_polygon_index= old_polygon_index;
789 	do
790 	{
791 		// LP note: "penetrates media boundary" means "hit media surface and continue";
792 		// create a hack for enabling a projectile with this flag to continue
793 		// Idiot-proofing of media handling
794 		media_data *media = get_media_data(old_polygon->media_index);
795 		media_height= (old_polygon->media_index==NONE || definition->flags&_penetrates_media || !media) ? INT16_MIN : media->height;
796 
797 		// This flag says it can
798 		bool traveled_underneath = false;
799 		if (film_profile.a1_smg)
800 		{
801 			traveled_underneath = (definition->flags&_penetrates_media_boundary) && (old_location->z <= media_height);
802 		}
803 
804 		/* add this polygon�s monsters to our non-redundant list of possible intersections */
805 		possible_intersecting_monsters(&IntersectedObjects, GLOBAL_INTERSECTING_MONSTER_BUFFER_SIZE, old_polygon_index, true);
806 		intersected_object_count = IntersectedObjects.size();
807 
808  		line_index= find_line_crossed_leaving_polygon(old_polygon_index, (world_point2d *)old_location, (world_point2d *)new_location);
809 		if (line_index!=NONE)
810 		{
811 			/* we crossed a line: if the line is solid, we detonate immediately on the wall,
812 				otherwise we calculate our Z at the line and compare it to the ceiling and
813 				floor heights of the old and new polygon to see if we hit a wall between the
814 				polygons, or the floor or ceiling somewhere in the old polygon */
815 
816 			struct line_data *line= get_line_data(line_index);
817 
818 			find_line_intersection(&get_endpoint_data(line->endpoint_indexes[0])->vertex,
819 				&get_endpoint_data(line->endpoint_indexes[1])->vertex, old_location, new_location,
820 				&intersection);
821 
822 			// LP change: workaround for Pfhorte map bug: check to see if there is a polygon
823 			// on the other side
824 			short adjacent_polygon_index= find_adjacent_polygon(old_polygon_index, line_index);
825 			if ((!LINE_IS_SOLID(line) || LINE_HAS_TRANSPARENT_SIDE(line)) && (adjacent_polygon_index != NONE))
826 			{
827 				// short adjacent_polygon_index= find_adjacent_polygon(old_polygon_index, line_index);
828 
829 				struct polygon_data *adjacent_polygon= get_polygon_data(adjacent_polygon_index);
830 
831 				// LP change: if PMB is set, then a projectile can travel as if the liquid did not exist,
832 				// but it will be able to run into a media surface.
833 				// Here, test for whether the projectile is above the floor or the media surface;
834 				// ignore the latter test if PMB is set.
835 				if ((traveled_underneath || intersection.z>media_height) && intersection.z>old_polygon->floor_height)
836 				{
837 					// If PMB was set, check to see if the projectile hit the media surface.
838 					if ((!traveled_underneath || intersection.z<media_height) && intersection.z<old_polygon->ceiling_height)
839 					{
840 						if (intersection.z>adjacent_polygon->floor_height&&intersection.z<adjacent_polygon->ceiling_height)
841 						{
842 							if (!LINE_HAS_TRANSPARENT_SIDE(line) || (preflight && (definition->flags&(_usually_pass_transparent_side|_sometimes_pass_transparent_side))) ||
843 								((definition->flags&_usually_pass_transparent_side) && (global_random()&3)) ||
844 								((definition->flags&_sometimes_pass_transparent_side) && !(global_random()&3)))
845 							{
846 								/* no intersections, successfully entered new polygon */
847 								if (new_polygon_index) *new_polygon_index= adjacent_polygon_index;
848 								old_polygon_index= adjacent_polygon_index;
849 								old_polygon= adjacent_polygon;
850 							}
851 							else
852 							{
853 								/* hit and could not pass transparent texture */
854 								contact= _hit_wall;
855 							}
856 						}
857 						else
858 						{
859 							/* hit wall created by ceiling or floor of new polygon; test to see
860 								if we toggle a control panel */
861 							if (!preflight && (definition->flags&_can_toggle_control_panels)) try_and_toggle_control_panel(old_polygon_index, line_index, projectile_index);
862 							contact= _hit_wall;
863 						}
864 					}
865 					else
866 					{
867 						/* hit ceiling of old polygon */
868 						*obstruction_index= old_polygon_index;
869 						if (adjacent_polygon->ceiling_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape;
870 						// LP change: if PMB was set, check to see if hit media
871 						contact= (!traveled_underneath || old_polygon->ceiling_height<media_height) ? _hit_ceiling : _hit_media;
872 					}
873 				}
874 				else
875 				{
876 					/* hit floor or media of old polygon */
877 					*obstruction_index= old_polygon_index;
878 					if (adjacent_polygon->floor_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape;
879 					// LP change: suppress media-hit test only if PMB was set and the projectile was underneath the media
880 					contact= (traveled_underneath || old_polygon->floor_height>media_height) ? _hit_floor : _hit_media;
881 				}
882 			}
883 			else
884 			{
885 				/* hit wall created by solid line; test to see if we toggle a control panel */
886 				if (!preflight && (definition->flags&_can_toggle_control_panels)) try_and_toggle_control_panel(old_polygon_index, line_index, projectile_index);
887 				if (line_is_landscaped(old_polygon_index, line_index, intersection.z)) flags|= _projectile_hit_landscape;
888 				contact= _hit_wall;
889 			}
890 		}
891 		else
892 		{
893 			/* make sure we didn�t hit the ceiling or floor in this polygon */
894 			// LP change: if PMB is set, then a projectile can travel as if the liquid did not exist,
895 			// but it will be able to run into a media surface.
896 			// Here, test for whether the projectile is above the floor or the media surface;
897 			// ignore the latter test if PMB is set.
898 			if ((traveled_underneath || new_location->z>media_height) && new_location->z>old_polygon->floor_height)
899 			{
900 				// If PMB was set, check to see if the projectile hit the media surface.
901 				if ((!traveled_underneath || new_location->z<media_height) && new_location->z<old_polygon->ceiling_height)
902 				{
903 					/* we�re staying in this polygon and we�re finally done screwing around;
904 						the caller can look in *new_polygon_index to find out where we ended up */
905 				}
906 				else
907 				{
908 					/* hit ceiling of current polygon */
909 					*obstruction_index= old_polygon_index;
910 					if (old_polygon->ceiling_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape;
911 					// LP change: if PMB was set, check to see if hit media
912 					contact= (!traveled_underneath || old_polygon->ceiling_height<media_height) ? _hit_ceiling : _hit_media;
913 				}
914 			}
915 			else
916 			{
917 				/* hit floor of current polygon */
918 				*obstruction_index= old_polygon_index;
919 				if (old_polygon->floor_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape;
920 				// LP change: suppress media-hit test only if PMB was set and the projectile was underneath the media
921 				contact= (traveled_underneath || old_polygon->floor_height>media_height) ? _hit_floor : _hit_media;
922 			}
923 		}
924 	}
925 	while (line_index!=NONE&&contact==_hit_nothing);
926 
927 	/* ceilings and floor intersections still don�t have accurate intersection points, so calculate
928 		them */
929 	if (contact!=_hit_nothing)
930 	{
931 		switch (contact)
932 		{
933 			case _hit_media:
934 				find_floor_or_ceiling_intersection(media_height, old_location, new_location, &intersection);
935 				break;
936 			case _hit_floor:
937 				find_floor_or_ceiling_intersection(old_polygon->floor_height, old_location, new_location, &intersection);
938 				break;
939 			case _hit_ceiling:
940 				find_floor_or_ceiling_intersection(old_polygon->ceiling_height, old_location, new_location, &intersection);
941 				break;
942 		}
943 
944 		/* change new_location to the point of intersection with the ceiling, floor, or wall */
945 		*new_location= intersection;
946 
947 	}
948 
949 	/* check our object list and find the best intersection ... if we find an intersection at all,
950 		then we hit this before we hit the wall, because the object list is checked against the
951 		clipped new_location. */
952 	{
953 		world_distance best_intersection_distance = 0;
954 		world_distance distance_traveled;
955 		world_distance best_radius = 0;
956 		short best_intersection_object = NONE;
957 
958 		distance_traveled= distance2d((world_point2d *)old_location, (world_point2d *)new_location);
959 		for (size_t i=0;i<intersected_object_count;++i)
960 		{
961 			// LP change:
962 			struct object_data *object= get_object_data(IntersectedObjects[i]);
963 			int32 separation= point_to_line_segment_distance_squared((world_point2d *)&object->location,
964 				(world_point2d *)old_location, (world_point2d *)new_location);
965 			world_distance radius, height;
966 
967 			if (object->permutation!=owner_index) /* don�t hit ourselves */
968 			{
969 				int32 radius_squared;
970 
971 				switch (GET_OBJECT_OWNER(object))
972 				{
973 					case _object_is_monster: get_monster_dimensions(object->permutation, &radius, &height); break;
974 					case _object_is_scenery: get_scenery_dimensions(object->permutation, &radius, &height); break;
975 					default:
976 						assert(false);
977 						break;
978 				}
979 				radius_squared= (radius+definition->radius)*(radius+definition->radius);
980 
981 				if (separation<radius_squared) /* if we�re within radius^2 we passed through this monster */
982 				{
983 					world_distance distance= distance2d((world_point2d *)old_location, (world_point2d *)&object->location);
984 					world_distance projectile_z= distance_traveled ?
985 						old_location->z + (distance*(new_location->z-old_location->z))/distance_traveled :
986 						old_location->z;
987 
988 					if ((height>0 && projectile_z>=object->location.z && projectile_z<=object->location.z+height) ||
989 						(height<0 && projectile_z>=object->location.z+height && projectile_z<=object->location.z))
990 					{
991 						if (best_intersection_object==NONE || distance<best_intersection_distance)
992 						{
993 							// LP change:
994 							best_intersection_object= IntersectedObjects[i];
995 							best_intersection_distance= distance;
996 							best_radius= radius;
997 
998 							switch (GET_OBJECT_OWNER(object))
999 							{
1000 								case _object_is_monster: contact= _hit_monster; break;
1001 								case _object_is_scenery: contact= _hit_scenery; break;
1002 								default:
1003 									assert(false);
1004 									break;
1005 							}
1006 						}
1007 					}
1008 				}
1009 				else
1010 				{
1011 					if (GET_OBJECT_OWNER(object)==_object_is_monster && separation<12*radius_squared) /* if we�re within (x*radius)^2 we passed near this monster */
1012 					{
1013 						if (MONSTER_IS_PLAYER(get_monster_data(object->permutation)) &&
1014 							monster_index_to_player_index(object->permutation)==current_player_index)
1015 						{
1016 							flags|= _flyby_of_current_player;
1017 						}
1018 					}
1019 				}
1020 			}
1021 		}
1022 
1023 		if (best_intersection_object!=NONE) /* if we hit something, take it */
1024 		{
1025 			struct object_data *object= get_object_data(best_intersection_object);
1026 
1027 			*obstruction_index= best_intersection_object;
1028 
1029 			if (distance_traveled)
1030 			{
1031 				world_distance actual_distance_to_hit;
1032 
1033 				actual_distance_to_hit= distance2d((world_point2d *)old_location, (world_point2d *) &object->location);
1034 				actual_distance_to_hit-= best_radius;
1035 
1036 				new_location->x= old_location->x + (actual_distance_to_hit*(new_location->x-old_location->x))/distance_traveled;
1037 				new_location->y= old_location->y + (actual_distance_to_hit*(new_location->y-old_location->y))/distance_traveled;
1038 				new_location->z= old_location->z + (actual_distance_to_hit*(new_location->z-old_location->z))/distance_traveled;
1039 
1040 				if (new_polygon_index) *new_polygon_index= find_new_object_polygon((world_point2d *) &object->location,
1041 					(world_point2d *) new_location, object->polygon);
1042 			}
1043 			else
1044 			{
1045 				*new_location= *old_location;
1046 			}
1047 		}
1048 	}
1049 
1050 	switch (contact)
1051 	{
1052 		case _hit_monster: flags|= _projectile_hit|_projectile_hit_monster; break;
1053 		case _hit_floor: flags|= _projectile_hit|_projectile_hit_floor; break;
1054 		case _hit_media: flags|= _projectile_hit|_projectile_hit_media; break;
1055 		case _hit_scenery: flags|= _projectile_hit|_projectile_hit_scenery; break;
1056 		case _hit_nothing: break;
1057 		default: flags|= _projectile_hit; break;
1058 	}
1059 
1060 	if (last_line_index) *last_line_index = line_index;
1061 
1062 	/* returns true if we hit something, false otherwise */
1063 	return flags;
1064 }
1065 
1066 
1067 // Indicates this feature of some type of projectile
ProjectileIsGuided(short Type)1068 bool ProjectileIsGuided(short Type)
1069 {
1070 	projectile_definition *definition = get_projectile_definition(Type);
1071 	return ((definition->flags&_guided) != 0);
1072 }
1073 
1074 
unpack_projectile_data(uint8 * Stream,projectile_data * Objects,size_t Count)1075 uint8 *unpack_projectile_data(uint8 *Stream, projectile_data* Objects, size_t Count)
1076 {
1077 	uint8* S = Stream;
1078 	projectile_data* ObjPtr = Objects;
1079 
1080 	for (size_t k = 0; k < Count; k++, ObjPtr++)
1081 	{
1082 		StreamToValue(S,ObjPtr->type);
1083 
1084 		StreamToValue(S,ObjPtr->object_index);
1085 
1086 		StreamToValue(S,ObjPtr->target_index);
1087 
1088 		StreamToValue(S,ObjPtr->elevation);
1089 
1090 		StreamToValue(S,ObjPtr->owner_index);
1091 		StreamToValue(S,ObjPtr->owner_type);
1092 		StreamToValue(S,ObjPtr->flags);
1093 
1094 		StreamToValue(S,ObjPtr->ticks_since_last_contrail);
1095 		StreamToValue(S,ObjPtr->contrail_count);
1096 
1097 		StreamToValue(S,ObjPtr->distance_travelled);
1098 
1099 		StreamToValue(S,ObjPtr->gravity);
1100 
1101 		StreamToValue(S,ObjPtr->damage_scale);
1102 
1103 		StreamToValue(S,ObjPtr->permutation);
1104 
1105 		S += 2*2;
1106 	}
1107 
1108 	assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_projectile_data));
1109 	return S;
1110 }
1111 
pack_projectile_data(uint8 * Stream,projectile_data * Objects,size_t Count)1112 uint8 *pack_projectile_data(uint8 *Stream, projectile_data* Objects, size_t Count)
1113 {
1114 	uint8* S = Stream;
1115 	projectile_data* ObjPtr = Objects;
1116 
1117 	for (size_t k = 0; k < Count; k++, ObjPtr++)
1118 	{
1119 		ValueToStream(S,ObjPtr->type);
1120 
1121 		ValueToStream(S,ObjPtr->object_index);
1122 
1123 		ValueToStream(S,ObjPtr->target_index);
1124 
1125 		ValueToStream(S,ObjPtr->elevation);
1126 
1127 		ValueToStream(S,ObjPtr->owner_index);
1128 		ValueToStream(S,ObjPtr->owner_type);
1129 		ValueToStream(S,ObjPtr->flags);
1130 
1131 		ValueToStream(S,ObjPtr->ticks_since_last_contrail);
1132 		ValueToStream(S,ObjPtr->contrail_count);
1133 
1134 		ValueToStream(S,ObjPtr->distance_travelled);
1135 
1136 		ValueToStream(S,ObjPtr->gravity);
1137 
1138 		ValueToStream(S,ObjPtr->damage_scale);
1139 
1140 		ValueToStream(S,ObjPtr->permutation);
1141 
1142 		S += 2*2;
1143 	}
1144 
1145 	assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_projectile_data));
1146 	return S;
1147 }
1148 
1149 
unpack_projectile_definition(uint8 * Stream,projectile_definition * Objects,size_t Count)1150 uint8 *unpack_projectile_definition(uint8 *Stream, projectile_definition *Objects, size_t Count)
1151 {
1152 	uint8* S = Stream;
1153 	projectile_definition* ObjPtr = Objects;
1154 
1155 	for (size_t k = 0; k < Count; k++, ObjPtr++)
1156 	{
1157 		StreamToValue(S,ObjPtr->collection);
1158 		StreamToValue(S,ObjPtr->shape);
1159 		StreamToValue(S,ObjPtr->detonation_effect);
1160 		StreamToValue(S,ObjPtr->media_detonation_effect);
1161 		StreamToValue(S,ObjPtr->contrail_effect);
1162 		StreamToValue(S,ObjPtr->ticks_between_contrails);
1163 		StreamToValue(S,ObjPtr->maximum_contrails);
1164 		StreamToValue(S,ObjPtr->media_projectile_promotion);
1165 
1166 		StreamToValue(S,ObjPtr->radius);
1167 		StreamToValue(S,ObjPtr->area_of_effect);
1168 		S = unpack_damage_definition(S,&ObjPtr->damage,1);
1169 
1170 		StreamToValue(S,ObjPtr->flags);
1171 
1172 		StreamToValue(S,ObjPtr->speed);
1173 		StreamToValue(S,ObjPtr->maximum_range);
1174 
1175 		StreamToValue(S,ObjPtr->sound_pitch);
1176 		StreamToValue(S,ObjPtr->flyby_sound);
1177 		StreamToValue(S,ObjPtr->rebound_sound);
1178 	}
1179 
1180 	assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_projectile_definition));
1181 	return S;
1182 }
1183 
unpack_projectile_definition(uint8 * Stream,size_t Count)1184 uint8 *unpack_projectile_definition(uint8 *Stream, size_t Count)
1185 {
1186 	return unpack_projectile_definition(Stream,projectile_definitions,Count);
1187 }
1188 
unpack_m1_projectile_definition(uint8 * Stream,size_t Count)1189 uint8* unpack_m1_projectile_definition(uint8* Stream, size_t Count)
1190 {
1191 	uint8* S = Stream;
1192 	projectile_definition* ObjPtr = projectile_definitions;
1193 
1194 	for (size_t k = 0; k < Count; k++, ObjPtr++)
1195 	{
1196 		StreamToValue(S,ObjPtr->collection);
1197 		StreamToValue(S,ObjPtr->shape);
1198 		StreamToValue(S,ObjPtr->detonation_effect);
1199 		ObjPtr->media_detonation_effect = NONE;
1200 		StreamToValue(S,ObjPtr->contrail_effect);
1201 		StreamToValue(S,ObjPtr->ticks_between_contrails);
1202 		StreamToValue(S,ObjPtr->maximum_contrails);
1203 		ObjPtr->media_projectile_promotion = 0;
1204 
1205 		StreamToValue(S,ObjPtr->radius);
1206 		StreamToValue(S,ObjPtr->area_of_effect);
1207 		S = unpack_damage_definition(S, &ObjPtr->damage, 1);
1208 
1209 		uint16 flags;
1210 		StreamToValue(S, flags);
1211 		ObjPtr->flags = flags;
1212 
1213 		StreamToValue(S,ObjPtr->speed);
1214 		StreamToValue(S,ObjPtr->maximum_range);
1215 
1216 		ObjPtr->sound_pitch = FIXED_ONE;
1217 		StreamToValue(S,ObjPtr->flyby_sound);
1218 		ObjPtr->rebound_sound = NONE;
1219 
1220 		if (ObjPtr->damage.type == _damage_projectile)
1221 		{
1222 			ObjPtr->flags |= _bleeding_projectile;
1223 		}
1224 	}
1225 
1226 	return S;
1227 }
1228 
1229 
pack_projectile_definition(uint8 * Stream,projectile_definition * Objects,size_t Count)1230 uint8 *pack_projectile_definition(uint8 *Stream, projectile_definition *Objects, size_t Count)
1231 {
1232 	uint8* S = Stream;
1233 	projectile_definition* ObjPtr = Objects;
1234 
1235 	for (size_t k = 0; k < Count; k++, ObjPtr++)
1236 	{
1237 		ValueToStream(S,ObjPtr->collection);
1238 		ValueToStream(S,ObjPtr->shape);
1239 		ValueToStream(S,ObjPtr->detonation_effect);
1240 		ValueToStream(S,ObjPtr->media_detonation_effect);
1241 		ValueToStream(S,ObjPtr->contrail_effect);
1242 		ValueToStream(S,ObjPtr->ticks_between_contrails);
1243 		ValueToStream(S,ObjPtr->maximum_contrails);
1244 		ValueToStream(S,ObjPtr->media_projectile_promotion);
1245 
1246 		ValueToStream(S,ObjPtr->radius);
1247 		ValueToStream(S,ObjPtr->area_of_effect);
1248 		S = pack_damage_definition(S,&ObjPtr->damage,1);
1249 
1250 		ValueToStream(S,ObjPtr->flags);
1251 
1252 		ValueToStream(S,ObjPtr->speed);
1253 		ValueToStream(S,ObjPtr->maximum_range);
1254 
1255 		ValueToStream(S,ObjPtr->sound_pitch);
1256 		ValueToStream(S,ObjPtr->flyby_sound);
1257 		ValueToStream(S,ObjPtr->rebound_sound);
1258 	}
1259 
1260 	assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_projectile_definition));
1261 	return S;
1262 }
1263 
pack_projectile_definition(uint8 * Stream,size_t Count)1264 uint8 *pack_projectile_definition(uint8 *Stream, size_t Count)
1265 {
1266 	return pack_projectile_definition(Stream,projectile_definitions,Count);
1267 }
1268 
init_projectile_definitions()1269 void init_projectile_definitions()
1270 {
1271 	memcpy(projectile_definitions, original_projectile_definitions, sizeof(projectile_definitions));
1272 }
1273