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