1 /*
2 MONSTERS.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 Tuesday, November 10, 1992 1:10:20 PM
22
23 Friday, May 27, 1994 11:21:07 AM
24 split into MONSTERS.C, PROJECTILES.C and EFFECTS.C; unified active_monster and monster array.
25 Friday, September 30, 1994 5:48:25 PM (Jason)
26 started adding comments again. damage_monsters_in_radius() is less forgiving in z now.
27 Monday, December 5, 1994 9:07:37 PM (Jason)
28 rebellion environment function (all _clients hate all _pfhor).
29 Wednesday, February 1, 1995 2:29:01 AM (Jason')
30 kill_sounds; invisible monsters don�t move
31 Wednesday, June 14, 1995 10:14:24 AM (Jason)
32 rewrite for marathon2 (halfway done).
33 Monday, July 10, 1995 11:49:06 AM (Jason)
34 rewrite for marathon2 done. my bobs won�t listen to your fucking whining.
35
36 Jan 30, 2000 (Loren Petrich):
37 Added some typecasts
38 Removed some "static" declarations that conflict with "extern"
39
40 Feb 3, 2000 (Loren Petrich):
41 Treating Jjaro goo like sewage
42
43 Feb. 4, 2000 (Loren Petrich):
44 Changed halt() to assert(false) for better debugging
45
46 Feb 6, 2000 (Loren Petrich):
47 Added access to size of monster-definition structure
48
49 Feb 12, 2000 (Loren Petrich):
50 Suppressed an exposed "dprintf" as an unnecessary interrupt.
51
52 Feb 16, 2000 (Loren Petrich):
53 Added a check on the polygon index after a line-transparency check;
54 this is in case there is no polygon on the other side.
55
56 Feb 17, 2000 (Loren Petrich):
57 Fixed stuff near GUESS_HYPOTENUSE() to be long-distance-friendly
58
59 Feb 19, 2000 (Loren Petrich):
60 Added growable lists of indices of objects to be checked for collisions
61
62 Feb 24, 2000 (Loren Petrich):
63 Suppressed some asserts about monster speeds
64
65 Apr 27, 2000 (Loren Petrich):
66 Added some behavior in the case of a monster both floating and flying
67 to handle the map "Aqualung" correctly.
68
69 May 29, 2000 (Loren Petirch):
70 Fixed side effect of fixing keyframe-never-zero bug:
71 if the keyframe is zero, then a sequence never triggers shrapnel damage.
72 Thus, Hunters die a soft death more harmlessly.
73
74 Jun 11, 2000 (Loren Petrich):
75 Pegging health and oxygen to maximum values when damaged;
76 takes into account negative damage from healing projectiles.
77
78 Jul 1, 2000 (Loren Petrich):
79 Inlined the accessors
80
81 Aug 30, 2000 (Loren Petrich):
82 Added stuff for unpacking and packing
83
84 Oct 13, 2000 (Loren Petrich)
85 Converted the intersected-objects list into a Standard Template Library vector
86
87 Oct 26, 2000 (Mark Levin)
88 Revealed a few functions needed by Pfhortran
89
90 Jan 12, 2003 (Loren Petrich)
91 Added controllable damage kicks
92 */
93
94 #include <string.h>
95 #include <limits.h>
96
97 #include "cseries.h"
98 #include "map.h"
99 #include "render.h"
100 #include "interface.h"
101 #include "FilmProfile.h"
102 #include "flood_map.h"
103 #include "effects.h"
104 #include "monsters.h"
105 #include "projectiles.h"
106 #include "player.h"
107 #include "platforms.h"
108 #include "scenery.h"
109 #include "SoundManager.h"
110 #include "fades.h"
111 #include "items.h"
112 #include "media.h"
113 #include "Packing.h"
114 #include "lua_script.h"
115 #include "Logging.h"
116 #include "InfoTree.h"
117
118
119 /*
120 //explosive deaths should cause damage during their key frame
121 */
122
123 /* ---------- sounds */
124
125 /* ---------- constants */
126
127 #define OBSTRUCTION_DEACTIVATION_MASK 0x7
128
129 #define EVASIVE_MANOUVER_DISTANCE WORLD_ONE_HALF
130
131 #define MONSTER_EXTERNAL_DECELERATION (WORLD_ONE/200)
132 #define MONSTER_MINIMUM_EXTERNAL_VELOCITY (10*MONSTER_EXTERNAL_DECELERATION)
133 #define MONSTER_MAXIMUM_EXTERNAL_VELOCITY (TICKS_PER_SECOND*MONSTER_EXTERNAL_DECELERATION)
134
135 /* the height below which we don�t bother to float up a ledge (we just run right over it) */
136 #define MINIMUM_FLOATING_HEIGHT WORLD_ONE_FOURTH
137
138 #define MINIMUM_ACTIVATION_SEPARATION TICKS_PER_SECOND
139
140 /* when looking for things under or at this light intensity the monster must use his dark visual range */
141 #define LOW_LIGHT_INTENSITY 0
142
143 /* maximum area we will search out to find a new target */
144 #define MAXIMUM_TARGET_SEARCH_AREA (7*WORLD_ONE*WORLD_ONE)
145
146 #define MONSTER_PLATFORM_BUFFER_DISTANCE (WORLD_ONE/8)
147
148 #define GLUE_TRIGGER_ACTIVATION_RANGE (8*WORLD_ONE)
149 #define MONSTER_ALERT_ACTIVATION_RANGE (5*WORLD_ONE)
150
151 #define MONSTER_PATHFINDING_OBSTRUCTION_COST (2*WORLD_ONE*WORLD_ONE)
152 #define MONSTER_PATHFINDING_PLATFORM_COST (4*WORLD_ONE*WORLD_ONE)
153 #define MINIMUM_MONSTER_PATHFINDING_POLYGON_AREA (WORLD_ONE)
154
155 #define TERMINAL_VERTICAL_MONSTER_VELOCITY (WORLD_ONE/5)
156
157 #define MINIMUM_DYING_EXTERNAL_VELOCITY (WORLD_ONE/8)
158
159 #define CIVILIANS_KILLED_BY_PLAYER_THRESHHOLD 3
160 #define CIVILIANS_KILLED_DECREMENT_MASK 0x1ff
161
162 enum /* monster attitudes, extracted from enemies and friends bitfields by get_monster_attitude() */
163 {
164 _neutral,
165 _friendly,
166 _hostile
167 };
168
169 enum /* returned by find_obstructing_terrain_feature() */
170 {
171 _standing_on_sniper_ledge,
172 _entering_platform_polygon,
173 _leaving_platform_polygon,
174 _flying_or_floating_transition
175 };
176
177 #define MINIMUM_SNIPER_ELEVATION WORLD_ONE_HALF
178
179 /* ---------- structures */
180
181 struct monster_pathfinding_data
182 {
183 struct monster_definition *definition;
184 struct monster_data *monster;
185
186 bool cross_zone_boundaries;
187 };
188
189 // How much external velocity is imparted by some damage?
190 struct damage_kick_definition
191 {
192 short base_value;
193 float delta_vitality_multiplier;
194 bool is_also_vertical;
195
196 // if non-zero, will enable vertical_component if
197 // delta_vitality is greater than threshold
198 short vertical_threshold;
199
200 // whether monsters die a hard death, or in flames
201 short death_action;
202 };
203
204 /* ---------- definitions */
205
206 // LP: implements commented-out damage-kick code
207 struct damage_kick_definition damage_kick_definitions[NUMBER_OF_DAMAGE_TYPES] =
208 {
209 {0, 1, true, 0, _monster_is_dying_hard}, // _damage_explosion,
210 {0, 3, true, 0, _monster_is_dying_soft}, // _damage_electrical_staff,
211 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_projectile,
212 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_absorbed,
213 {0, 1, false, 0, _monster_is_dying_flaming}, // _damage_flame,
214 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_hound_claws,
215 {0, 1, false, 0, _monster_is_dying_flaming}, // _damage_alien_projectile,
216 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_hulk_slap,
217 {0, 3, true, 0, _monster_is_dying_soft}, // _damage_compiler_bolt,
218 {0, 0, false, 100, _monster_is_dying_soft}, // _damage_fusion_bolt,
219 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_hunter_bolt,
220 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_fist,
221 {250, 0, false, 0, _monster_is_dying_soft}, // _damage_teleporter,
222 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_defender,
223 {0, 3, true, 0, _monster_is_dying_soft}, // _damage_yeti_claws,
224 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_yeti_projectile,
225 {0, 1, false, 0, _monster_is_dying_hard}, // _damage_crushing,
226 {0, 1, false, 0, _monster_is_dying_flaming}, // _damage_lava,
227 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_suffocation,
228 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_goo,
229 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_energy_drain,
230 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_oxygen_drain,
231 {0, 1, false, 0, _monster_is_dying_soft}, // _damage_hummer_bolt,
232 {0, 0, true, 0, _monster_is_dying_soft} // _damage_shotgun_projectile,
233 };
234
235 /* ---------- globals */
236
237 /* import monster definition constants, structures and globals */
238 #include "monster_definitions.h"
239
240 // LP addition: growable list of intersected objects
241 static vector<short> IntersectedObjects;
242
243 /* ---------- private prototypes */
244
245 static monster_definition *get_monster_definition(
246 const short type);
247
248 static void monster_needs_path(short monster_index, bool immediately);
249 static void generate_new_path_for_monster(short monster_index);
250 void advance_monster_path(short monster_index);
251
252 static short get_monster_attitude(short monster_index, short target_index);
253 void change_monster_target(short monster_index, short target_index);
254 static bool switch_target_check(short monster_index, short attacker_index, short delta_vitality);
255 static bool clear_line_of_sight(short viewer_index, short target_index, bool full_circle);
256
257 static void handle_moving_or_stationary_monster(short monster_index);
258 static void execute_monster_attack(short monster_index);
259 static void kill_monster(short monster_index);
260 static bool translate_monster(short monster_index, world_distance distance);
261 static bool try_monster_attack(short monster_index);
262
263 int32 monster_pathfinding_cost_function(short source_polygon_index, short line_index,
264 short destination_polygon_index, void *data);
265 int32 monster_m1_trigger_flood_proc(short source_polygon_index, short line_index,
266 short destination_polygon_index, void *data);
267
268 void set_monster_action(short monster_index, short action);
269 void set_monster_mode(short monster_index, short new_mode, short target_index);
270
271 static short find_obstructing_terrain_feature(short monster_index, short *feature_index, short *relevant_polygon_index);
272
273 static short position_monster_projectile(short aggressor_index, short target_index, struct attack_definition *attack,
274 world_point3d *origin, world_point3d *destination, world_point3d *_vector, angle theta);
275
276 static void update_monster_vertical_physics_model(short monster_index);
277 static void update_monster_physics_model(short monster_index);
278
279 static int32 monster_activation_flood_proc(short source_polygon_index, short line_index,
280 short destination_polygon_index, void *data);
281
282 static bool attempt_evasive_manouvers(short monster_index);
283
284 static short nearest_goal_polygon_index(short polygon_index);
285 static int32 nearest_goal_cost_function(short source_polygon_index, short line_index,
286 short destination_polygon_index, void *unused);
287
288 static void cause_shrapnel_damage(short monster_index);
289
290 // For external use
291 monster_definition *get_monster_definition_external(const short type);
292
293 /* ---------- code */
294
mTYPE_IS_ENEMY(monster_definition * definition,short type)295 static bool mTYPE_IS_ENEMY(monster_definition *definition, short type)
296 {
297 if (static_world->environment_flags & _environment_rebellion_m1)
298 {
299 if (definition->_class & _class_client_m1)
300 {
301 return (get_monster_definition(type)->_class & _class_pfhor_m1);
302 }
303 else if (definition->_class & _class_pfhor_m1)
304 {
305 if (get_monster_definition(type)->_class & _class_client_m1)
306 {
307 return true;
308 }
309 }
310 }
311 return TYPE_IS_ENEMY(definition, type);
312 }
313
314
get_monster_data(short monster_index)315 monster_data *get_monster_data(
316 short monster_index)
317 {
318 struct monster_data *monster = GetMemberWithBounds(monsters,monster_index,MAXIMUM_MONSTERS_PER_MAP);
319
320 vassert(monster, csprintf(temporary, "monster index #%d is out of range", monster_index));
321 vassert(SLOT_IS_USED(monster), csprintf(temporary, "monster index #%d (%p) is unused", monster_index, (void*)monster));
322
323 return monster;
324 }
325
get_monster_definition(const short type)326 monster_definition *get_monster_definition(
327 const short type)
328 {
329 monster_definition *definition = GetMemberWithBounds(monster_definitions,type,NUMBER_OF_MONSTER_TYPES);
330 assert(definition);
331
332 return definition;
333 }
334
335 //a non-inlined version for external use
get_monster_definition_external(const short type)336 monster_definition *get_monster_definition_external(
337 const short type)
338 {
339 return get_monster_definition(type);
340 }
341
342
343 /* returns new monster index if successful, NONE otherwise */
new_monster(struct object_location * location,short monster_type)344 short new_monster(
345 struct object_location *location,
346 short monster_type)
347 {
348 struct monster_definition *definition= get_monster_definition(monster_type);
349 short original_monster_type= monster_type;
350 struct monster_data *monster;
351 short drop_mask= NONE;
352 short monster_index= NONE;
353 short flags= _monster_has_never_been_activated;
354
355 switch (dynamic_world->game_information.difficulty_level)
356 {
357 case _wuss_level: drop_mask= 3; break; /* drop every fourth monster */
358 case _easy_level: drop_mask= 7; break; /* drop every eighth monster */
359 /* otherwise, drop no monsters */
360 }
361
362 if ((definition->flags&_monster_cannot_be_dropped) || !(definition->flags&_monster_is_alien) || drop_mask==NONE || (++dynamic_world->new_monster_vanishing_cookie&drop_mask))
363 {
364 /* check to see if we should promote or demote this monster based on difficulty level */
365 if (definition->flags&_monster_major)
366 {
367 short demote_mask= NONE;
368
369 switch (dynamic_world->game_information.difficulty_level)
370 {
371 case _wuss_level: demote_mask= 1; break; /* demote every other major */
372 case _easy_level: demote_mask= 3; break; /* demote every fourth major */
373 /* otherwise, demote no monsters */
374 }
375
376 if (demote_mask!=NONE && !(++dynamic_world->new_monster_mangler_cookie&demote_mask)) definition= get_monster_definition(monster_type-= 1), flags|= _monster_was_demoted;
377 }
378 else
379 {
380 if (definition->flags&_monster_minor)
381 {
382 short promote_mask= NONE;
383
384 switch (dynamic_world->game_information.difficulty_level)
385 {
386 case _major_damage_level: promote_mask= 1; break; /* promote every other minor */
387 case _total_carnage_level: promote_mask= 0; break; /* promote every minor */
388 /* otherwise, promote no monsters */
389 }
390 if (promote_mask!=NONE && !(++dynamic_world->new_monster_mangler_cookie&promote_mask)) definition= get_monster_definition(monster_type+= 1), flags|= _monster_was_promoted;
391 }
392 }
393
394 for (monster_index=0,monster=monsters;monster_index<MAXIMUM_MONSTERS_PER_MAP;++monster_index,++monster)
395 {
396 if (SLOT_IS_FREE(monster))
397 {
398 short object_index= new_map_object(location, BUILD_DESCRIPTOR(definition->collection, definition->stationary_shape));
399
400 if (object_index!=NONE)
401 {
402 struct object_data *object= get_object_data(object_index);
403
404 /* not doing this in !DEBUG resulted in sync errors; mmm... random data, so tasty */
405 obj_set(*monster, 0x80);
406
407 if (location->flags&_map_object_is_blind) flags|= _monster_is_blind;
408 if (location->flags&_map_object_is_deaf) flags|= _monster_is_deaf;
409 if (location->flags&_map_object_floats) flags|= _monster_teleports_out_when_deactivated;
410
411 /* initialize the monster_data structure; we don�t touch most of the fields here
412 because the monster is initially inactive (and they will be initialized when the
413 monster is activated) */
414 monster->type= monster_type;
415 monster->activation_bias= DECODE_ACTIVATION_BIAS(location->flags);
416 monster->vitality= NONE; /* if a monster is activated with vitality==NONE, it will be properly initialized */
417 monster->object_index= object_index;
418 monster->flags= flags;
419 monster->goal_polygon_index= monster->activation_bias==_activate_on_goal ?
420 nearest_goal_polygon_index(location->polygon_index) : NONE;
421 monster->sound_polygon_index= object->polygon;
422 monster->sound_location= object->location;
423 MARK_SLOT_AS_USED(monster);
424
425 /* initialize the monster�s object */
426 if (definition->flags&_monster_is_invisible) object->transfer_mode= _xfer_invisibility;
427 if (definition->flags&_monster_is_subtly_invisible) object->transfer_mode= _xfer_subtle_invisibility;
428 if (definition->flags&_monster_is_enlarged) object->flags|= _object_is_enlarged;
429 if (definition->flags&_monster_is_tiny) object->flags|= _object_is_tiny;
430 SET_OBJECT_SOLIDITY(object, true);
431 SET_OBJECT_OWNER(object, _object_is_monster);
432 object->permutation= monster_index;
433 object->sound_pitch= definition->sound_pitch;
434
435 /* make sure the object frequency stuff keeps track of how many monsters are
436 on the map */
437 object_was_just_added(_object_is_monster, original_monster_type);
438 }
439 else
440 {
441 monster_index= NONE;
442 }
443
444 break;
445 }
446 }
447 if (monster_index==MAXIMUM_MONSTERS_PER_MAP) monster_index= NONE;
448 }
449
450 /* keep track of how many civilians we drop on this level */
451 if ((static_world->mission_flags & _mission_rescue_m1) &&
452 monster_index!=NONE &&
453 (definition->_class&_class_human_civilian_m1))
454 {
455 dynamic_world->current_civilian_count+= 1;
456 }
457
458 return monster_index;
459 }
460
461 /* assumes �t==1 tick */
move_monsters(void)462 void move_monsters(
463 void)
464 {
465 struct monster_data *monster;
466 bool monster_got_time= false;
467 bool monster_built_path= (dynamic_world->tick_count&3) ? true : false;
468 short monster_index;
469
470 for (monster_index= 0, monster= monsters; monster_index<MAXIMUM_MONSTERS_PER_MAP; ++monster_index, ++monster)
471 {
472 if (SLOT_IS_USED(monster) && !MONSTER_IS_PLAYER(monster))
473 {
474 struct object_data *object= get_object_data(monster->object_index);
475
476 if (MONSTER_IS_ACTIVE(monster))
477 {
478 if (!OBJECT_IS_INVISIBLE(object))
479 {
480 struct monster_definition *definition= get_monster_definition(monster->type);
481 short animation_flags;
482
483 // AlexJLS patch: effect of dangerous polygons
484 cause_polygon_damage(object->polygon,monster_index);
485
486 /* clear the recovering from hit flag, mark the monster as not idle */
487 SET_MONSTER_IDLE_STATUS(monster, false);
488
489 update_monster_vertical_physics_model(monster_index);
490
491 /* update our object�s animation unless we�re �suffering� from an external velocity
492 or we�re airborne (if we�re a flying or floating monster, ignore both of these */
493 if ((!monster->external_velocity&&!monster->vertical_velocity) ||
494 (film_profile.ketchup_fix && (monster->action==_monster_is_attacking_close||monster->action==_monster_is_attacking_far)) ||
495 ((monster->action!=_monster_is_being_hit||!monster->external_velocity) && (definition->flags&(_monster_floats|_monster_flys))))
496 {
497 animate_object(monster->object_index);
498 }
499 animation_flags= GET_OBJECT_ANIMATION_FLAGS(object);
500
501 /* give this monster time, if we can and he needs it */
502 if (!monster_got_time && monster_index>dynamic_world->last_monster_index_to_get_time && !MONSTER_IS_DYING(monster))
503 {
504 switch (monster->mode)
505 {
506 case _monster_unlocked:
507 /* if this monster is unlocked and we haven�t already given a monster time,
508 call find_closest_appropriate_target() */
509 change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false));
510 monster_got_time= true;
511 break;
512
513 case _monster_lost_lock:
514 case _monster_losing_lock:
515 /* if this monster has lost or is losing lock and we haven�t already given a monster
516 time, check to see if his target has become visible again */
517 if (clear_line_of_sight(monster_index, monster->target_index, false))
518 {
519 change_monster_target(monster_index, monster->target_index);
520 }
521 monster_got_time= true;
522 break;
523 }
524
525 /* if we gave this guy time, make room for the next guy */
526 if (monster_got_time) dynamic_world->last_monster_index_to_get_time= monster_index;
527 }
528
529 /* if this monster needs a path, generate one (unless we�ve already generated a
530 path this frame in which case we�ll wait until next frame, UNLESS the monster
531 has no path in which case it needs one regardless) */
532 if (MONSTER_NEEDS_PATH(monster) && !MONSTER_IS_DYING(monster) && !MONSTER_IS_ATTACKING(monster) &&
533 ((!monster_built_path && monster_index>dynamic_world->last_monster_index_to_build_path) || monster->path==NONE))
534 {
535 generate_new_path_for_monster(monster_index);
536 if (!monster_built_path)
537 {
538 monster_built_path= true;
539 dynamic_world->last_monster_index_to_build_path= monster_index;
540 }
541 }
542
543 /* it�s possible that we couldn�t get where we wanted to go, or that we arrived there
544 and deactivated ourselves; if this happens we don�t want to continue processing
545 the monster as if it were active */
546 if (MONSTER_IS_ACTIVE(monster))
547 {
548 /* move the monster; check to see if we can attack; resolve modes ending; etc. */
549 switch (monster->action)
550 {
551 case _monster_is_waiting_to_attack_again:
552 case _monster_is_stationary:
553 case _monster_is_moving:
554 handle_moving_or_stationary_monster(monster_index);
555 break;
556
557 case _monster_is_attacking_close:
558 case _monster_is_attacking_far:
559 if (animation_flags&_obj_keyframe_started) execute_monster_attack(monster_index);
560 if (animation_flags&_obj_last_frame_animated)
561 {
562 if (((monster->attack_repetitions-=1)<0) || !try_monster_attack(monster_index))
563 {
564 /* after an attack has been initiated successfully we need to return to
565 _monster_is_moving action, kill our path and ask for a new one
566 (because we�re pointed in the wrong direction now) */
567 set_monster_action(monster_index,
568 (monster->attack_repetitions<0 && (definition->flags&_monster_waits_with_clear_shot) && MONSTER_IS_LOCKED(monster)) ?
569 _monster_is_waiting_to_attack_again : _monster_is_moving);
570 monster_needs_path(monster_index, true);
571 monster->ticks_since_attack= 0;
572 }
573 }
574 break;
575
576 case _monster_is_teleporting_in:
577 if (animation_flags&_obj_last_frame_animated)
578 {
579 monster->action= _monster_is_moving;
580 set_monster_action(monster_index, _monster_is_moving);
581 change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false));
582 }
583 break;
584 case _monster_is_teleporting_out:
585 if (animation_flags&_obj_keyframe_started)
586 {
587 monster->action= _monster_is_dying_soft; // to prevent aggressors from relocking
588 monster_died(monster_index);
589 teleport_object_out(monster->object_index);
590 remove_map_object(monster->object_index);
591 L_Invalidate_Monster(monster_index);
592 MARK_SLOT_AS_FREE(monster);
593 }
594 break;
595
596 case _monster_is_being_hit:
597 update_monster_physics_model(monster_index);
598 if (animation_flags&_obj_last_frame_animated)
599 {
600 monster_needs_path(monster_index, true);
601 set_monster_action(monster_index, _monster_is_moving);
602 monster->external_velocity= 0;
603 }
604 break;
605
606 case _monster_is_dying_soft:
607 case _monster_is_dying_hard:
608 case _monster_is_dying_flaming:
609 update_monster_physics_model(monster_index);
610 if ((definition->flags&_monster_has_delayed_hard_death) && monster->action==_monster_is_dying_soft)
611 {
612 if (!monster->external_velocity && object->location.z==monster->desired_height) //&& !monster->vertical_velocity)
613 {
614 set_monster_action(monster_index, _monster_is_dying_hard);
615 }
616 else
617 {
618 if (definition->contrail_effect!=NONE) new_effect(&object->location, object->polygon, definition->contrail_effect, object->facing);
619 }
620 }
621 else
622 {
623 // LP change: if keyframe is zero, then a monster should not produce shrapnel damage.
624 // This fixes a side effect of a fix of the keyframe-never-zero bug,
625 // which is that Hunters injure those nearby when they die a soft death.
626 if (animation_flags&_obj_keyframe_started && (!film_profile.keyframe_fix || GET_SEQUENCE_FRAME(object->sequence) != 0))
627 cause_shrapnel_damage(monster_index);
628 if (animation_flags&_obj_last_frame_animated) kill_monster(monster_index);
629 }
630 break;
631
632 default:
633 assert(false);
634 break;
635 }
636 }
637 }
638 }
639 else
640 {
641 /* all inactive monsters get time to scan for targets */
642 if (!monster_got_time && !MONSTER_IS_BLIND(monster) && monster_index>dynamic_world->last_monster_index_to_get_time)
643 {
644 change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false));
645 if (MONSTER_HAS_VALID_TARGET(monster)) activate_nearby_monsters(monster->target_index, monster_index, _pass_one_zone_border, MONSTER_ALERT_ACTIVATION_RANGE);
646
647 monster_got_time= true;
648 dynamic_world->last_monster_index_to_get_time= monster_index;
649 }
650 }
651 }
652
653 /* WARNING: a large number of unusual things could have happened here, including the monster
654 being dead, his slot being free, and his object having been removed from the map; in other
655 words, it�s probably not a good idea to do any postprocessing here */
656 }
657
658 /* either there are no unlocked monsters or �dynamic_world->last_monster_index_to_get_time� is higher than
659 all of them (so we reset it to zero) ... same for paths */
660 if (!monster_got_time) dynamic_world->last_monster_index_to_get_time= -1;
661 if (!monster_built_path) dynamic_world->last_monster_index_to_build_path= -1;
662
663 if (dynamic_world->civilians_killed_by_players)
664 {
665 uint32 mask = 0;
666
667 switch (dynamic_world->game_information.difficulty_level)
668 {
669 case _wuss_level: mask= 0x7f; break;
670 case _easy_level: mask= 0xff; break;
671 case _normal_level: mask= 0x1ff; break;
672 case _major_damage_level: mask= 0x3ff; break;
673 case _total_carnage_level: mask= 0x7ff; break;
674 }
675
676 if (!(dynamic_world->tick_count&mask))
677 {
678 dynamic_world->civilians_killed_by_players-= 1;
679 }
680 }
681 }
682
683 /* when a monster dies, all monsters locked on it need to find something better to do; this
684 function should be called before the given target is expunged from the monster list but
685 after it is marked as dying */
monster_died(short target_index)686 void monster_died(
687 short target_index)
688 {
689 struct monster_data *monster= get_monster_data(target_index);
690 short monster_index;
691
692 // dprintf("monster #%d is dead;g;", target_index);
693
694 /* orphan this monster�s projectiles if they don�t belong to a player (player�s monster
695 slots are always valid and we want to correctly attribute damage and kills that ocurr
696 after a player dies) */
697 if (!MONSTER_IS_PLAYER(monster)) orphan_projectiles(target_index);
698
699 /* active monsters need extant paths deleted and should be marked as unlocked */
700 if (MONSTER_IS_ACTIVE(monster))
701 {
702 set_monster_mode(target_index, _monster_unlocked, NONE);
703 if (monster->path!=NONE) delete_path(monster->path);
704 SET_MONSTER_NEEDS_PATH_STATUS(monster, false);
705 monster->path= NONE;
706 }
707
708 /* anyone locked on this monster needs a clue */
709 for (monster_index= 0, monster= monsters; monster_index<MAXIMUM_MONSTERS_PER_MAP; ++monster_index, ++monster)
710 {
711 if (SLOT_IS_USED(monster) && MONSTER_IS_ACTIVE(monster) && monster->target_index==target_index)
712 {
713 short closest_target_index= find_closest_appropriate_target(monster_index, true);
714
715 monster->target_index= NONE;
716 monster_needs_path(monster_index, false);
717
718 play_object_sound(monster->object_index, get_monster_definition(monster->type)->kill_sound);
719 if (closest_target_index!=NONE)
720 {
721 change_monster_target(monster_index, closest_target_index);
722 }
723 else
724 {
725 if (monster->action==_monster_is_waiting_to_attack_again) set_monster_action(monster_index, _monster_is_moving);
726 set_monster_mode(monster_index, _monster_unlocked, NONE);
727 }
728 }
729 }
730 }
731
initialize_monsters(void)732 void initialize_monsters(
733 void)
734 {
735 /* initialize our globals to be the same thing on all machines */
736 dynamic_world->civilians_killed_by_players= 0;
737 dynamic_world->last_monster_index_to_get_time= -1;
738 dynamic_world->last_monster_index_to_build_path= -1;
739 dynamic_world->new_monster_mangler_cookie= global_random();
740 dynamic_world->new_monster_vanishing_cookie= global_random();
741 }
742
743 /* call this when a new level is loaded from disk so the monsters can cope with their new world */
initialize_monsters_for_new_level(void)744 void initialize_monsters_for_new_level(
745 void)
746 {
747 struct monster_data *monster;
748 short monster_index;
749
750 /* when a level is loaded after being saved all of an active monster�s data is still intact,
751 but it�s path no longer exists. this function resets all monsters so that they recalculate
752 their paths, first thing. */
753 for (monster_index=0,monster=monsters;monster_index<MAXIMUM_MONSTERS_PER_MAP;++monster_index,++monster)
754 {
755 if (SLOT_IS_USED(monster)&&MONSTER_IS_ACTIVE(monster))
756 {
757 SET_MONSTER_NEEDS_PATH_STATUS(monster, true);
758 monster->path= NONE;
759 }
760 }
761 }
762
load_sound(short sound_index)763 static void load_sound(short sound_index)
764 {
765 SoundManager::instance()->LoadSound(sound_index);
766 }
767
load_monster_sounds(short monster_type)768 void load_monster_sounds(
769 short monster_type)
770 {
771 if (monster_type!=NONE)
772 {
773 struct monster_definition *definition= get_monster_definition(monster_type);
774
775 process_collection_sounds(definition->collection, load_sound);
776
777 load_projectile_sounds(definition->ranged_attack.type);
778 load_projectile_sounds(definition->melee_attack.type);
779
780 SoundManager::instance()->LoadSounds(&definition->activation_sound, 8);
781 }
782 }
783
mark_monster_collections(short monster_type,bool loading)784 void mark_monster_collections(
785 short monster_type,
786 bool loading)
787 {
788 if (monster_type!=NONE)
789 {
790 struct monster_definition *definition= get_monster_definition(monster_type);
791
792 /* mark the monster collection */
793 mark_collection(definition->collection, loading);
794
795 /* mark the monster�s projectile�s collection */
796 mark_projectile_collections(definition->ranged_attack.type, loading);
797 mark_projectile_collections(definition->melee_attack.type, loading);
798 }
799 }
800
801 enum
802 {
803 MAXIMUM_NEED_TARGET_INDEXES= 32
804 };
805
activate_nearby_monsters(short target_index,short caller_index,short flags,int32 max_range)806 void activate_nearby_monsters(
807 short target_index, /* activate with lock on this target (or NONE for lock-less activation) */
808 short caller_index, /* start the flood from here */
809 short flags,
810 int32 max_range)
811 {
812 struct monster_data *caller= get_monster_data(caller_index);
813 int32 max_cost= INT32_MAX;
814 if (static_world->environment_flags&_environment_activation_ranges)
815 {
816 if (max_range>0)
817 max_cost= max_range*max_range;
818 else if (flags&_activate_glue_monsters)
819 max_cost= GLUE_TRIGGER_ACTIVATION_RANGE*GLUE_TRIGGER_ACTIVATION_RANGE;
820 }
821
822 if (dynamic_world->tick_count-caller->ticks_since_last_activation>MINIMUM_ACTIVATION_SEPARATION ||
823 (flags&_activation_cannot_be_avoided))
824 {
825 short polygon_index= get_object_data(caller->object_index)->polygon;
826 short need_target_indexes[MAXIMUM_NEED_TARGET_INDEXES];
827 short need_target_count= 0;
828 int32 flood_flags= flags;
829
830 /* flood out from the target monster�s polygon, searching through the object lists of all
831 polygons we encounter */
832 polygon_index= flood_map(polygon_index, max_cost, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags);
833 while (polygon_index!=NONE)
834 {
835 short object_index;
836 struct object_data *object;
837
838 /* loop through all objects in this polygon looking for _hostile inactive or unlocked monsters */
839 for (object_index= get_polygon_data(polygon_index)->first_object; object_index!=NONE; object_index= object->next_object)
840 {
841 object= get_object_data(object_index);
842 if (GET_OBJECT_OWNER(object)==_object_is_monster &&
843 (!OBJECT_IS_INVISIBLE(object) || (flags&_activate_invisible_monsters)))
844 {
845 short aggressor_index= object->permutation;
846 struct monster_data *aggressor= get_monster_data(aggressor_index);
847 // bool target_hostile= get_monster_attitude(aggressor_index, target_index)==_hostile;
848 // bool caller_hostile= get_monster_attitude(aggressor_index, caller_index)==_hostile;
849
850 // deaf monsters are only deaf to players which have always been hostile, so:
851 // bobs are deaf to friendly players but not hostile ones
852 // monsters are deaf to all players
853 // deaf monsters ignore friendly monsters activating on other friendly monsters but
854 // non-deaf ones DO NOT
855
856 // !MONSTER_IS_PLAYER(caller) || TYPE_IS_FRIEND(get_monster_definition(aggressor->type), caller->type) || caller_hostile
857
858 /* don�t activate players or ourselves, and only activate monsters on glue polygons
859 if they have previously been activated or we�ve been explicitly told to */
860 if (!MONSTER_IS_PLAYER(aggressor) && caller_index!=aggressor_index && target_index!=aggressor_index &&
861 (!(flood_flags&_passed_zone_border) || (!(aggressor->flags&_monster_has_never_been_activated))) &&
862 ((flood_flags&_activate_deaf_monsters) || !MONSTER_IS_DEAF(aggressor)) && // || !MONSTER_IS_PLAYER(caller) || !TYPE_IS_FRIEND(get_monster_definition(aggressor->type), caller->type) || !caller_hostile) &&
863 aggressor->mode!=_monster_locked)
864 {
865 bool monster_was_active= true;
866
867 /* activate the monster if he�s inactive */
868 if (!MONSTER_IS_ACTIVE(aggressor))
869 {
870 activate_monster(aggressor_index);
871 monster_was_active= false;
872 }
873
874 if (monster_was_active || !(flags&_use_activation_biases) ||
875 (aggressor->activation_bias!=_activate_on_goal && aggressor->activation_bias!=_activate_randomly))
876 {
877 if (monster_was_active || aggressor->activation_bias!=_activate_on_nearest_hostile)
878 {
879 /* if we have valid target and this monster thinks that target is hostile, lock on */
880 if (get_monster_attitude(aggressor_index, target_index)==_hostile)
881 {
882 switch_target_check(aggressor_index, target_index, 0);
883 }
884 else
885 {
886 /* but hey, if the target isn�t hostile, maybe the caller is ...
887 (mostly for the automated defenses and the civilians on the ship) */
888 if (get_monster_attitude(aggressor_index, caller_index)==_hostile)
889 {
890 switch_target_check(aggressor_index, caller_index, 0);
891 }
892 }
893 }
894 else
895 {
896 // must defer find_closest_appropriate_target; pathfinding is not reentrant
897 if (need_target_count<MAXIMUM_NEED_TARGET_INDEXES)
898 {
899 need_target_indexes[need_target_count++]= aggressor_index;
900 }
901 }
902 }
903 }
904 }
905 }
906
907 polygon_index= flood_map(NONE, max_cost, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags);
908 }
909
910 // deferred find_closest_appropriate_target() calls
911 while (--need_target_count>=0)
912 {
913 change_monster_target(need_target_indexes[need_target_count],
914 find_closest_appropriate_target(need_target_indexes[need_target_count], true));
915 }
916
917 caller->ticks_since_last_activation= dynamic_world->tick_count;
918 }
919 }
920
monster_activation_flood_proc(short source_polygon_index,short line_index,short destination_polygon_index,void * data)921 static int32 monster_activation_flood_proc(
922 short source_polygon_index,
923 short line_index,
924 short destination_polygon_index,
925 void *data)
926 {
927 int32 *flags=(int32 *)data;
928 struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index);
929 struct polygon_data *source_polygon= get_polygon_data(source_polygon_index);
930 struct line_data *line= get_line_data(line_index);
931 bool obey_glue= (static_world->environment_flags&_environment_glue_m1);
932 bool limit_activation= (static_world->environment_flags&_environment_activation_ranges);
933 int32 cost= limit_activation ? source_polygon->area : 1;
934
935 // dprintf("P#%d==>P#%d by L#%d", source_polygon_index, destination_polygon_index, line_index);
936
937 if (destination_polygon->type==_polygon_is_zone_border)
938 {
939 if (((*flags)&_pass_one_zone_border) && !((*flags)&_passed_zone_border))
940 {
941 *flags|= _passed_zone_border;
942 }
943 else
944 {
945 // can�t pass this zone border
946 cost= -1;
947 }
948 }
949 else if ((destination_polygon->type==_polygon_is_superglue) &&
950 ((*flags)&_cannot_pass_superglue) &&
951 obey_glue)
952 {
953 cost= -1;
954 }
955 else if ((destination_polygon->type==_polygon_is_glue) &&
956 !((*flags)&_activate_glue_monsters) &&
957 obey_glue)
958 {
959 cost= -1;
960 }
961 else if ((destination_polygon->type==_polygon_is_monster_impassable) &&
962 limit_activation)
963 {
964 cost= -1;
965 }
966 else if ((destination_polygon->type==_polygon_is_platform) &&
967 (destination_polygon->floor_height==destination_polygon->ceiling_height) &&
968 !((*flags)&_pass_solid_lines) &&
969 limit_activation)
970 {
971 cost= -1;
972 }
973
974 if (!((*flags)&_pass_solid_lines) && LINE_IS_SOLID(line)) cost= -1;
975
976 if (cost>0 && limit_activation)
977 {
978 world_distance delta_height= destination_polygon->floor_height-source_polygon->floor_height;
979 cost+= delta_height*delta_height;
980 }
981
982 return cost;
983 }
984
985 #define LIVE_ALIEN_THRESHHOLD 8
986
987 static std::vector<bool> monster_must_be_exterminated(NUMBER_OF_MONSTER_TYPES, false);
988
live_aliens_on_map(void)989 bool live_aliens_on_map(
990 void)
991 {
992 bool found_alien_which_must_be_killed= false;
993 struct monster_data *monster;
994 short live_alien_count= 0;
995 short threshhold= LIVE_ALIEN_THRESHHOLD;
996 short monster_index;
997
998 for (monster_index= 0, monster= monsters; monster_index<MAXIMUM_MONSTERS_PER_MAP; ++monster_index, ++monster)
999 {
1000 if (SLOT_IS_USED(monster))
1001 {
1002 struct monster_definition *definition= get_monster_definition(monster->type);
1003
1004 if (monster_must_be_exterminated[monster->type])
1005 {
1006 found_alien_which_must_be_killed = true;
1007 break;
1008 }
1009
1010 if ((definition->flags&_monster_is_alien) ||
1011 ((static_world->environment_flags&_environment_rebellion) && !MONSTER_IS_PLAYER(monster)))
1012 {
1013 live_alien_count+= 1;
1014 }
1015 }
1016 }
1017
1018 if (static_world->environment_flags&_environment_rebellion) threshhold= 0;
1019
1020 return live_alien_count<=threshhold ? found_alien_which_must_be_killed : true;
1021 }
1022
1023 /* activate the given monster (initially unlocked) */
activate_monster(short monster_index)1024 void activate_monster(
1025 short monster_index)
1026 {
1027 struct monster_data *monster= get_monster_data(monster_index);
1028 struct object_data *object= get_object_data(monster->object_index);
1029 struct monster_definition *definition= get_monster_definition(monster->type);
1030
1031 // dprintf("monster #%d activated;g;", monster_index);
1032
1033 assert(!MONSTER_IS_ACTIVE(monster));
1034 assert(!MONSTER_IS_PLAYER(monster));
1035
1036 if (OBJECT_IS_INVISIBLE(object))
1037 {
1038 struct polygon_data *polygon= get_polygon_data(object->polygon);
1039
1040 if (polygon->media_index!=NONE)
1041 {
1042 struct media_data *media= get_media_data(polygon->media_index);
1043
1044 // LP change: idiot-proofed this
1045 if (media)
1046 {
1047 if (media->height>object->location.z+definition->height &&
1048 !(definition->flags&_monster_can_teleport_under_media))
1049 {
1050 return;
1051 }
1052 }
1053 }
1054 }
1055
1056 CLEAR_MONSTER_RECOVERING_FROM_HIT(monster);
1057 SET_MONSTER_IDLE_STATUS(monster, false);
1058 SET_MONSTER_ACTIVE_STATUS(monster, true);
1059 SET_MONSTER_BERSERK_STATUS(monster, false);
1060 SET_MONSTER_HAS_BEEN_ACTIVATED(monster);
1061 monster->flags&= ~(_monster_is_blind|_monster_is_deaf);
1062
1063 monster->path= NONE;
1064 /* we used to set monster->target_index here, but it is invalid when mode==_monster_unlocked */
1065 monster->mode= _monster_unlocked, monster->target_index= NONE;
1066
1067 if (definition->attack_frequency == 0) // IP: Avoid division by zero
1068 definition->attack_frequency = 1;
1069
1070 monster->ticks_since_attack= (definition->flags&_monster_attacks_immediately) ?
1071 definition->attack_frequency : global_random()%definition->attack_frequency;
1072 monster->desired_height= object->location.z; /* best guess */
1073 monster->random_desired_height= INT16_MAX; // to be out of range and recalculated
1074 monster->external_velocity= monster->vertical_velocity= 0;
1075 monster->ticks_since_last_activation= 0;
1076
1077 /* if vitality is NONE (-1) initialize it from the monster_definition, respecting
1078 the difficulty level if necessary */
1079 if (monster->vitality==NONE)
1080 {
1081 short vitality= definition->vitality;
1082
1083 if (definition->flags&_monster_is_alien)
1084 {
1085 switch (dynamic_world->game_information.difficulty_level)
1086 {
1087 case _wuss_level: vitality-= vitality>>1; break;
1088 case _easy_level: vitality-= vitality>>2; break;
1089 case _major_damage_level: vitality+= vitality>>2; break;
1090 case _total_carnage_level: vitality+= vitality>>1; break;
1091 }
1092 }
1093
1094 monster->vitality= vitality;
1095 }
1096
1097 set_monster_action(monster_index, _monster_is_stationary);
1098 monster_needs_path(monster_index, true);
1099
1100 if (OBJECT_IS_INVISIBLE(object))
1101 {
1102 teleport_object_in(monster->object_index);
1103 }
1104 else if (definition->flags & _monster_makes_sound_when_activated)
1105 {
1106 play_object_sound(monster->object_index, definition->activation_sound);
1107 }
1108
1109 changed_polygon(object->polygon, object->polygon, NONE);
1110 }
1111
deactivate_monster(short monster_index)1112 void deactivate_monster(
1113 short monster_index)
1114 {
1115 struct monster_data *monster= get_monster_data(monster_index);
1116
1117 // dprintf("monster #%d deactivated;g;", monster_index);
1118
1119 assert(MONSTER_IS_ACTIVE(monster));
1120
1121 if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster)) monster->vertical_velocity= monster->external_velocity= 0;
1122
1123 if (!monster->vertical_velocity && !monster->external_velocity)
1124 {
1125 if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster) && monster->action!=_monster_is_teleporting_out)
1126 {
1127 set_monster_action(monster_index, _monster_is_teleporting_out);
1128 }
1129 else
1130 {
1131 /* assume stationary shape before deactivation */
1132 set_monster_action(monster_index, _monster_is_stationary);
1133
1134 /* get rid of this monster�s path if he has one */
1135 if (monster->path!=NONE) delete_path(monster->path);
1136
1137 SET_MONSTER_ACTIVE_STATUS(monster, false);
1138 }
1139 }
1140 }
1141
1142 /* returns a list of object indexes of all monsters in or adjacent to the given polygon,
1143 up to maximum_object_count. */
1144 // LP change: called with growable list
possible_intersecting_monsters(vector<short> * IntersectedObjectsPtr,unsigned maximum_object_count,short polygon_index,bool include_scenery)1145 bool possible_intersecting_monsters(
1146 vector<short> *IntersectedObjectsPtr,
1147 unsigned maximum_object_count,
1148 short polygon_index,
1149 bool include_scenery)
1150 {
1151 struct polygon_data *polygon= get_polygon_data(polygon_index);
1152 short *neighbor_indexes= get_map_indexes(polygon->first_neighbor_index, polygon->neighbor_count);
1153 bool found_solid_object= false;
1154
1155 // Skip this step if neighbor indexes were not found
1156 if (!neighbor_indexes) return found_solid_object;
1157
1158 for (short i=0;i<polygon->neighbor_count;++i)
1159 {
1160 struct polygon_data *neighboring_polygon= get_polygon_data(*neighbor_indexes++);
1161
1162 if (!POLYGON_IS_DETACHED(neighboring_polygon))
1163 {
1164 short object_index= neighboring_polygon->first_object;
1165
1166 while (object_index!=NONE)
1167 {
1168 struct object_data *object= get_object_data(object_index);
1169 bool solid_object= false;
1170
1171 if (!OBJECT_IS_INVISIBLE(object))
1172 {
1173 switch (GET_OBJECT_OWNER(object))
1174 {
1175 case _object_is_monster:
1176 {
1177 struct monster_data *monster= get_monster_data(object->permutation);
1178
1179 if (!MONSTER_IS_DYING(monster) && !MONSTER_IS_TELEPORTING(monster))
1180 {
1181 solid_object= true;
1182 }
1183
1184 break;
1185 }
1186
1187 case _object_is_scenery:
1188 if (include_scenery && OBJECT_IS_SOLID(object)) solid_object= true;
1189 break;
1190 }
1191
1192 if (solid_object)
1193 {
1194 found_solid_object= true;
1195
1196 // LP change:
1197 if (IntersectedObjectsPtr && IntersectedObjectsPtr->size()<maximum_object_count) /* do we have enough space to add it? */
1198 {
1199 unsigned j;
1200
1201 /* only add this object_index if it's not already in the list */
1202 vector<short>& IntersectedObjects = *IntersectedObjectsPtr;
1203 for (j=0; j<IntersectedObjects.size() && IntersectedObjects[j]!=object_index; ++j)
1204 ;
1205 if (j==IntersectedObjects.size())
1206 IntersectedObjects.push_back(object_index);
1207 }
1208 }
1209 }
1210
1211 object_index= object->next_object;
1212 }
1213 }
1214 }
1215
1216 return found_solid_object;
1217 }
1218
1219 /* when a target changes polygons, all monsters locked on it must recalculate their paths.
1220 target is an index into the monster list. */
monster_moved(short target_index,short old_polygon_index)1221 void monster_moved(
1222 short target_index,
1223 short old_polygon_index)
1224 {
1225 struct monster_data *monster= get_monster_data(target_index);
1226 struct object_data *object= get_object_data(monster->object_index);
1227 short monster_index;
1228
1229 if (!MONSTER_IS_PLAYER(monster))
1230 {
1231 /* cause lights to light, platforms to trigger, etc.; the player does this differently */
1232 changed_polygon(old_polygon_index, object->polygon, NONE);
1233 }
1234 else if ((static_world->environment_flags & _environment_glue_m1) &&
1235 (get_polygon_data(object->polygon)->type == _polygon_is_glue_trigger))
1236 {
1237 activate_nearby_monsters(target_index, target_index,
1238 _pass_solid_lines|_activate_deaf_monsters|_activate_invisible_monsters|_use_activation_biases|_cannot_pass_superglue|_activate_glue_monsters);
1239 }
1240
1241 for (monster_index=0,monster=monsters;monster_index<MAXIMUM_MONSTERS_PER_MAP;++monster_index,++monster)
1242 {
1243 /* look for active monsters locked (or losing lock) on the given target_index */
1244 if (SLOT_IS_USED(monster) && MONSTER_HAS_VALID_TARGET(monster) && monster->target_index==target_index)
1245 {
1246 if (clear_line_of_sight(monster_index, target_index, true))
1247 {
1248 if (monster->mode==_monster_losing_lock) set_monster_mode(monster_index, _monster_locked, monster->target_index);
1249 }
1250 else
1251 {
1252 struct monster_definition *definition= get_monster_definition(monster->type);
1253
1254 /* we can�t see our target: if this is first time, change from _monster_locked
1255 to _monster_losing_lock, if this isn�t the first time and our target has
1256 switched polygons more times out of our sight than we have intelligence points,
1257 go to _lost_lock (which means we won�t get any more new paths when our target
1258 switches polygons, but we won�t clear our last one until we reach the end). */
1259 if (monster->mode==_monster_locked) monster->changes_until_lock_lost= 0;
1260 if (monster->mode==_monster_losing_lock) monster->changes_until_lock_lost+= 1;
1261 set_monster_mode(monster_index, (monster->changes_until_lock_lost>=definition->intelligence) ?
1262 _monster_lost_lock : _monster_losing_lock, NONE);
1263 }
1264
1265 /* if we�re losing lock, don�t recalculate our path (we�re headed towards the target�s
1266 last-known location) */
1267 if (monster->mode!=_monster_losing_lock) monster_needs_path(monster_index, false);
1268 }
1269 }
1270 }
1271
1272 /* returns NONE or a monster_index that prevented us from moving */
legal_player_move(short monster_index,world_point3d * new_location,world_distance * object_floor)1273 short legal_player_move(
1274 short monster_index,
1275 world_point3d *new_location,
1276 world_distance *object_floor) /* must be set on entry */
1277 {
1278 struct monster_data *monster= get_monster_data(monster_index);
1279 struct object_data *object= get_object_data(monster->object_index);
1280 world_point3d *old_location= &object->location;
1281 size_t monster_count;
1282 world_distance radius, height;
1283 short obstacle_index= NONE;
1284
1285 get_monster_dimensions(monster_index, &radius, &height);
1286
1287 IntersectedObjects.clear();
1288 possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, object->polygon, true);
1289 monster_count = IntersectedObjects.size();
1290 for (size_t i=0;i<monster_count;++i)
1291 {
1292 struct object_data *obstacle= get_object_data(IntersectedObjects[i]);
1293 world_distance obstacle_radius, obstacle_height;
1294
1295 switch (GET_OBJECT_OWNER(obstacle))
1296 {
1297 case _object_is_monster: get_monster_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break;
1298 case _object_is_scenery: get_scenery_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break;
1299 default:
1300 assert(false);
1301 break;
1302 }
1303
1304 if (IntersectedObjects[i]!=monster->object_index) /* no self-intersection */
1305 {
1306 world_point3d *obstacle_location= &obstacle->location;
1307
1308 world_distance separation= radius+obstacle_radius;
1309 int32 separation_squared= separation*separation;
1310
1311 world_distance new_dx= obstacle_location->x-new_location->x;
1312 world_distance new_dy= obstacle_location->y-new_location->y;
1313 int32 new_distance_squared= new_dx*new_dx+new_dy*new_dy;
1314
1315 if (new_distance_squared<separation_squared)
1316 {
1317 world_distance old_dx= obstacle_location->x-old_location->x;
1318 world_distance old_dy= obstacle_location->y-old_location->y;
1319 int32 old_distance_squared= old_dx*old_dx+old_dy*old_dy;
1320
1321 if (old_distance_squared>new_distance_squared)
1322 {
1323 world_distance this_object_floor= obstacle_location->z+obstacle_height;
1324
1325 /* it�s possible we don�t intersect in z */
1326 if (new_location->z+height<obstacle_location->z) continue;
1327 if (new_location->z>this_object_floor)
1328 {
1329 if (this_object_floor>*object_floor) *object_floor= this_object_floor;
1330 continue;
1331 }
1332
1333 // dprintf("#%d (%d,%d) hit #%d (%d,%d) moving to (%d,%d)", monster_index, old_location->x, old_location->y, obstacle->permutation, obstacle_location->x, obstacle_location->y, new_location->x, new_location->y);
1334 obstacle_index= IntersectedObjects[i];
1335 break;
1336 }
1337 }
1338 }
1339 }
1340
1341 return obstacle_index;
1342 }
1343
1344 /* returns NONE or a monster_index that prevented us from moving */
legal_monster_move(short monster_index,angle facing,world_point3d * new_location)1345 short legal_monster_move(
1346 short monster_index,
1347 angle facing, /* could be different than object->facing for players and �flying� (heh heh) monsters */
1348 world_point3d *new_location)
1349 {
1350 struct monster_data *monster= get_monster_data(monster_index);
1351 struct object_data *object= get_object_data(monster->object_index);
1352 // world_point2d *old_location= (world_point2d *) &object->location;
1353 size_t monster_count;
1354 world_distance radius, height;
1355 short obstacle_index= NONE;
1356
1357 get_monster_dimensions(monster_index, &radius, &height);
1358
1359 IntersectedObjects.clear();
1360 possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, object->polygon, true);
1361 monster_count= IntersectedObjects.size();
1362 for (size_t i=0;i<monster_count;++i)
1363 {
1364 struct object_data *obstacle= get_object_data(IntersectedObjects[i]);
1365 world_distance obstacle_radius, obstacle_height;
1366
1367 switch (GET_OBJECT_OWNER(obstacle))
1368 {
1369 case _object_is_monster: get_monster_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break;
1370 case _object_is_scenery: get_scenery_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break;
1371 default:
1372 assert(false);
1373 break;
1374 }
1375
1376 // LP change:
1377 if (IntersectedObjects[i]!=monster->object_index) /* no self-intersection */
1378 {
1379 world_point3d *obstacle_location= &obstacle->location;
1380
1381 if (obstacle_location->z<new_location->z+height && obstacle_location->z+obstacle_height>new_location->z)
1382 {
1383 world_distance separation= radius+obstacle_radius;
1384 world_distance dx= obstacle_location->x-new_location->x;
1385 world_distance dy= obstacle_location->y-new_location->y;
1386
1387 if (GET_OBJECT_OWNER(obstacle)!=_object_is_scenery && obstacle->permutation>monster_index && !MONSTER_IS_PLAYER(get_monster_data(obstacle->permutation))) separation= (separation>>1) + (separation>>2);
1388 if (dx>-separation && dx<separation && dy>-separation && dy<separation)
1389 {
1390 /* we intersect sloppily; get arctan to be sure */
1391 angle theta= NORMALIZE_ANGLE(arctangent(dx, dy)-facing);
1392
1393 if (theta<EIGHTH_CIRCLE||theta>FULL_CIRCLE-EIGHTH_CIRCLE)
1394 {
1395 // dprintf("#%d (%d,%d) hit #%d (%d,%d) moving to (%d,%d)", monster_index, old_location->x, old_location->y, obstacle->permutation, obstacle_location->x, obstacle_location->y, new_location->x, new_location->y);
1396 obstacle_index= IntersectedObjects[i];
1397 break;
1398 }
1399 }
1400 }
1401 }
1402 }
1403
1404 return obstacle_index;
1405 }
1406
get_monster_dimensions(short monster_index,world_distance * radius,world_distance * height)1407 void get_monster_dimensions(
1408 short monster_index,
1409 world_distance *radius,
1410 world_distance *height)
1411 {
1412 struct monster_data *monster= get_monster_data(monster_index);
1413 struct monster_definition *definition= get_monster_definition(monster->type);
1414
1415 *radius= definition->radius;
1416 *height= definition->height;
1417 }
1418
damage_monsters_in_radius(short primary_target_index,short aggressor_index,short aggressor_type,world_point3d * epicenter,short epicenter_polygon_index,world_distance radius,struct damage_definition * damage,short projectile_index)1419 void damage_monsters_in_radius(
1420 short primary_target_index,
1421 short aggressor_index,
1422 short aggressor_type,
1423 world_point3d *epicenter,
1424 short epicenter_polygon_index,
1425 world_distance radius,
1426 struct damage_definition *damage,
1427 short projectile_index)
1428 {
1429 size_t object_count;
1430
1431 bool aggressor_is_live_player = false;
1432
1433 (void) (primary_target_index);
1434
1435 IntersectedObjects.clear();
1436 possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, epicenter_polygon_index, false);
1437 object_count= IntersectedObjects.size();
1438 struct object_data *aggressor = NULL;
1439 if (film_profile.infinity_tag_fix && aggressor_index != NONE)
1440 {
1441 monster_data* monster = get_monster_data(aggressor_index);
1442 if (MONSTER_IS_PLAYER(monster))
1443 {
1444 player_data* player = get_player_data(monster_index_to_player_index(aggressor_index));
1445
1446 if (!PLAYER_IS_DEAD(player)) aggressor_is_live_player = true;
1447 }
1448 }
1449
1450 for (size_t i=0;i<object_count;++i)
1451 {
1452 struct object_data *object= get_object_data(IntersectedObjects[i]);
1453 if (film_profile.damage_aggressor_last_in_tag &&
1454 GET_GAME_TYPE() == _game_of_tag &&
1455 object->permutation == aggressor_index) {
1456 // damage the aggressor last, so tag suicides are handled correctly
1457 aggressor = object;
1458 } else {
1459 world_distance distance= distance2d((world_point2d*)epicenter, (world_point2d*)&object->location);
1460 world_distance monster_radius, monster_height;
1461
1462 get_monster_dimensions(object->permutation, &monster_radius, &monster_height);
1463
1464 /* make sure we intersect the monster�s radius in the x,y-plane and that we intersect
1465 his cylinder in z */
1466 if (distance<radius+monster_radius)
1467 {
1468 if (epicenter->z+radius-distance>object->location.z && epicenter->z-radius+distance<object->location.z+monster_height)
1469 {
1470 if (!line_is_obstructed(epicenter_polygon_index, (world_point2d*)epicenter, object->polygon, (world_point2d*)&object->location))
1471 {
1472 damage_monster(object->permutation, aggressor_index, aggressor_type, epicenter, damage, projectile_index);
1473 }
1474 }
1475 }
1476 }
1477 }
1478
1479 // damage the aggressor
1480 if (film_profile.damage_aggressor_last_in_tag && aggressor != NULL)
1481 {
1482 world_distance distance= distance2d((world_point2d*)epicenter, (world_point2d*)&aggressor->location);
1483 world_distance monster_radius, monster_height;
1484
1485 get_monster_dimensions(aggressor->permutation, &monster_radius, &monster_height);
1486 if (distance<radius+monster_radius)
1487 {
1488 if (epicenter->z+radius-distance>aggressor->location.z && epicenter->z-radius+distance<aggressor->location.z+monster_height)
1489 {
1490 if (!line_is_obstructed(epicenter_polygon_index, (world_point2d*)epicenter, aggressor->polygon, (world_point2d*)&aggressor->location))
1491 {
1492 damage_monster(aggressor->permutation, aggressor_index, aggressor_type, epicenter, damage, projectile_index);
1493 }
1494 }
1495 }
1496 }
1497
1498 // or, just make him it
1499 if (GET_GAME_TYPE() == _game_of_tag && aggressor_is_live_player)
1500 {
1501 monster_data* monster = get_monster_data(aggressor_index);
1502 if (MONSTER_IS_PLAYER(monster))
1503 {
1504 short player_index = monster_index_to_player_index(aggressor_index);
1505 player_data* player = get_player_data(player_index);
1506
1507 // he blew himself up, so make sure he's it
1508 if (PLAYER_IS_DEAD(player))
1509 {
1510 dynamic_world->game_player_index = player_index;
1511 }
1512 }
1513 }
1514 }
1515
damage_monster(short target_index,short aggressor_index,short aggressor_type,world_point3d * epicenter,struct damage_definition * damage,short projectile_index)1516 void damage_monster(
1517 short target_index,
1518 short aggressor_index,
1519 short aggressor_type,
1520 world_point3d *epicenter,
1521 struct damage_definition *damage,
1522 short projectile_index)
1523 {
1524 struct monster_data *monster= get_monster_data(target_index);
1525 struct monster_definition *definition= get_monster_definition(monster->type);
1526 struct monster_data *aggressor_monster= aggressor_index!=NONE ? get_monster_data(aggressor_index) : (struct monster_data *) NULL;
1527 // dprintf("%d base, %d random, %d scale.\n", damage->base, damage->random, damage->scale);
1528 short delta_vitality= calculate_damage(damage);
1529 // dprintf("we are doing %d damage\n", delta_vitality);
1530 world_distance external_velocity= 0;
1531 bool vertical_component= false;
1532
1533 if (!(definition->immunities&FLAG(damage->type)))
1534 {
1535 // double damage for weaknesses
1536 if (definition->weaknesses&FLAG(damage->type)) delta_vitality<<= 1;
1537
1538 // if this player was shot by a friendly, make him apologise
1539 if (aggressor_index!=NONE && get_monster_attitude(aggressor_index, target_index)==_friendly)
1540 {
1541 play_object_sound(aggressor_monster->object_index, get_monster_definition(aggressor_monster->type)->apology_sound);
1542 }
1543
1544 if (MONSTER_IS_PLAYER(monster))
1545 {
1546 damage_player(target_index, aggressor_index, aggressor_type, damage, projectile_index);
1547 }
1548 else
1549 {
1550 struct player_data *aggressor_player= (struct player_data *) NULL;
1551
1552 /* only active monsters can take damage */
1553 if (!MONSTER_IS_ACTIVE(monster)) activate_monster(target_index);
1554
1555 /* convert aggressor monster index to a player index, if possible, to record damage */
1556 if (aggressor_index!=NONE)
1557 {
1558 if (MONSTER_IS_PLAYER(aggressor_monster))
1559 {
1560 aggressor_player= get_player_data(monster_index_to_player_index(aggressor_index));
1561 aggressor_player->monster_damage_given.damage+= MAX(monster->vitality, delta_vitality);
1562 team_monster_damage_given[aggressor_player->team].damage += MAX(monster->vitality, delta_vitality);
1563 }
1564 }
1565
1566 // LP change: pegging to maximum value
1567 monster->vitality = MIN(int32(monster->vitality) - int32(delta_vitality), int32(INT16_MAX));
1568 L_Call_Monster_Damaged(target_index, aggressor_index, damage->type, delta_vitality, projectile_index);
1569
1570 if (monster->vitality > 0)
1571 {
1572 set_monster_action(target_index, _monster_is_being_hit);
1573 if ((definition->flags&_monster_is_berserker) && monster->vitality<(definition->vitality>>2))
1574 SET_MONSTER_BERSERK_STATUS(monster, true);
1575 if (aggressor_index!=NONE) switch_target_check(target_index, aggressor_index, delta_vitality);
1576
1577 // if a player shoots a monster who thinks the player is friendly; ask him what the fuck is up
1578 if (aggressor_player && get_monster_attitude(target_index, aggressor_index)==_friendly) play_object_sound(monster->object_index, definition->friendly_fire_sound);
1579 }
1580 else
1581 {
1582 if (!MONSTER_IS_DYING(monster))
1583 {
1584 short action;
1585
1586 if ((damage_kick_definitions[damage->type].death_action == _monster_is_dying_flaming) && (definition->flags&_monster_can_die_in_flames))
1587 {
1588 action= _monster_is_dying_flaming;
1589 }
1590 else
1591 {
1592 if ((damage_kick_definitions[damage->type].death_action == _monster_is_dying_hard || ((FLAG(damage->type)&definition->weaknesses) && !(definition->flags & _monster_weaknesses_cause_soft_death)) ||
1593 definition->soft_dying_shape==UNONE) && definition->hard_dying_shape!=UNONE && !(definition->flags&_monster_has_delayed_hard_death))
1594 {
1595 action= _monster_is_dying_hard;
1596 }
1597 else
1598 {
1599 action= _monster_is_dying_soft;
1600 }
1601 if (definition->flags&_monster_has_delayed_hard_death) monster->vertical_velocity= -1;
1602 }
1603
1604 if (action==_monster_is_dying_flaming || (damage->type == _damage_crushing && (definition->flags & _monster_screams_when_crushed))) play_object_sound(monster->object_index, definition->flaming_sound);
1605 set_monster_action(target_index, action);
1606 monster_died(target_index); /* orphan projectile, recalculate aggressor paths */
1607
1608 if (aggressor_player)
1609 {
1610 aggressor_player->monster_damage_given.kills+= 1;
1611 team_monster_damage_given[aggressor_player->team].kills += 1;
1612
1613 if (definition->_class&_class_human_civilian) dynamic_world->civilians_killed_by_players+= 1;
1614 }
1615
1616 if ((static_world->mission_flags & _mission_rescue_m1) && (definition->_class & _class_human_civilian_m1))
1617 {
1618 dynamic_world->current_civilian_causalties += 1;
1619 }
1620 }
1621
1622 // Lua script hook
1623 int aggressor_player_index = -1;
1624 if (aggressor_index!=NONE)
1625 if (MONSTER_IS_PLAYER(aggressor_monster))
1626 aggressor_player_index = monster_index_to_player_index(aggressor_index);
1627 L_Call_Monster_Killed (target_index, aggressor_player_index, projectile_index);
1628 }
1629 }
1630
1631
1632 if (damage->type >= 0 && damage->type < NUMBER_OF_DAMAGE_TYPES)
1633 {
1634 damage_kick_definition& kick_def = damage_kick_definitions[damage->type];
1635
1636 external_velocity = (world_distance)(kick_def.base_value + kick_def.delta_vitality_multiplier*delta_vitality);
1637 vertical_component = kick_def.is_also_vertical;
1638 if (film_profile.use_vertical_kick_threshold
1639 && kick_def.vertical_threshold
1640 && delta_vitality > kick_def.vertical_threshold)
1641 {
1642 vertical_component = true;
1643 }
1644 }
1645
1646 /*
1647 switch (damage->type)
1648 {
1649 case _damage_teleporter:
1650 external_velocity= 250;
1651 break;
1652
1653 case _damage_fusion_bolt:
1654 if (delta_vitality>100) vertical_component= true;
1655 break;
1656
1657 case _damage_electrical_staff:
1658 case _damage_yeti_claws:
1659 case _damage_compiler_bolt:
1660 vertical_component= true;
1661 external_velocity= 3*delta_vitality;
1662 break;
1663
1664 case _damage_shotgun_projectile:
1665 vertical_component= true;
1666 break;
1667
1668 case _damage_explosion:
1669 vertical_component= true;
1670 external_velocity= delta_vitality;
1671 break;
1672
1673 default:
1674 external_velocity= delta_vitality;
1675 break;
1676 }
1677 */
1678
1679 if (MONSTER_IS_DYING(monster) && external_velocity<MINIMUM_DYING_EXTERNAL_VELOCITY) external_velocity= MINIMUM_DYING_EXTERNAL_VELOCITY;
1680 external_velocity= (external_velocity*definition->external_velocity_scale)>>FIXED_FRACTIONAL_BITS;
1681 if (external_velocity && epicenter)
1682 {
1683 struct object_data *object= get_object_data(monster->object_index);
1684 world_distance dx= object->location.x - epicenter->x;
1685 world_distance dy= object->location.y - epicenter->y;
1686 world_distance dz= object->location.z + (definition->height>>1) - epicenter->z;
1687 angle direction= arctangent(dx, dy);
1688 world_distance radius= isqrt(dx*dx+dy*dy+dz*dz);
1689 world_distance vertical_velocity= (vertical_component&&radius) ? ((external_velocity*dz)/radius) : 0;
1690
1691 accelerate_monster(target_index, vertical_velocity, direction, external_velocity);
1692 }
1693 }
1694 }
1695
bump_monster(short aggressor_index,short monster_index)1696 bool bump_monster(
1697 short aggressor_index,
1698 short monster_index)
1699 {
1700 return switch_target_check(monster_index, aggressor_index, 0);
1701 }
1702
1703
legal_polygon_height_change(short polygon_index,world_distance new_floor_height,world_distance new_ceiling_height,struct damage_definition * damage)1704 bool legal_polygon_height_change(
1705 short polygon_index,
1706 world_distance new_floor_height,
1707 world_distance new_ceiling_height,
1708 struct damage_definition *damage)
1709 {
1710 world_distance new_polygon_height= new_ceiling_height-new_floor_height;
1711 struct polygon_data *polygon= get_polygon_data(polygon_index);
1712 short object_index= polygon->first_object;
1713 world_distance minimum_height= dead_player_minimum_polygon_height(polygon_index);
1714 bool legal_change= true;
1715
1716 while (object_index!=NONE)
1717 {
1718 struct object_data *object= get_object_data(object_index);
1719
1720 if (GET_OBJECT_OWNER(object)==_object_is_monster && OBJECT_IS_VISIBLE(object))
1721 {
1722 world_distance radius, height;
1723
1724 get_monster_dimensions(object->permutation, &radius, &height);
1725 if (height>=new_polygon_height)
1726 {
1727 if (damage)
1728 {
1729 damage_monster(object->permutation, NONE, NONE, (world_point3d *) NULL, damage, NONE);
1730 play_object_sound(object_index, Sound_Crunched());
1731 }
1732 legal_change= false;
1733 }
1734 }
1735
1736 object_index= object->next_object;
1737 }
1738
1739 return new_polygon_height<minimum_height ? false : legal_change;
1740 }
1741
1742 /* we�ve already checked and this monster is not obstructing the polygon from changing heights */
adjust_monster_for_polygon_height_change(short monster_index,short polygon_index,world_distance new_floor_height,world_distance new_ceiling_height)1743 void adjust_monster_for_polygon_height_change(
1744 short monster_index,
1745 short polygon_index,
1746 world_distance new_floor_height,
1747 world_distance new_ceiling_height)
1748 {
1749 struct polygon_data *polygon= get_polygon_data(polygon_index);
1750 struct monster_data *monster= get_monster_data(monster_index);
1751 world_distance radius, height;
1752
1753 get_monster_dimensions(monster_index, &radius, &height);
1754
1755 if (MONSTER_IS_PLAYER(monster))
1756 {
1757 adjust_player_for_polygon_height_change(monster_index, polygon_index, new_floor_height, new_ceiling_height);
1758 }
1759 else
1760 {
1761 struct object_data *object= get_object_data(monster->object_index);
1762
1763 if (object->location.z==polygon->floor_height) object->location.z= new_floor_height;
1764 }
1765 }
1766
accelerate_monster(short monster_index,world_distance vertical_velocity,angle direction,world_distance velocity)1767 void accelerate_monster(
1768 short monster_index,
1769 world_distance vertical_velocity,
1770 angle direction,
1771 world_distance velocity)
1772 {
1773 struct monster_data *monster= get_monster_data(monster_index);
1774
1775 if (MONSTER_IS_PLAYER(monster))
1776 {
1777 accelerate_player(monster_index, vertical_velocity, direction, velocity);
1778 }
1779 else
1780 {
1781 struct object_data *object= get_object_data(monster->object_index);
1782
1783 object->facing= NORMALIZE_ANGLE(direction+HALF_CIRCLE);
1784 monster->external_velocity+= velocity;
1785 monster->vertical_velocity+= PIN(monster->vertical_velocity+vertical_velocity, -TERMINAL_VERTICAL_MONSTER_VELOCITY, TERMINAL_VERTICAL_MONSTER_VELOCITY);
1786 }
1787 }
1788
get_monster_impact_effect(short monster_index)1789 short get_monster_impact_effect(
1790 short monster_index)
1791 {
1792 struct monster_data *monster= get_monster_data(monster_index);
1793 struct monster_definition *definition= get_monster_definition(monster->type);
1794 short impact_effect_index= definition->impact_effect;
1795
1796 if (MONSTER_IS_PLAYER(monster))
1797 {
1798 struct object_data *object= get_object_data(monster->object_index);
1799
1800 switch (object->transfer_mode)
1801 {
1802 case _xfer_static:
1803 impact_effect_index= NONE;
1804 break;
1805 }
1806 }
1807
1808 return impact_effect_index;
1809 }
1810
get_monster_melee_impact_effect(short monster_index)1811 short get_monster_melee_impact_effect(
1812 short monster_index)
1813 {
1814 struct monster_data *monster= get_monster_data(monster_index);
1815 struct monster_definition *definition= get_monster_definition(monster->type);
1816
1817 return definition->melee_impact_effect;
1818 }
1819
1820 /* ---------- private code */
1821
cause_shrapnel_damage(short monster_index)1822 static void cause_shrapnel_damage(
1823 short monster_index)
1824 {
1825 struct monster_data *monster= get_monster_data(monster_index);
1826 struct object_data *object= get_object_data(monster->object_index);
1827 struct monster_definition *definition= get_monster_definition(monster->type);
1828
1829 if (definition->shrapnel_radius!=NONE)
1830 {
1831 damage_monsters_in_radius(NONE, NONE, NONE, &object->location, object->polygon,
1832 definition->shrapnel_radius, &definition->shrapnel_damage, NONE);
1833 }
1834 }
1835
update_monster_vertical_physics_model(short monster_index)1836 static void update_monster_vertical_physics_model(
1837 short monster_index)
1838 {
1839 struct monster_data *monster= get_monster_data(monster_index);
1840 struct monster_definition *definition= get_monster_definition(monster->type);
1841 struct object_data *object= get_object_data(monster->object_index);
1842 struct polygon_data *polygon= get_polygon_data(object->polygon);
1843 struct media_data *media= polygon->media_index==NONE ? (struct media_data *) NULL : get_media_data(polygon->media_index);
1844 uint32 moving_flags= MONSTER_IS_DYING(monster) ? 0 : (definition->flags&(_monster_flys|_monster_floats));
1845 world_distance gravity= (static_world->environment_flags&_environment_low_gravity) ? (definition->gravity>>1) : definition->gravity;
1846 world_distance floor_height= polygon->floor_height;
1847 world_distance desired_height;
1848 world_distance old_height= object->location.z;
1849 bool above_ground, below_ground;
1850
1851 if (media)
1852 {
1853 // flying and floating monsters treat media as the floor
1854 if (moving_flags && media->height>floor_height) floor_height= media->height + WORLD_ONE/16;
1855
1856 // take damage if necessary
1857 if (media->height>object->location.z)
1858 {
1859 struct damage_definition *damage= get_media_damage(polygon->media_index, FIXED_ONE);
1860
1861 if (damage) damage_monster(monster_index, NONE, NONE, (world_point3d *) NULL, damage, NONE);
1862 }
1863 }
1864 desired_height= (monster->desired_height==NONE||MONSTER_IS_DYING(monster)) ? polygon->floor_height : monster->desired_height;
1865 above_ground= object->location.z>desired_height;
1866 below_ground= object->location.z<desired_height;
1867
1868 switch (moving_flags)
1869 {
1870 case 0:
1871 /* if we�re above the floor, adjust vertical velocity */
1872 if (above_ground) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity);
1873 if (below_ground) monster->vertical_velocity= 0, object->location.z= desired_height;
1874 break;
1875
1876 case _monster_flys:
1877 if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity);
1878 if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity);
1879 break;
1880
1881 case _monster_floats:
1882 if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity);
1883 if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity);
1884 break;
1885
1886 default:
1887 /* can�t fly and float, beavis */
1888 // LP change: this stuff put in to handle the map "Aqualung" correctly
1889 if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity);
1890 if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity);
1891 break;
1892 }
1893
1894 /* add our vertical velocity to z */
1895 object->location.z= PIN(object->location.z+monster->vertical_velocity, polygon->floor_height, polygon->ceiling_height-definition->height);
1896
1897 /* if we�re under the floor moving down, put us on the floor and clear our velocity;
1898 if we�re above the floor moving up, put us on the floor and clear our velocity if we were previously below ground */
1899 switch (moving_flags)
1900 {
1901 case 0:
1902 case _monster_floats:
1903 if (object->location.z<=desired_height && monster->vertical_velocity<0) monster->vertical_velocity= 0, object->location.z= desired_height;
1904 if (object->location.z>=desired_height && monster->vertical_velocity>0 && below_ground) monster->vertical_velocity= 0, object->location.z= desired_height;
1905 break;
1906
1907 case _monster_flys:
1908 default: // LP: added this case to handle "Aqualung" correctly
1909 if (object->location.z<=desired_height && above_ground) monster->vertical_velocity>>= 1, object->location.z= desired_height;
1910 if (object->location.z>=desired_height && below_ground) monster->vertical_velocity>>= 1, object->location.z= desired_height;
1911 break;
1912 }
1913
1914 /* reset desired height (flying and floating monsters often change this later) */
1915 if (moving_flags&_monster_flys)
1916 {
1917 /* we�re flying!: if we have no target, take the middle ground; if we have a target aim
1918 for his midsection */
1919 if (MONSTER_HAS_VALID_TARGET(monster))
1920 {
1921 struct monster_data *target= get_monster_data(monster->target_index);
1922 struct monster_definition *target_definition= get_monster_definition(target->type);
1923
1924 monster->desired_height= get_object_data(target->object_index)->location.z + ((target_definition->height-definition->height)>>1) + definition->preferred_hover_height;
1925 monster->desired_height= PIN(monster->desired_height, floor_height+(definition->height>>2), polygon->ceiling_height-definition->height);
1926 }
1927 else
1928 {
1929 if (monster->random_desired_height<floor_height || monster->random_desired_height>polygon->ceiling_height)
1930 {
1931 world_distance delta= polygon->ceiling_height-floor_height-definition->height;
1932
1933 monster->random_desired_height= floor_height + ((delta>0) ? (global_random()%delta) : 0);
1934 }
1935
1936 monster->desired_height= MONSTER_IS_DYING(monster) ? polygon->floor_height : monster->random_desired_height;
1937 }
1938 }
1939 else
1940 {
1941 monster->desired_height= floor_height;
1942 }
1943
1944 monster->sound_location= object->location;
1945 monster->sound_polygon_index= object->polygon;
1946 monster->sound_location.z+= definition->height - (definition->height>>1);
1947
1948 if (media)
1949 {
1950 world_point3d location= object->location;
1951 short media_effect_type= NONE;
1952
1953 location.z= media->height;
1954 if (old_height>=media->height && object->location.z<media->height)
1955 {
1956 media_effect_type= _large_media_detonation_effect;
1957 }
1958 if (old_height<media->height && object->location.z>=media->height)
1959 {
1960 media_effect_type= _large_media_emergence_effect;
1961 }
1962
1963 if (media_effect_type!=NONE)
1964 {
1965 short effect_type= NONE;
1966
1967 get_media_detonation_effect(polygon->media_index, media_effect_type, &effect_type);
1968 new_effect(&location, object->polygon, effect_type, object->facing);
1969 }
1970 }
1971 }
1972
update_monster_physics_model(short monster_index)1973 static void update_monster_physics_model(
1974 short monster_index)
1975 {
1976 struct monster_data *monster= get_monster_data(monster_index);
1977 struct monster_definition *definition= get_monster_definition(monster->type);
1978 struct object_data *object= get_object_data(monster->object_index);
1979
1980 if (monster->external_velocity)
1981 {
1982 world_point3d new_location= object->location;
1983 world_distance adjusted_floor_height, adjusted_ceiling_height;
1984 angle negative_facing= NORMALIZE_ANGLE(HALF_CIRCLE+object->facing);
1985 struct polygon_data *polygon;
1986 short supporting_polygon_index;
1987
1988 /* move the monster */
1989 translate_point2d((world_point2d*)&new_location, monster->external_velocity, negative_facing);
1990 keep_line_segment_out_of_walls(object->polygon, &object->location, &new_location,
1991 0, definition->height, &adjusted_floor_height, &adjusted_ceiling_height, &supporting_polygon_index);
1992 if (legal_monster_move(monster_index, negative_facing, &new_location)==NONE)
1993 {
1994 short old_polygon_index= object->polygon;
1995
1996 if (translate_map_object(monster->object_index, &new_location, NONE)) monster_moved(monster_index, old_polygon_index);
1997 }
1998
1999 /* slow him down if he�s touching the ground or flying */
2000 polygon= get_polygon_data(object->polygon);
2001 if (object->location.z<=polygon->floor_height || (definition->flags&(_monster_flys|_monster_floats)))
2002 {
2003 if ((monster->external_velocity-= MONSTER_EXTERNAL_DECELERATION)<MONSTER_MINIMUM_EXTERNAL_VELOCITY)
2004 {
2005 monster->external_velocity= 0;
2006 }
2007 }
2008 }
2009 }
2010
monster_needs_path(short monster_index,bool immediately)2011 static void monster_needs_path(
2012 short monster_index,
2013 bool immediately)
2014 {
2015 struct monster_data *monster= get_monster_data(monster_index);
2016
2017 if (monster->path!=NONE && immediately) delete_path(monster->path), monster->path= NONE;
2018 if (monster->action==_monster_is_moving && immediately) set_monster_action(monster_index, _monster_is_stationary);
2019 SET_MONSTER_NEEDS_PATH_STATUS(monster, true);
2020 }
2021
set_monster_mode(short monster_index,short new_mode,short target_index)2022 void set_monster_mode(
2023 short monster_index,
2024 short new_mode,
2025 short target_index)
2026 {
2027 struct monster_data *monster= get_monster_data(monster_index);
2028
2029 /* if we were locked on a monster in our own polygon and we lost him then we don�t have a path
2030 and going anywhere would be dangerous so we need to ask for a new path */
2031 if (monster->mode==_monster_locked&&new_mode!=_monster_locked&&monster->path==NONE) monster_needs_path(monster_index, false);
2032
2033 switch (new_mode)
2034 {
2035 case _monster_locked:
2036 (void)get_monster_data(target_index); /* for bounds checking only */
2037 monster->target_index= target_index;
2038 CLEAR_TARGET_DAMAGE_FLAG(monster);
2039 // if (target_index==local_player->monster_index)
2040 // dprintf("monster #%d is locked on new target #%d;g;", monster_index, target_index);
2041 // switch (monster->type)
2042 // {
2043 // case _civilian_crew: case _civilian_engineering: case _civilian_science: case _civilian_security:
2044 // dprintf("monster #%d is locked on new target #%d;g;", monster_index, target_index);
2045 // }
2046 break;
2047
2048 case _monster_losing_lock: /* target_index ignored, but still valid */
2049 case _monster_lost_lock:
2050 (void)get_monster_data(monster->target_index); /* for bounds checking only */
2051 break;
2052
2053 case _monster_unlocked:
2054 monster->target_index= NONE;
2055 break;
2056
2057 default:
2058 assert(false);
2059 break;
2060 }
2061
2062 monster->mode= new_mode;
2063 }
2064
2065 /* this function decides what the given monster actually wants to do, and then generates a path
2066 to get him there; if a monster who has lost lock calls this function, he will be forced to
2067 wander randomly or follow a guard path. */
generate_new_path_for_monster(short monster_index)2068 static void generate_new_path_for_monster(
2069 short monster_index)
2070 {
2071 struct monster_data *monster= get_monster_data(monster_index);
2072 struct object_data *object= get_object_data(monster->object_index);
2073 struct monster_definition *definition= get_monster_definition(monster->type);
2074 struct monster_pathfinding_data data;
2075 short destination_polygon_index;
2076 world_point2d *destination;
2077 world_vector2d bias;
2078
2079 /* delete this monster�s old path, if one exists, and clear the need path flag */
2080 if (monster->path!=NONE) delete_path(monster->path), monster->path= NONE;
2081 SET_MONSTER_NEEDS_PATH_STATUS(monster, false);
2082
2083 switch (monster->mode)
2084 {
2085 case _monster_losing_lock:
2086 /* our target is out of sight, but we�re still zen-ing his position until we run out
2087 of intelligence points */
2088 case _monster_locked:
2089 {
2090 struct monster_data *target= get_monster_data(monster->target_index);
2091 struct object_data *target_object= get_object_data(target->object_index);
2092
2093 if (definition->random_sound_mask && !(global_random()&definition->random_sound_mask)) play_object_sound(monster->object_index, definition->random_sound);
2094
2095 /* if we can�t attack, run away, otherwise go for the target */
2096 if (definition->flags&_monster_cannot_attack)
2097 {
2098 // LP changed: unnecessary to interrupt for this
2099 // dprintf("%p", monster);
2100 destination= (world_point2d *) &bias;
2101 bias.i= object->location.x - target_object->location.x;
2102 bias.j= object->location.y - target_object->location.y;
2103 destination_polygon_index= NONE;
2104 }
2105 else
2106 {
2107 /* if we still have lock, just build a new path and keep charging */
2108 destination= (world_point2d *) &target_object->location;
2109 destination_polygon_index= MONSTER_IS_PLAYER(target) ?
2110 get_polygon_index_supporting_player(monster->target_index) :
2111 target_object->polygon;
2112 }
2113 break;
2114 }
2115
2116 case _monster_lost_lock:
2117 /* if we lost lock during this path and we went as far as we could go, unlock */
2118 set_monster_mode(monster_index, _monster_unlocked, NONE);
2119 // dprintf("monster #%d lost lock and reached end of path;g;", monster_index);
2120 case _monster_unlocked:
2121 /* if we�re unlocked and need a new path, follow our guard path if we have one and
2122 run around randomly if we don�t */
2123 if ((destination_polygon_index= monster->goal_polygon_index)!=NONE)
2124 {
2125 destination= &get_polygon_data(destination_polygon_index)->center;
2126 }
2127 else
2128 {
2129 destination= (world_point2d *) NULL;
2130 }
2131 break;
2132
2133 default:
2134 assert(false);
2135 break;
2136 }
2137
2138 // dprintf("#%d: generating new %spath for monster #%d;g;", dynamic_world->tick_count, destination?"":"random ", monster_index);
2139
2140 data.definition= definition;
2141 data.monster= monster;
2142 data.cross_zone_boundaries= destination_polygon_index==NONE ? false : true;
2143
2144 monster->path= new_path((world_point2d *)&object->location, object->polygon, destination,
2145 destination_polygon_index, 3*definition->radius, monster_pathfinding_cost_function, &data);
2146 if (monster->path==NONE)
2147 {
2148 if (monster->action!=_monster_is_being_hit || MONSTER_IS_DYING(monster)) set_monster_action(monster_index, _monster_is_stationary);
2149 set_monster_mode(monster_index, _monster_unlocked, NONE);
2150 }
2151 else
2152 {
2153 advance_monster_path(monster_index);
2154 }
2155 }
2156
2157
2158 /* somebody just did damage to us; see if we should start attacking them or not. berserk
2159 monsters always switch targets. this is where we check to see if we go berserk, right?
2160 monster->vitality has already been changed (a monster who just bumped into another monster
2161 also calls this, with a delta_vitality of zero). returns true if an attack was started. */
switch_target_check(short monster_index,short attacker_index,short delta_vitality)2162 static bool switch_target_check(
2163 short monster_index,
2164 short attacker_index,
2165 short delta_vitality)
2166 {
2167 struct monster_data *monster= get_monster_data(monster_index);
2168 bool switched_target= false;
2169
2170 if (!MONSTER_IS_PLAYER(monster) && !MONSTER_IS_DYING(monster)) /* don�t mess with players or dying monsters */
2171 {
2172 if (MONSTER_HAS_VALID_TARGET(monster) && monster->target_index==attacker_index)
2173 {
2174 /* if we didn�t know where our target was and he just shot us, we sort of like, know
2175 where he is now */
2176 if (monster->mode==_monster_losing_lock)
2177 {
2178 set_monster_mode(monster_index, _monster_locked, attacker_index);
2179 monster_needs_path(monster_index, false);
2180 }
2181
2182 /* if we�re already after this guy and he just did damage to us, remember that */
2183 if (delta_vitality)
2184 SET_TARGET_DAMAGE_FLAG(monster);
2185
2186 switched_target= true;
2187 }
2188 else
2189 {
2190 struct monster_definition *definition= get_monster_definition(monster->type);
2191 struct monster_data *attacker= get_monster_data(attacker_index);
2192
2193 CLEAR_TARGET_DAMAGE_FLAG(monster);
2194
2195 if (!MONSTER_IS_DYING(attacker) && !(definition->flags&_monster_cannot_attack))
2196 {
2197 /* if our attacker is an enemy (or a neutral doing non-zero damage or we are berserk) and
2198 a) we�re inactive, or,
2199 b) idle, or,
2200 c) unlocked, or,
2201 d) our current target has not done any damage
2202 then go kick his ass. */
2203 if (mTYPE_IS_ENEMY(definition, attacker->type) ||
2204 (TYPE_IS_NEUTRAL(definition, attacker->type)&&delta_vitality) ||
2205 MONSTER_IS_BERSERK(monster))
2206 {
2207 if (!MONSTER_IS_ACTIVE(monster) ||
2208 MONSTER_IS_IDLE(monster) ||
2209 monster->mode!=_monster_locked ||
2210 !TARGET_HAS_DONE_DAMAGE(monster))
2211 {
2212 change_monster_target(monster_index, attacker_index);
2213 if (delta_vitality) SET_TARGET_DAMAGE_FLAG(monster);
2214 switched_target= true;
2215 }
2216 }
2217 }
2218 }
2219 }
2220
2221 return switched_target;
2222 }
2223
get_monster_attitude(short monster_index,short target_index)2224 static short get_monster_attitude(
2225 short monster_index,
2226 short target_index)
2227 {
2228 struct monster_data *monster= get_monster_data(monster_index);
2229 struct monster_definition *definition= get_monster_definition(monster->type);
2230 struct monster_data *target= get_monster_data(target_index);
2231 short target_type= target->type;
2232 short attitude;
2233
2234 /* berserk monsters are hostile toward everything */
2235 if (mTYPE_IS_ENEMY(definition, target_type) || MONSTER_IS_BERSERK(monster) ||
2236 (MONSTER_HAS_VALID_TARGET(monster) && monster->target_index==target_index) ||
2237 ((definition->_class&_class_human_civilian) && MONSTER_IS_PLAYER(target) && dynamic_world->civilians_killed_by_players>=CIVILIANS_KILLED_BY_PLAYER_THRESHHOLD))
2238 {
2239 attitude= _hostile;
2240 }
2241 else
2242 {
2243 attitude= (TYPE_IS_FRIEND(definition, target_type)) ? _friendly : _neutral;
2244 }
2245
2246 // if ((definition->_class&_class_human_civilian) && MONSTER_IS_PLAYER(target))
2247 // {
2248 // dprintf("#%d vs. #%d ==> #%d", monster_index, target_index, attitude);
2249 // }
2250
2251 return attitude;
2252 }
2253
2254 /* find_closest_appropriate_target() tries to do just that. it is a little broken in that it
2255 treats all monsters in a given polygon as if they were the same distance away, which could
2256 result in strange behavior. the assumption is that if there is a more accessable hostile monster
2257 nearby, that monster will attack and thus end a possible wild goose chase. if there is a
2258 closer hostile target which the aggressor subsequently attempts to move through, he will
2259 change lock and attack the obstruction instead, which will help minimize weirdness.
2260 full_circle is passed directly to clear_line_of_sight(). */
find_closest_appropriate_target(short aggressor_index,bool full_circle)2261 short find_closest_appropriate_target(
2262 short aggressor_index,
2263 bool full_circle)
2264 {
2265 struct monster_data *aggressor= get_monster_data(aggressor_index);
2266 struct monster_definition *definition= get_monster_definition(aggressor->type);
2267 short closest_hostile_target_index= NONE;
2268
2269 if (MONSTER_IS_ACTIVE(aggressor))
2270 {
2271 int32 flood_flags= _pass_one_zone_border;
2272 short polygon_index= get_object_data(get_monster_data(aggressor_index)->object_index)->polygon;
2273
2274 /* flood out from the aggressor monster�s polygon, searching through the object lists of all
2275 polygons we encounter */
2276 polygon_index= flood_map(polygon_index, INT32_MAX, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags);
2277 while (polygon_index!=NONE && closest_hostile_target_index==NONE)
2278 {
2279 short object_index;
2280 struct object_data *object;
2281
2282 /* loop through all objects in this polygon looking for hostile monsters we can see */
2283 for (object_index= get_polygon_data(polygon_index)->first_object; object_index!=NONE; object_index= object->next_object)
2284 {
2285 object= get_object_data(object_index);
2286 if (GET_OBJECT_OWNER(object)==_object_is_monster && OBJECT_IS_VISIBLE(object))
2287 {
2288 short target_monster_index= object->permutation;
2289 struct monster_data *target_monster= get_monster_data(target_monster_index);
2290
2291 if (!MONSTER_IS_DYING(target_monster) && target_monster_index!=aggressor_index)
2292 {
2293 if (get_monster_attitude(aggressor_index, target_monster_index)==_hostile)
2294 {
2295 if (((definition->flags&_monster_is_omniscent) || clear_line_of_sight(aggressor_index, target_monster_index, full_circle)) &&
2296 (MONSTER_IS_ACTIVE(target_monster) || MONSTER_IS_PLAYER(target_monster) || (static_world->environment_flags&_environment_rebellion)))
2297 {
2298 /* found hostile, live, visible monster */
2299 closest_hostile_target_index= target_monster_index;
2300 break;
2301 }
2302 }
2303 }
2304 }
2305 }
2306
2307 polygon_index= flood_map(NONE, INT32_MAX, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags);
2308 }
2309 }
2310 else
2311 {
2312 short player_index;
2313
2314 /* if this monster is deactivated, only seeing a player will activate him */
2315
2316 for (player_index= 0; player_index<dynamic_world->player_count; ++player_index)
2317 {
2318 struct player_data *player= get_player_data(player_index);
2319
2320 if (get_monster_attitude(aggressor_index, player->monster_index)==_hostile &&
2321 clear_line_of_sight(aggressor_index, player->monster_index, full_circle))
2322 {
2323 closest_hostile_target_index= player->monster_index;
2324
2325 break;
2326 }
2327 }
2328 }
2329
2330 return closest_hostile_target_index;
2331 }
2332
2333 /* if �full_circle� is true, the monster can see in all directions. if �full_circle� is false
2334 the monster respects his visual_arc and current facing. clear_line_of_sight() is implemented
2335 wholly in 2D and only attempts to connect the centers of the two monsters by a line. */
clear_line_of_sight(short viewer_index,short target_index,bool full_circle)2336 static bool clear_line_of_sight(
2337 short viewer_index,
2338 short target_index,
2339 bool full_circle)
2340 {
2341 struct monster_data *viewer= get_monster_data(viewer_index);
2342 struct object_data *viewer_object= get_object_data(viewer->object_index);
2343 struct monster_definition *viewer_definition= get_monster_definition(viewer->type);
2344 struct monster_data *target= get_monster_data(target_index);
2345 struct object_data *target_object= get_object_data(target->object_index);
2346 bool target_visible= true;
2347
2348 {
2349 world_point3d *origin= &viewer_object->location;
2350 world_point3d *destination= &target_object->location;
2351 // LP change: made this long-distance friendly
2352 int32 dx= int32(destination->x)-int32(origin->x);
2353 int32 dy= int32(destination->y)-int32(origin->y);
2354 world_distance dz= destination->z-origin->z;
2355 int32 distance2d= GUESS_HYPOTENUSE(ABS(dx), ABS(dy));
2356
2357 /* if we can�t see full circle, make sure the target is in our visual arc */
2358 if (!full_circle)
2359 {
2360 angle theta= arctangent(dx, dy)-viewer_object->facing;
2361 angle phi= arctangent(distance2d, ABS(dz));
2362
2363 if (ABS(theta)>viewer_definition->half_visual_arc) target_visible= false;
2364 if (phi>=viewer_definition->half_vertical_visual_arc&&phi<FULL_CIRCLE-viewer_definition->half_vertical_visual_arc) target_visible= false;
2365 }
2366
2367 /* we can�t see some transfer modes */
2368 switch (target_object->transfer_mode)
2369 {
2370 case _xfer_invisibility:
2371 case _xfer_subtle_invisibility:
2372 if (distance2d>viewer_definition->dark_visual_range) target_visible= false;
2373 break;
2374 }
2375
2376 /* make sure the target is within our visual_range (taking any of his active
2377 effects, i.e. invisibility, into account) and that he isn�t standing in a
2378 dark polygon beyond our dark_visual_range. */
2379 if (target_visible)
2380 {
2381 if (distance2d>viewer_definition->visual_range) // || (distance2d>viewer_definition->dark_visual_range&&get_object_light_intensity(target->object_index)<=LOW_LIGHT_INTENSITY))
2382 {
2383 target_visible= false;
2384 }
2385 }
2386
2387 /* make sure there are no non-transparent lines between the viewer and the target */
2388 if (target_visible)
2389 {
2390 short polygon_index= viewer_object->polygon;
2391 short line_index;
2392
2393 do
2394 {
2395 line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)origin, (world_point2d *)destination);
2396 if (line_index!=NONE)
2397 {
2398 if (LINE_IS_TRANSPARENT(get_line_data(line_index)))
2399 {
2400 /* transparent line, find adjacent polygon */
2401 polygon_index= find_adjacent_polygon(polygon_index, line_index);
2402 // LP change: make no polygon act like a non-transparent line
2403 if (polygon_index == NONE) target_visible= false;
2404 }
2405 else
2406 {
2407 /* non-transparent line, target not visible */
2408 target_visible= false;
2409 }
2410 }
2411 else
2412 {
2413 /* we got to the target�s (x,y) location, but we�re in a different polygon;
2414 he�s invisible */
2415 if (polygon_index!=target_object->polygon) target_visible= false;
2416 }
2417 }
2418 while (target_visible&&line_index!=NONE);
2419 }
2420 }
2421
2422 return target_visible;
2423 }
2424
2425 /* lock the given monster onto the given target, playing a locking sound if the monster
2426 previously didn�t have a lock */
change_monster_target(short monster_index,short target_index)2427 void change_monster_target(
2428 short monster_index,
2429 short target_index)
2430 {
2431 /* locking on ourselves would be cool, but ... */
2432 if (monster_index!=target_index)
2433 {
2434 struct monster_data *monster= get_monster_data(monster_index);
2435 struct monster_definition *definition= get_monster_definition(monster->type);
2436
2437 if (target_index!=NONE)
2438 {
2439 /* only active monsters can have lock, so activate inactive monsters */
2440 if (!MONSTER_IS_ACTIVE(monster)) activate_monster(monster_index);
2441
2442 /* play activation sounds (including activating on a friendly) */
2443 if (monster->target_index!=target_index && TYPE_IS_FRIEND(definition, get_monster_data(target_index)->type))
2444 {
2445 play_object_sound(monster->object_index, definition->friendly_activation_sound);
2446 }
2447 else if (!(definition->flags & _monster_makes_sound_when_activated))
2448 {
2449 if (monster->mode==_monster_unlocked) play_object_sound(monster->object_index, definition->activation_sound);
2450 }
2451
2452 /* instantiate the new target and ask for a new path */
2453 if (MONSTER_HAS_VALID_TARGET(monster) && target_index!=monster->target_index) CLEAR_TARGET_DAMAGE_FLAG(monster);
2454 monster_needs_path(monster_index, false);
2455 set_monster_mode(monster_index, _monster_locked, target_index);
2456 }
2457 else
2458 {
2459 if (MONSTER_IS_ACTIVE(monster))
2460 {
2461 /* no target, if we�re not unlocked mark us as unlocked and ask for a new path */
2462 if (monster->mode!=_monster_unlocked)
2463 {
2464 // dprintf("monster #%d was locked on NONE;g;", monster_index);
2465
2466 set_monster_mode(monster_index, _monster_unlocked, NONE);
2467 monster_needs_path(monster_index, false);
2468 }
2469 }
2470 }
2471 }
2472 }
2473
handle_moving_or_stationary_monster(short monster_index)2474 static void handle_moving_or_stationary_monster(
2475 short monster_index)
2476 {
2477 struct monster_data *monster= get_monster_data(monster_index);
2478 struct object_data *object= get_object_data(monster->object_index);
2479 struct monster_definition *definition= get_monster_definition(monster->type);
2480
2481 if (monster->path==NONE && monster->mode!=_monster_locked && monster->action==_monster_is_stationary)
2482 {
2483 /* stationary, unlocked monsters without paths cannot move */
2484 monster_needs_path(monster_index, false);
2485 }
2486 else
2487 {
2488 world_distance distance_moved= definition->speed;
2489
2490 /* base speed on difficulty level (for aliens) and berserk status */
2491 if (definition->flags&_monster_is_alien)
2492 {
2493 switch (dynamic_world->game_information.difficulty_level)
2494 {
2495 case _wuss_level: distance_moved-= distance_moved>>3; break;
2496 case _easy_level: distance_moved-= distance_moved>>4; break;
2497 case _major_damage_level: distance_moved+= distance_moved>>3; break;
2498 case _total_carnage_level: distance_moved+= distance_moved>>2; break;
2499 }
2500 }
2501 if (MONSTER_IS_BERSERK(monster)) distance_moved+= (distance_moved>>1);
2502
2503 if (monster->action!=_monster_is_waiting_to_attack_again)
2504 {
2505 if (translate_monster(monster_index, distance_moved))
2506 {
2507 /* we moved: _monster_is_stationary becomes _monster_is_moving */
2508 if (monster->action==_monster_is_stationary) set_monster_action(monster_index, _monster_is_moving);
2509 }
2510 else
2511 {
2512 /* we couldn�t move: _monster_is_moving becomes _monster_is_stationary */
2513 if (monster->action==_monster_is_moving) set_monster_action(monster_index, _monster_is_stationary);
2514 monster->ticks_since_attack+= 1; /* attacks occur twice as frequently if we can�t move (damnit!) */
2515 }
2516 }
2517 else
2518 {
2519 monster->ticks_since_attack+= 1;
2520 }
2521
2522 /* whether we moved or not, see if we can attack if we have lock */
2523 monster->ticks_since_attack+= MONSTER_IS_BERSERK(monster) ? 3 : 1;
2524 if (OBJECT_WAS_ANIMATED(object) && monster->mode==_monster_locked)
2525 {
2526 short attack_frequency= definition->attack_frequency;
2527
2528 if (definition->flags&_monster_is_alien)
2529 {
2530 switch (dynamic_world->game_information.difficulty_level)
2531 {
2532 case _wuss_level: attack_frequency= 3*attack_frequency; break;
2533 case _easy_level: attack_frequency= 2*attack_frequency; break;
2534 case _major_damage_level: attack_frequency= attack_frequency>>1; break;
2535 case _total_carnage_level: attack_frequency= attack_frequency>>2; break;
2536 }
2537 }
2538
2539 if (monster->ticks_since_attack>attack_frequency)
2540 {
2541 if (try_monster_attack(monster_index))
2542 {
2543 /* activate with lock nearby monsters on our target */
2544 activate_nearby_monsters(monster->target_index, monster_index, _pass_one_zone_border, MONSTER_ALERT_ACTIVATION_RANGE);
2545 }
2546 else
2547 {
2548 if (monster->action==_monster_is_waiting_to_attack_again)
2549 {
2550 set_monster_action(monster_index, _monster_is_stationary);
2551 monster_needs_path(monster_index, true);
2552 }
2553 }
2554 }
2555 }
2556 }
2557 }
2558
set_monster_action(short monster_index,short action)2559 void set_monster_action(
2560 short monster_index,
2561 short action)
2562 {
2563 struct monster_data *monster= get_monster_data(monster_index);
2564 struct monster_definition *definition= get_monster_definition(monster->type);
2565 shape_descriptor shape;
2566
2567 /* what shape should we use? */
2568 if (action==_monster_is_dying_flaming)
2569 {
2570 shape= FLAMING_DYING_SHAPE;
2571 }
2572 else
2573 {
2574 switch (action)
2575 {
2576 case _monster_is_waiting_to_attack_again:
2577 case _monster_is_stationary: shape= definition->stationary_shape; break;
2578 case _monster_is_moving: shape= definition->moving_shape; break;
2579 case _monster_is_attacking_close: shape= definition->melee_attack.attack_shape; break;
2580 case _monster_is_attacking_far: shape= definition->ranged_attack.attack_shape; break;
2581 case _monster_is_being_hit: shape= definition->hit_shapes; break;
2582 case _monster_is_dying_hard: shape= definition->hard_dying_shape; break;
2583 case _monster_is_dying_soft: shape= definition->soft_dying_shape; break;
2584 case _monster_is_teleporting_in: shape= definition->teleport_in_shape; break;
2585 case _monster_is_teleporting_out: shape= definition->teleport_out_shape; break;
2586 default: dprintf("what is monster action #%d?", action); assert(false); break;
2587 }
2588
2589 shape= shape==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, shape);
2590 }
2591
2592 if (shape!=UNONE)
2593 {
2594 /* only set the action of the shape is not UNONE */
2595 monster->action= action;
2596 set_object_shape_and_transfer_mode(monster->object_index, shape, NONE);
2597
2598 /* if this monster does shrapnel damage, do it */
2599 if (action == _monster_is_dying_hard)
2600 {
2601 if (definition->flags & _monster_has_delayed_hard_death)
2602 {
2603 cause_shrapnel_damage(monster_index);
2604 }
2605 else if (film_profile.key_frame_zero_shrapnel_fix)
2606 {
2607 object_data* object = get_object_data(monster->object_index);
2608 shape_animation_data* animation = get_shape_animation_data(object->shape);
2609 if (animation && animation->key_frame == 0)
2610 {
2611 cause_shrapnel_damage(monster_index);
2612 }
2613 }
2614 }
2615
2616 if ((definition->flags&_monster_has_nuclear_hard_death) && action==_monster_is_dying_hard)
2617 {
2618 start_fade(_fade_long_bright);
2619 SoundManager::instance()->PlayLocalSound(Sound_Exploding());
2620 }
2621 }
2622 }
2623
2624 /* do whatever needs to be done when this monster dies and remove it from the monster list */
kill_monster(short monster_index)2625 static void kill_monster(
2626 short monster_index)
2627 {
2628 struct monster_data *monster= get_monster_data(monster_index);
2629 struct monster_definition *definition= get_monster_definition(monster->type);
2630 struct object_data *object= get_object_data(monster->object_index);
2631 shape_descriptor shape;
2632
2633 switch (monster->action)
2634 {
2635 case _monster_is_dying_soft:
2636 shape= definition->soft_dead_shapes==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, definition->soft_dead_shapes);
2637 break;
2638 case _monster_is_dying_hard:
2639 shape= definition->hard_dead_shapes==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, definition->hard_dead_shapes);
2640 break;
2641 case _monster_is_dying_flaming:
2642 shape= FLAMING_DEAD_SHAPE;
2643 break;
2644
2645 default:
2646 assert(false);
2647 break;
2648 }
2649
2650 /* add an item if we�re supposed to be carrying something */
2651 if (definition->carrying_item_type!=NONE && monster->action==_monster_is_dying_soft)
2652 {
2653 world_distance radius, height;
2654 world_point3d random_point;
2655 short random_polygon_index;
2656
2657 get_monster_dimensions(monster_index, &radius, &height);
2658 random_point_on_circle(&object->location, object->polygon, radius, &random_point, &random_polygon_index);
2659 if (random_polygon_index!=NONE)
2660 {
2661 struct polygon_data *random_polygon= get_polygon_data(random_polygon_index);
2662 struct object_location location;
2663
2664 switch (random_polygon->type)
2665 {
2666 case _polygon_is_platform:
2667 case _polygon_is_item_impassable:
2668 case _polygon_is_monster_impassable:
2669 case _polygon_is_teleporter:
2670 break;
2671
2672 default:
2673 location.polygon_index= random_polygon_index;
2674 location.p.x= random_point.x, location.p.y= random_point.y, location.p.z= 0;
2675 location.yaw= 0;
2676 location.flags= 0;
2677 new_item(&location, definition->carrying_item_type);
2678 break;
2679 }
2680 }
2681 }
2682
2683 /* stuff in an appropriate dead shape (or remove our object if we don�t have a dead shape) */
2684 bool remove_object = (shape == UNONE);
2685 if (!remove_object && (static_world->environment_flags & _environment_ouch_m1))
2686 {
2687 struct polygon_data *polygon = get_polygon_data(object->polygon);
2688 switch (polygon->type)
2689 {
2690 case _polygon_is_major_ouch:
2691 case _polygon_is_minor_ouch:
2692 remove_object = true;
2693 break;
2694 case _polygon_is_platform:
2695 if (PLATFORM_IS_FLOODED(get_platform_data(polygon->permutation)) &&
2696 find_flooding_polygon(object->polygon) != NONE)
2697 remove_object = true;
2698 break;
2699 }
2700 }
2701
2702 if (remove_object)
2703 {
2704 remove_map_object(monster->object_index);
2705 }
2706 else
2707 {
2708 turn_object_to_shit(monster->object_index);
2709 randomize_object_sequence(monster->object_index, shape);
2710 }
2711
2712 /* recover original type and notify the object stuff a monster died */
2713 if (monster->flags&_monster_was_promoted) monster->type-= 1;
2714 if (monster->flags&_monster_was_demoted) monster->type+= 1;
2715 object_was_just_destroyed(_object_is_monster, monster->type);
2716
2717 L_Invalidate_Monster(monster_index);
2718 MARK_SLOT_AS_FREE(monster);
2719 }
2720
2721 /* move the monster along his current heading; if he reaches the center of his destination square,
2722 then point him at the next square and send him off. this used to chuck if the monster moved
2723 too far during a certain turn (which was completely possible when the player was wearing the
2724 red cloak in Pathways), but that was fixed. i just recoded this for marathon and it looks
2725 a hell of a lot better now. */
translate_monster(short monster_index,world_distance distance)2726 static bool translate_monster(
2727 short monster_index,
2728 world_distance distance)
2729 {
2730 struct monster_data *monster= get_monster_data(monster_index);
2731 struct object_data *object= get_object_data(monster->object_index);
2732 struct monster_definition *definition= get_monster_definition(monster->type);
2733 world_point3d new_location;
2734 short obstacle_index;
2735 bool legal_move= false;
2736
2737 new_location= object->location;
2738 translate_point2d((world_point2d *)&new_location, distance, object->facing);
2739
2740 /* find out where we�re going and see if we could actually move there */
2741 if ((obstacle_index= legal_monster_move(monster_index, object->facing, &new_location))==NONE)
2742 {
2743 /* legal move: see if there is a platform that we have to open or wait for,
2744 if not move, if so, wait */
2745
2746 short feature_index;
2747 short relevant_polygon_index;
2748
2749 legal_move= true;
2750 switch (find_obstructing_terrain_feature(monster_index, &feature_index, &relevant_polygon_index))
2751 {
2752 case _entering_platform_polygon:
2753 switch (monster_can_enter_platform(feature_index, relevant_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta))
2754 {
2755 case _platform_will_never_be_accessable:
2756 monster_needs_path(monster_index, true);
2757 break;
2758
2759 case _platform_will_be_accessable:
2760 /* we avoid vidding the door by only trying to open it every door_retry_mask+1 ticks */
2761 if (!(dynamic_world->tick_count&definition->door_retry_mask)) try_and_change_platform_state(feature_index, true);
2762 SET_MONSTER_IDLE_STATUS(monster, true);
2763 legal_move= false;
2764 break;
2765
2766 /* _platform_is_accessable */
2767 }
2768 break;
2769
2770 case _leaving_platform_polygon:
2771 switch (monster_can_leave_platform(feature_index, relevant_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta))
2772 {
2773 case _exit_will_never_be_accessable:
2774 monster_needs_path(monster_index, true);
2775 break;
2776
2777 case _exit_will_be_accessable:
2778 SET_MONSTER_IDLE_STATUS(monster, true);
2779 legal_move= false;
2780 break;
2781
2782 /* _exit_is_accessable, ignored */
2783 }
2784 break;
2785
2786 case _flying_or_floating_transition:
2787 /* there is a wall in our way which we have to rise (or fall) along, so don�t
2788 go anywhere unless we�re over it (or under it) */
2789 if (ABS(object->location.z-monster->desired_height)>MINIMUM_FLOATING_HEIGHT) legal_move= false;
2790 break;
2791
2792 case _standing_on_sniper_ledge:
2793 /* we�ve been told to freeze on a sniper ledge (no saving throw) */
2794 legal_move= false;
2795 break;
2796 }
2797
2798 if (legal_move)
2799 {
2800 if ((monster->path_segment_length-= distance)<=0)
2801 {
2802 advance_monster_path(monster_index);
2803 }
2804 else
2805 {
2806 short old_polygon_index= object->polygon;
2807
2808 /* update the monster�s object to reflect his new position */
2809 if (translate_map_object(monster->object_index, &new_location, NONE)) monster_moved(monster_index, old_polygon_index);
2810 }
2811
2812 legal_move= true;
2813 }
2814 }
2815 else
2816 {
2817 struct object_data *obstacle_object= get_object_data(obstacle_index);
2818
2819 if (GET_OBJECT_OWNER(obstacle_object)==_object_is_monster)
2820 {
2821 struct monster_data *obstacle_monster= get_monster_data(obstacle_object->permutation);
2822
2823 /* we collided with another monster: see if we want to attack him; if not, see if we
2824 can attack his current target (if he is locked or losing_lock); if not, drop lock
2825 and ask for a new path. */
2826
2827 if (!mTYPE_IS_ENEMY(definition, obstacle_monster->type) && !(MONSTER_HAS_VALID_TARGET(monster)&&monster->target_index==obstacle_object->permutation) &&
2828 !MONSTER_IS_BERSERK(monster))
2829 {
2830 if (!MONSTER_IS_PLAYER(obstacle_monster))
2831 {
2832 if (monster->mode!=_monster_locked)
2833 {
2834 if (!MONSTER_HAS_VALID_TARGET(obstacle_monster) || !switch_target_check(monster_index, obstacle_monster->target_index, 0))
2835 {
2836 if (monster->mode==_monster_unlocked && !(global_random()&OBSTRUCTION_DEACTIVATION_MASK) &&
2837 (monster->goal_polygon_index==NONE || monster->goal_polygon_index==object->polygon))
2838 {
2839 deactivate_monster(monster_index);
2840 }
2841 else
2842 {
2843 monster_needs_path(monster_index, false);
2844 if (monster->mode!=_monster_locked)
2845 {
2846 /* if we�re not locked, we might want to think about deactivating here, but
2847 for now we just build a new random path by forcing our state to _unlocked. */
2848 set_monster_mode(monster_index, _monster_unlocked, NONE);
2849 // dprintf("monster #%d going unlocked by obstruction;g;", monster_index);
2850 }
2851 }
2852 }
2853 }
2854 else
2855 {
2856 attempt_evasive_manouvers(monster_index);
2857 }
2858 }
2859
2860 SET_MONSTER_IDLE_STATUS(monster, true);
2861 }
2862 else
2863 {
2864 struct monster_definition *obstacle_definition= get_monster_definition(obstacle_monster->type);
2865 world_distance key_height= obstacle_object->location.z+(obstacle_definition->height>>1);
2866
2867 change_monster_target(monster_index, obstacle_object->permutation);
2868
2869 /* if we�re a kamakazi and we�re within range, pop */
2870 if ((definition->flags&_monster_is_kamakazi) &&
2871 object->location.z<key_height)
2872 {
2873 bool in_range = object->location.z+definition->height>key_height;
2874
2875 /* if we're short and can't float, take out their knees! */
2876 if (!in_range && film_profile.allow_short_kamikaze && !(definition->flags&_monster_floats))
2877 in_range = object->location.z>=obstacle_object->location.z;
2878
2879 if (in_range)
2880 {
2881 set_monster_action(monster_index, _monster_is_dying_hard);
2882 monster_died(monster_index);
2883 }
2884 }
2885
2886 /* if we float and this is our target, go up */
2887 if (definition->flags&_monster_floats)
2888 {
2889 monster->desired_height= obstacle_object->location.z;
2890 }
2891 }
2892 }
2893 else
2894 {
2895 attempt_evasive_manouvers(monster_index); // to avoid the scenery
2896 }
2897 }
2898
2899 return legal_move;
2900 }
2901
attempt_evasive_manouvers(short monster_index)2902 static bool attempt_evasive_manouvers(
2903 short monster_index)
2904 {
2905 struct monster_data *monster= get_monster_data(monster_index);
2906 struct object_data *object= get_object_data(monster->object_index);
2907 world_point2d destination= { object->location.x, object->location.y };
2908 angle new_facing= NORMALIZE_ANGLE(object->facing + ((global_random()&1) ? QUARTER_CIRCLE : -QUARTER_CIRCLE));
2909 world_distance original_floor_height= get_polygon_data(object->polygon)->floor_height;
2910 short polygon_index= object->polygon;
2911 bool successful= true;
2912
2913 translate_point2d(&destination, EVASIVE_MANOUVER_DISTANCE, new_facing);
2914 do
2915 {
2916 short line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)&object->location, &destination);
2917
2918 if (line_index==NONE)
2919 {
2920 polygon_index= NONE;
2921 }
2922 else
2923 {
2924 /* if we ran off the map, we failed */
2925 if (LINE_IS_SOLID(get_line_data(line_index)) || (polygon_index= find_adjacent_polygon(polygon_index, line_index))==NONE)
2926 {
2927 polygon_index= NONE;
2928 successful= false;
2929 }
2930 else
2931 {
2932 struct polygon_data *polygon= get_polygon_data(polygon_index);
2933 if (polygon->floor_height!=original_floor_height || polygon->type==_polygon_is_monster_impassable)
2934 {
2935 polygon_index= NONE;
2936 successful= false;
2937 }
2938 }
2939 }
2940 }
2941 while (polygon_index!=NONE);
2942
2943 if (successful)
2944 {
2945 object->facing= new_facing;
2946 if (monster->path!=NONE) delete_path(monster->path), monster->path= NONE;
2947 monster->path_segment_length= EVASIVE_MANOUVER_DISTANCE;
2948 }
2949
2950 return successful;
2951 }
2952
advance_monster_path(short monster_index)2953 void advance_monster_path(
2954 short monster_index)
2955 {
2956 struct monster_data *monster= get_monster_data(monster_index);
2957 struct object_data *object= get_object_data(monster->object_index);
2958 world_point2d path_goal;
2959 bool done= true;
2960
2961 if (monster->path==NONE)
2962 {
2963 /* only locked monsters in their target�s polygon can advance without paths */
2964 if (monster->mode!=_monster_locked || object->polygon!=get_object_data(get_monster_data(monster->target_index)->object_index)->polygon)
2965 {
2966 monster_needs_path(monster_index, true);
2967 return;
2968 }
2969 }
2970 else
2971 {
2972 done= move_along_path(monster->path, &path_goal);
2973 if (done)
2974 monster->path= NONE;
2975 }
2976
2977 /* if we�re locked without a path, head right for the bastard (he�s in our polygon) */
2978 if ((done||monster->path==NONE) && monster->mode==_monster_locked)
2979 {
2980 struct monster_data *target= get_monster_data(monster->target_index);
2981 struct object_data *target_object= get_object_data(target->object_index);
2982
2983 if (object->polygon==target_object->polygon)
2984 {
2985 world_point3d location= get_object_data(get_monster_data(monster->target_index)->object_index)->location;
2986 path_goal.x= location.x;
2987 path_goal.y= location.y;
2988 done= false;
2989 }
2990 }
2991
2992 if (done)
2993 {
2994 /* ask for a new path (never happens to locked monsters) */
2995 monster_needs_path(monster_index, false);
2996 if (monster->mode==_monster_unlocked)
2997 {
2998 monster->goal_polygon_index= NONE;
2999 if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster)) deactivate_monster(monster_index);
3000 }
3001 }
3002 else
3003 {
3004 /* point ourselves at this new point in the path */
3005 object->facing= arctangent(path_goal.x-object->location.x, path_goal.y-object->location.y);
3006 monster->path_segment_length= distance2d(&path_goal, (world_point2d *)&object->location);
3007 }
3008 }
3009
try_monster_attack(short monster_index)3010 static bool try_monster_attack(
3011 short monster_index)
3012 {
3013 struct monster_data *monster= get_monster_data(monster_index);
3014 struct object_data *object= get_object_data(monster->object_index);
3015 struct monster_definition *definition= get_monster_definition(monster->type);
3016 short repetitions= NONE;
3017 short new_action= NONE, obstruction_index= NONE;
3018 angle theta = 0;
3019
3020 if (MONSTER_HAS_VALID_TARGET(monster))
3021 {
3022 struct object_data *target_object= get_object_data(get_monster_data(monster->target_index)->object_index);
3023 world_point3d origin= object->location, destination= target_object->location;
3024 world_distance range= distance2d((world_point2d *)&origin, (world_point2d *)&destination);
3025 short polygon_index;
3026 world_point3d _vector;
3027
3028 theta= arctangent(destination.x-origin.x, destination.y-origin.y);
3029 angle delta_theta= NORMALIZE_ANGLE(theta-object->facing);
3030
3031 if (!(definition->flags&_monster_cant_fire_backwards) || (delta_theta<QUARTER_CIRCLE+QUARTER_CIRCLE/2 || delta_theta>FULL_CIRCLE-QUARTER_CIRCLE-QUARTER_CIRCLE/2))
3032 {
3033 switch (monster->action)
3034 {
3035 case _monster_is_attacking_close:
3036 case _monster_is_attacking_far:
3037 new_action= monster->action;
3038 break;
3039
3040 default:
3041 if (definition->ranged_attack.type!=NONE && range<definition->ranged_attack.range) new_action= _monster_is_attacking_far;
3042 if (definition->melee_attack.type!=NONE && range<definition->melee_attack.range)
3043 {
3044 new_action= _monster_is_attacking_close;
3045
3046 if (definition->flags&_monster_chooses_weapons_randomly)
3047 {
3048 bool switch_to_ranged = true;
3049 if (film_profile.validate_random_ranged_attack)
3050 {
3051 if (definition->ranged_attack.type == NONE)
3052 {
3053 logWarning("Monster chooses weapons randomly, but has no ranged attack");
3054 definition->flags &= ~_monster_chooses_weapons_randomly;
3055 switch_to_ranged = false;
3056 }
3057 else
3058 switch_to_ranged = (range<definition->ranged_attack.range);
3059 }
3060 if (switch_to_ranged && global_random()&1)
3061 new_action= _monster_is_attacking_far;
3062 }
3063 }
3064 break;
3065 }
3066
3067 /* if we have a melee attack and we're at short range, use it */
3068 if (new_action==_monster_is_attacking_close)
3069 {
3070 /* make sure this is a valid projectile, that we don�t hit any walls and that whatever
3071 we did hit is _hostile. */
3072 polygon_index= position_monster_projectile(monster_index, monster->target_index, &definition->melee_attack, &origin, &destination, &_vector, theta);
3073 if (preflight_projectile(&origin, polygon_index, &destination, definition->melee_attack.error,
3074 definition->melee_attack.type, monster_index, monster->type, &obstruction_index))
3075 {
3076 if ((obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_hostile) ||
3077 !line_is_obstructed(object->polygon, (world_point2d *) &object->location, target_object->polygon, (world_point2d *) &target_object->location))
3078 {
3079 repetitions= definition->melee_attack.repetitions;
3080 }
3081 }
3082 }
3083 else
3084 {
3085 /* make sure we have a ranged attack and our target is within range */
3086 if (new_action==_monster_is_attacking_far)
3087 {
3088 /* make sure this is a valid projectile, that we don�t hit any walls and that whatever
3089 we did hit is _hostile. */
3090 polygon_index= position_monster_projectile(monster_index, monster->target_index, &definition->ranged_attack, &origin, &destination, &_vector, theta);
3091 if (preflight_projectile(&origin, polygon_index, &destination, definition->ranged_attack.error,
3092 definition->ranged_attack.type, monster_index, monster->type, &obstruction_index))
3093 {
3094 if ((obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_hostile) ||
3095 (obstruction_index==NONE && !line_is_obstructed(object->polygon, (world_point2d *) &object->location, target_object->polygon, (world_point2d *) &target_object->location)))
3096 {
3097 repetitions= definition->ranged_attack.repetitions;
3098 }
3099 }
3100 }
3101 }
3102 }
3103 }
3104
3105 if (repetitions!=NONE)
3106 {
3107 /* we can attack; set monster facing, start the attack action and reset ticks_since_attack */
3108 object->facing= theta;
3109 if (monster->action!=new_action) /* if we�re already attacking, this is a chained attack */
3110 {
3111 switch (dynamic_world->game_information.difficulty_level)
3112 {
3113 case _wuss_level: case _easy_level: repetitions>>= 1;
3114 case _normal_level: repetitions= (repetitions<=1) ? repetitions : repetitions-1; break;
3115 }
3116
3117 set_monster_action(monster_index, new_action);
3118 monster->attack_repetitions= repetitions;
3119 }
3120
3121 /* on the highest level, hitting a monster in the middle of an attack doesn�t really
3122 stop him from continuing to attack because ticks_since_attack is never reset */
3123 switch (dynamic_world->game_information.difficulty_level)
3124 {
3125 case _total_carnage_level:
3126 break;
3127
3128 default:
3129 monster->ticks_since_attack= 0;
3130 break;
3131 }
3132 }
3133 else
3134 {
3135 /* we can�t attack (for whatever reason), halve ticks_since_attack so we try again soon */
3136 monster->ticks_since_attack= 0;
3137
3138 if (obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_friendly &&
3139 MONSTER_IS_PLAYER(get_monster_data(obstruction_index)))
3140 {
3141 play_object_sound(monster->object_index, definition->clear_sound);
3142 }
3143 }
3144
3145 return new_action==NONE ? false : true;
3146 }
3147
execute_monster_attack(short monster_index)3148 static void execute_monster_attack(
3149 short monster_index)
3150 {
3151 struct monster_data *monster= get_monster_data(monster_index);
3152
3153 /* we used to assert that the attacking monster was locked, but monsters can be deactivated
3154 or lose lock during an attack (!) so we just abort if we no longer have a valid target */
3155 if (MONSTER_HAS_VALID_TARGET(monster))
3156 {
3157 struct monster_definition *definition= get_monster_definition(monster->type);
3158 struct object_data *object= get_object_data(monster->object_index);
3159 struct attack_definition *attack= (monster->action==_monster_is_attacking_close) ? &definition->melee_attack : &definition->ranged_attack;
3160 short projectile_polygon_index;
3161 world_point3d origin= object->location;
3162 world_point3d _vector;
3163
3164 projectile_polygon_index= position_monster_projectile(monster_index, monster->target_index, attack, &origin, (world_point3d *) NULL, &_vector, object->facing);
3165 if (projectile_polygon_index != NONE)
3166 new_projectile(&origin, projectile_polygon_index, &_vector, attack->error, attack->type, monster_index, monster->type, monster->target_index, FIXED_ONE);
3167 if (definition->flags&_monster_fires_symmetrically)
3168 {
3169 attack->dy= -attack->dy;
3170 projectile_polygon_index= position_monster_projectile(monster_index, monster->target_index, attack, &origin, (world_point3d *) NULL, &_vector, object->facing);
3171 if (projectile_polygon_index != NONE)
3172 new_projectile(&origin, projectile_polygon_index, &_vector, attack->error, attack->type, monster_index, monster->type, monster->target_index, FIXED_ONE);
3173 attack->dy= -attack->dy;
3174 }
3175 }
3176 }
3177
monster_pathfinding_cost_function(short source_polygon_index,short line_index,short destination_polygon_index,void * vdata)3178 int32 monster_pathfinding_cost_function(
3179 short source_polygon_index,
3180 short line_index,
3181 short destination_polygon_index,
3182 void *vdata)
3183 {
3184 struct monster_pathfinding_data *data=(struct monster_pathfinding_data *)vdata;
3185 struct monster_definition *definition= data->definition;
3186 struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index);
3187 struct polygon_data *source_polygon= get_polygon_data(source_polygon_index);
3188 struct line_data *line= get_line_data(line_index);
3189 bool respect_polygon_heights= true;
3190 struct object_data *object;
3191 short object_index;
3192 int32 cost;
3193
3194 /* base cost is the area of the polygon we�re leaving */
3195 cost= source_polygon->area;
3196
3197 /* no solid lines (baby) */
3198 if (LINE_IS_SOLID(line) && !LINE_IS_VARIABLE_ELEVATION(line)) cost= -1;
3199
3200 /* count up the monsters in destination_polygon and add a constant cost, MONSTER_PATHFINDING_OBSTRUCTION_PENALTY,
3201 for each of them to discourage overcrowding */
3202 for (object_index= destination_polygon->first_object; object_index!=NONE; object_index= object->next_object)
3203 {
3204 object= get_object_data(object_index);
3205 if (GET_OBJECT_OWNER(object)==_object_is_monster) cost+= MONSTER_PATHFINDING_OBSTRUCTION_COST;
3206 }
3207
3208 /* if we�re trying to move into a polygon with an area smaller than MINIMUM_MONSTER_PATHFINDING_POLYGON_AREA, disallow the move */
3209 if (source_polygon->area<MINIMUM_MONSTER_PATHFINDING_POLYGON_AREA) cost= -1;
3210
3211 // do platform stuff
3212 if (cost>0)
3213 {
3214 if (destination_polygon->type==_polygon_is_platform)
3215 {
3216 switch (monster_can_enter_platform(destination_polygon->permutation, source_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta))
3217 {
3218 case _platform_will_never_be_accessable: cost= -1; break;
3219 default: cost+= MONSTER_PATHFINDING_PLATFORM_COST; respect_polygon_heights= false; break;
3220 }
3221
3222 // don't move into flooded platforms
3223 if ((static_world->environment_flags&_environment_ouch_m1) &&
3224 !(definition->flags&(_monster_flys|_monster_floats)) &&
3225 PLATFORM_IS_FLOODED(get_platform_data(destination_polygon->permutation)) &&
3226 (find_flooding_polygon(destination_polygon_index) != NONE))
3227 cost= -1;
3228 }
3229 if (source_polygon->type==_polygon_is_platform)
3230 {
3231 switch (monster_can_leave_platform(source_polygon->permutation, destination_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta))
3232 {
3233 case _exit_will_never_be_accessable: cost= -1; break;
3234 default: respect_polygon_heights= false; break;
3235 }
3236 }
3237 }
3238
3239 /* if the ledge between polygons is too high, the fall is too far, or there just
3240 isn�t enough vertical space, disallow the move (and ignore this if we�re dealing with
3241 platforms or doors) */
3242 if (respect_polygon_heights)
3243 {
3244 world_distance delta_height= destination_polygon->floor_height-source_polygon->floor_height;
3245
3246 if (delta_height<definition->minimum_ledge_delta||delta_height>definition->maximum_ledge_delta) cost= -1;
3247 if (line->lowest_adjacent_ceiling-line->highest_adjacent_floor<definition->height) cost= -1;
3248
3249 if (cost>0) cost+= delta_height*delta_height; /* prefer not to change heights */
3250 }
3251
3252 /* if this line not wide enough, disallow the move */
3253 if (line->length<2*definition->radius) cost= -1;
3254
3255 if (cost>0)
3256 {
3257 /* if we�re trying to move into an impassable polygon, disallow the move */
3258 switch (destination_polygon->type)
3259 {
3260 case _polygon_is_zone_border:
3261 if (!data->cross_zone_boundaries) cost= -1;
3262 break;
3263
3264 case _polygon_is_monster_impassable:
3265 case _polygon_is_teleporter:
3266 cost= -1;
3267 break;
3268
3269 case _polygon_is_minor_ouch:
3270 case _polygon_is_major_ouch:
3271 if ((static_world->environment_flags&_environment_ouch_m1) &&
3272 !(definition->flags&(_monster_flys|_monster_floats)))
3273 cost= -1;
3274 break;
3275 }
3276 }
3277
3278 if (cost>0)
3279 {
3280 /* if we�re trying to move into media, pay the penalty */
3281 if (destination_polygon->media_index!=NONE)
3282 {
3283 struct media_data *media= get_media_data(destination_polygon->media_index);
3284
3285 // LP change: idiot-proofed this
3286 if (media)
3287 {
3288 if (media->height>destination_polygon->floor_height)
3289 {
3290 cost+= 2*destination_polygon->area;
3291 }
3292 }
3293 }
3294 }
3295
3296 return cost;
3297 }
3298
3299 /* returns the type and index of any interesting terrain feature (platform or door) in front
3300 of the given monster in his current direction; this lets us open doors and wait for
3301 platforms. relevant_polygon_index is the polygon_index we have to pass to platform_is_accessable */
find_obstructing_terrain_feature(short monster_index,short * feature_index,short * relevant_polygon_index)3302 static short find_obstructing_terrain_feature(
3303 short monster_index,
3304 short *feature_index,
3305 short *relevant_polygon_index)
3306 {
3307 struct monster_data *monster= get_monster_data(monster_index);
3308 struct monster_definition *definition= get_monster_definition(monster->type);
3309 struct object_data *object= get_object_data(monster->object_index);
3310 short polygon_index, feature_type;
3311 world_point2d p1;
3312
3313 ray_to_line_segment((world_point2d *)&object->location, &p1, object->facing, MONSTER_PLATFORM_BUFFER_DISTANCE+definition->radius);
3314
3315 feature_type= NONE;
3316 *feature_index= NONE;
3317 *relevant_polygon_index= polygon_index= object->polygon;
3318 do
3319 {
3320 struct polygon_data *polygon= get_polygon_data(polygon_index);
3321 short line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)&object->location, &p1);
3322
3323 switch (polygon->type)
3324 {
3325 case _polygon_is_platform:
3326 if (object->polygon==polygon_index)
3327 {
3328 /* we�re standing on the platform: find out where we�re headed (if we�re
3329 going nowhere then pretend like everything is o.k.) */
3330
3331 polygon_index= line_index==NONE ? NONE : find_adjacent_polygon(polygon_index, line_index);
3332 if (polygon_index!=NONE)
3333 {
3334 *relevant_polygon_index= polygon_index;
3335 *feature_index= polygon->permutation;
3336 feature_type= _leaving_platform_polygon;
3337 assert(*feature_index!=NONE);
3338 }
3339 }
3340 else
3341 {
3342 feature_type= _entering_platform_polygon;
3343 *feature_index= polygon->permutation;
3344 assert(*feature_index!=NONE);
3345 }
3346 break;
3347
3348 default:
3349 if (((definition->flags&_monster_floats) && polygon->floor_height>monster->desired_height) ||
3350 object->location.z+definition->height>polygon->ceiling_height)
3351 {
3352 monster->desired_height= polygon->floor_height;
3353 feature_type= _flying_or_floating_transition;
3354 *feature_index= 0;
3355 }
3356 if (definition->flags&_monster_flys)
3357 {
3358 if ((polygon->floor_height>monster->desired_height) ||
3359 (polygon->ceiling_height<monster->desired_height+definition->height))
3360 {
3361 monster->desired_height= (polygon->floor_height>monster->desired_height) ?
3362 polygon->floor_height : (polygon->ceiling_height - definition->height);
3363 feature_type= _flying_or_floating_transition;
3364 *feature_index= 0;
3365 }
3366
3367 if (object->location.z<polygon->floor_height || object->location.z+definition->height>polygon->ceiling_height)
3368 {
3369 feature_type= _flying_or_floating_transition;
3370 *feature_index= 0;
3371 }
3372 }
3373 if (definition->flags&_monster_uses_sniper_ledges)
3374 {
3375 if ((polygon->floor_height+MINIMUM_SNIPER_ELEVATION<monster->desired_height) &&
3376 monster->mode==_monster_locked)
3377 {
3378 feature_type= _standing_on_sniper_ledge;
3379 }
3380 }
3381 if (!(definition->flags&(_monster_floats|_monster_flys)) && polygon->media_index!=NONE)
3382 {
3383 struct media_data *media= get_media_data(polygon->media_index);
3384 // Monster will normally wade to half-height
3385 world_distance height= definition->height>>1;
3386 // LP change: idiot-proofing
3387 if (media)
3388 {
3389 // In dangerous media, wade only to zero height (that is, don't wade at all)
3390 if (IsMediaDangerous(media->type)) height= 0;
3391
3392 switch (media->type)
3393 {
3394 case _media_water: if (definition->flags&_monster_is_not_afraid_of_water) media= (struct media_data *) NULL; break;
3395 case _media_jjaro: // LP addition: monsters treat Jjaro goo like sewage
3396 case _media_sewage: if (definition->flags&_monster_is_not_afraid_of_sewage) media= (struct media_data *) NULL; break;
3397 case _media_lava: /* height= 0; */ if (definition->flags&_monster_is_not_afraid_of_lava) media= (struct media_data *) NULL; break;
3398 case _media_goo: /* height= 0; */ if (definition->flags&_monster_is_not_afraid_of_goo) media= (struct media_data *) NULL; break;
3399 }
3400 }
3401 if (media && media->height-polygon->floor_height>height)
3402 {
3403 if (get_polygon_data(object->polygon)->floor_height>polygon->floor_height)
3404 {
3405 feature_type= _standing_on_sniper_ledge;
3406 if (monster->mode!=_monster_locked) monster_needs_path(monster_index, false);
3407 }
3408 }
3409 }
3410 polygon_index= line_index==NONE ? NONE : find_adjacent_polygon(polygon_index, line_index);
3411 break;
3412 }
3413
3414 if (line_index!=NONE && polygon_index==NONE)
3415 {
3416 if (monster->path_segment_length<MONSTER_PLATFORM_BUFFER_DISTANCE)
3417 {
3418 monster->path_segment_length= 0;
3419 }
3420 else
3421 {
3422 /* we�re headed for a wall solid; freeze and get a new path, pronto */
3423 feature_type= _standing_on_sniper_ledge;
3424 monster_needs_path(monster_index, true);
3425 }
3426 }
3427 }
3428 while (polygon_index!=NONE&&(feature_type==NONE||feature_type==_flying_or_floating_transition));
3429
3430 return feature_type;
3431 }
3432
3433 /* returns new polygon index; if destination is NULL then we fire along the monster�s facing
3434 and elevation, if destination is not NULL then we set it correctly and save the elevation angle */
position_monster_projectile(short aggressor_index,short target_index,struct attack_definition * attack,world_point3d * origin,world_point3d * destination,world_point3d * _vector,angle theta)3435 static short position_monster_projectile(
3436 short aggressor_index,
3437 short target_index,
3438 struct attack_definition *attack,
3439 world_point3d *origin,
3440 world_point3d *destination,
3441 world_point3d *_vector,
3442 angle theta)
3443 {
3444 struct monster_data *aggressor= get_monster_data(aggressor_index);
3445 struct monster_data *target= get_monster_data(target_index);
3446 struct object_data *aggressor_object= get_object_data(aggressor->object_index);
3447 struct object_data *target_object= get_object_data(target->object_index);
3448 world_distance radius, height;
3449
3450 // dprintf("positioning #%d to #%d", aggressor_index, target_index);
3451
3452 /* adjust origin */
3453 *origin= aggressor_object->location;
3454 origin->z+= attack->dz;
3455 translate_point2d((world_point2d *)origin, attack->dy, NORMALIZE_ANGLE(theta+QUARTER_CIRCLE));
3456 translate_point2d((world_point2d *)origin, attack->dx, theta);
3457
3458 if (destination)
3459 {
3460 world_distance distance;
3461
3462 /* adjust destination */
3463 get_monster_dimensions(target_index, &radius, &height);
3464 *destination= target_object->location;
3465 destination->z+= (height>>1) + (height>>2); /* shoot 3/4ths up the target */
3466
3467 /* calculate outbound vector */
3468 _vector->x= destination->x-origin->x;
3469 _vector->y= destination->y-origin->y;
3470 _vector->z= destination->z-origin->z;
3471
3472 distance= isqrt(_vector->x*_vector->x + _vector->y*_vector->y);
3473 aggressor->elevation= distance ? (_vector->z*TRIG_MAGNITUDE)/distance : 0;
3474 }
3475 else
3476 {
3477 _vector->x= cosine_table[theta];
3478 _vector->y= sine_table[theta];
3479 _vector->z= aggressor->elevation;
3480 }
3481
3482 /* return polygon_index of the new origin point */
3483 return find_new_object_polygon((world_point2d *)&aggressor_object->location,
3484 (world_point2d *)origin, aggressor_object->polygon);
3485 }
3486
nearest_goal_polygon_index(short polygon_index)3487 short nearest_goal_polygon_index(
3488 short polygon_index)
3489 {
3490 polygon_index= flood_map(polygon_index, INT32_MAX, nearest_goal_cost_function, _breadth_first, (void *) NULL);
3491 while (polygon_index!=NONE)
3492 {
3493 struct polygon_data *polygon= get_polygon_data(polygon_index);
3494
3495 if (polygon->type==_polygon_is_goal) break;
3496
3497 polygon_index= flood_map(NONE, INT32_MAX, nearest_goal_cost_function, _breadth_first, (void *) NULL);
3498 }
3499
3500 return polygon_index;
3501 }
3502
nearest_goal_cost_function(short source_polygon_index,short line_index,short destination_polygon_index,void * unused)3503 static int32 nearest_goal_cost_function(
3504 short source_polygon_index,
3505 short line_index,
3506 short destination_polygon_index,
3507 void *unused)
3508 {
3509 struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index);
3510 // struct polygon_data *source_polygon= get_polygon_data(source_polygon_index);
3511 // struct line_data *line= get_line_data(line_index);
3512 int32 cost= 1;
3513
3514 (void) (unused);
3515 (void) (source_polygon_index);
3516 (void) (line_index);
3517
3518 if (destination_polygon->type==_polygon_is_zone_border) cost= -1;
3519
3520 return cost;
3521 }
3522
3523
3524 // LP: will set player view attributes when trying to shoot a guided projectile.
SetPlayerViewAttribs(int16 half_visual_arc,int16 half_vertical_visual_arc,world_distance visual_range,world_distance dark_visual_range)3525 void SetPlayerViewAttribs(int16 half_visual_arc, int16 half_vertical_visual_arc,
3526 world_distance visual_range, world_distance dark_visual_range)
3527 {
3528 // Added a modified version of AlexJS's changes: change only if necessary
3529 // Restoring AlexJLS's changes
3530 monster_definition& PlayerAsMonster = monster_definitions[_monster_marine];
3531 if (half_visual_arc > 0 || PlayerAsMonster.half_visual_arc > 0)
3532 PlayerAsMonster.half_visual_arc = half_visual_arc;
3533 if (half_vertical_visual_arc > 0 || PlayerAsMonster.half_vertical_visual_arc > 0)
3534 PlayerAsMonster.half_vertical_visual_arc = half_vertical_visual_arc;
3535 if (visual_range > 0 || PlayerAsMonster.visual_range > 0)
3536 PlayerAsMonster.visual_range = visual_range;
3537 if (dark_visual_range > 0 || PlayerAsMonster.dark_visual_range > 0)
3538 PlayerAsMonster.dark_visual_range = dark_visual_range;
3539 }
3540
3541
unpack_monster_data(uint8 * Stream,monster_data * Objects,size_t Count)3542 uint8 *unpack_monster_data(uint8 *Stream, monster_data *Objects, size_t Count)
3543 {
3544 uint8* S = Stream;
3545 monster_data* ObjPtr = Objects;
3546
3547 for (size_t k = 0; k < Count; k++, ObjPtr++)
3548 {
3549 StreamToValue(S,ObjPtr->type);
3550 StreamToValue(S,ObjPtr->vitality);
3551 StreamToValue(S,ObjPtr->flags);
3552
3553 StreamToValue(S,ObjPtr->path);
3554 StreamToValue(S,ObjPtr->path_segment_length);
3555 StreamToValue(S,ObjPtr->desired_height);
3556
3557 StreamToValue(S,ObjPtr->mode);
3558 StreamToValue(S,ObjPtr->action);
3559 StreamToValue(S,ObjPtr->target_index);
3560 StreamToValue(S,ObjPtr->external_velocity);
3561 StreamToValue(S,ObjPtr->vertical_velocity);
3562 StreamToValue(S,ObjPtr->ticks_since_attack);
3563 StreamToValue(S,ObjPtr->attack_repetitions);
3564 StreamToValue(S,ObjPtr->changes_until_lock_lost);
3565
3566 StreamToValue(S,ObjPtr->elevation);
3567
3568 StreamToValue(S,ObjPtr->object_index);
3569
3570 StreamToValue(S,ObjPtr->ticks_since_last_activation);
3571
3572 StreamToValue(S,ObjPtr->activation_bias);
3573
3574 StreamToValue(S,ObjPtr->goal_polygon_index);
3575
3576 StreamToValue(S,ObjPtr->sound_location.x);
3577 StreamToValue(S,ObjPtr->sound_location.y);
3578 StreamToValue(S,ObjPtr->sound_location.z);
3579 StreamToValue(S,ObjPtr->sound_polygon_index);
3580
3581 StreamToValue(S,ObjPtr->random_desired_height);
3582
3583 S += 7*2;
3584 }
3585
3586 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_monster_data));
3587 return S;
3588 }
3589
pack_monster_data(uint8 * Stream,monster_data * Objects,size_t Count)3590 uint8 *pack_monster_data(uint8 *Stream, monster_data *Objects, size_t Count)
3591 {
3592 uint8* S = Stream;
3593 monster_data* ObjPtr = Objects;
3594
3595 for (size_t k = 0; k < Count; k++, ObjPtr++)
3596 {
3597 ValueToStream(S,ObjPtr->type);
3598 ValueToStream(S,ObjPtr->vitality);
3599 ValueToStream(S,ObjPtr->flags);
3600
3601 ValueToStream(S,ObjPtr->path);
3602 ValueToStream(S,ObjPtr->path_segment_length);
3603 ValueToStream(S,ObjPtr->desired_height);
3604
3605 ValueToStream(S,ObjPtr->mode);
3606 ValueToStream(S,ObjPtr->action);
3607 ValueToStream(S,ObjPtr->target_index);
3608 ValueToStream(S,ObjPtr->external_velocity);
3609 ValueToStream(S,ObjPtr->vertical_velocity);
3610 ValueToStream(S,ObjPtr->ticks_since_attack);
3611 ValueToStream(S,ObjPtr->attack_repetitions);
3612 ValueToStream(S,ObjPtr->changes_until_lock_lost);
3613
3614 ValueToStream(S,ObjPtr->elevation);
3615
3616 ValueToStream(S,ObjPtr->object_index);
3617
3618 ValueToStream(S,ObjPtr->ticks_since_last_activation);
3619
3620 ValueToStream(S,ObjPtr->activation_bias);
3621
3622 ValueToStream(S,ObjPtr->goal_polygon_index);
3623
3624 ValueToStream(S,ObjPtr->sound_location.x);
3625 ValueToStream(S,ObjPtr->sound_location.y);
3626 ValueToStream(S,ObjPtr->sound_location.z);
3627 ValueToStream(S,ObjPtr->sound_polygon_index);
3628
3629 ValueToStream(S,ObjPtr->random_desired_height);
3630
3631 S += 7*2;
3632 }
3633
3634 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_monster_data));
3635 return S;
3636 }
3637
3638
StreamToAttackDef(uint8 * & S,attack_definition & Object)3639 inline void StreamToAttackDef(uint8* &S, attack_definition& Object)
3640 {
3641 StreamToValue(S,Object.type);
3642 StreamToValue(S,Object.repetitions);
3643 StreamToValue(S,Object.error);
3644 StreamToValue(S,Object.range);
3645 StreamToValue(S,Object.attack_shape);
3646
3647 StreamToValue(S,Object.dx);
3648 StreamToValue(S,Object.dy);
3649 StreamToValue(S,Object.dz);
3650 }
3651
AttackDefToStream(uint8 * & S,attack_definition & Object)3652 inline void AttackDefToStream(uint8* &S, attack_definition& Object)
3653 {
3654 ValueToStream(S,Object.type);
3655 ValueToStream(S,Object.repetitions);
3656 ValueToStream(S,Object.error);
3657 ValueToStream(S,Object.range);
3658 ValueToStream(S,Object.attack_shape);
3659
3660 ValueToStream(S,Object.dx);
3661 ValueToStream(S,Object.dy);
3662 ValueToStream(S,Object.dz);
3663 }
3664
3665
unpack_monster_definition(uint8 * Stream,size_t Count)3666 uint8 *unpack_monster_definition(uint8 *Stream, size_t Count)
3667 {
3668 return unpack_monster_definition(Stream,monster_definitions,Count);
3669 }
3670
unpack_monster_definition(uint8 * Stream,monster_definition * Objects,size_t Count)3671 uint8 *unpack_monster_definition(uint8 *Stream, monster_definition* Objects, size_t Count)
3672 {
3673 uint8* S = Stream;
3674 monster_definition* ObjPtr = Objects;
3675
3676 for (size_t k = 0; k < Count; k++, ObjPtr++)
3677 {
3678 StreamToValue(S,ObjPtr->collection);
3679
3680 StreamToValue(S,ObjPtr->vitality);
3681 StreamToValue(S,ObjPtr->immunities);
3682 StreamToValue(S,ObjPtr->weaknesses);
3683 StreamToValue(S,ObjPtr->flags);
3684
3685 StreamToValue(S,ObjPtr->_class);
3686 StreamToValue(S,ObjPtr->friends);
3687 StreamToValue(S,ObjPtr->enemies);
3688
3689 StreamToValue(S,ObjPtr->sound_pitch);
3690 StreamToValue(S,ObjPtr->activation_sound);
3691 StreamToValue(S,ObjPtr->friendly_activation_sound);
3692 StreamToValue(S,ObjPtr->clear_sound);
3693 StreamToValue(S,ObjPtr->kill_sound);
3694 StreamToValue(S,ObjPtr->apology_sound);
3695 StreamToValue(S,ObjPtr->friendly_fire_sound);
3696 StreamToValue(S,ObjPtr->flaming_sound);
3697 StreamToValue(S,ObjPtr->random_sound);
3698 StreamToValue(S,ObjPtr->random_sound_mask);
3699
3700 StreamToValue(S,ObjPtr->carrying_item_type);
3701
3702 StreamToValue(S,ObjPtr->radius);
3703 StreamToValue(S,ObjPtr->height);
3704 StreamToValue(S,ObjPtr->preferred_hover_height);
3705 StreamToValue(S,ObjPtr->minimum_ledge_delta);
3706 StreamToValue(S,ObjPtr->maximum_ledge_delta);
3707 StreamToValue(S,ObjPtr->external_velocity_scale);
3708 StreamToValue(S,ObjPtr->impact_effect);
3709 StreamToValue(S,ObjPtr->melee_impact_effect);
3710 StreamToValue(S,ObjPtr->contrail_effect);
3711
3712 StreamToValue(S,ObjPtr->half_visual_arc);
3713 StreamToValue(S,ObjPtr->half_vertical_visual_arc);
3714 StreamToValue(S,ObjPtr->visual_range);
3715 StreamToValue(S,ObjPtr->dark_visual_range);
3716 StreamToValue(S,ObjPtr->intelligence);
3717 StreamToValue(S,ObjPtr->speed);
3718 StreamToValue(S,ObjPtr->gravity);
3719 StreamToValue(S,ObjPtr->terminal_velocity);
3720 StreamToValue(S,ObjPtr->door_retry_mask);
3721 StreamToValue(S,ObjPtr->shrapnel_radius);
3722 S = unpack_damage_definition(S,&ObjPtr->shrapnel_damage,1);
3723
3724 StreamToValue(S,ObjPtr->hit_shapes);
3725 StreamToValue(S,ObjPtr->hard_dying_shape);
3726 StreamToValue(S,ObjPtr->soft_dying_shape);
3727 StreamToValue(S,ObjPtr->hard_dead_shapes);
3728 StreamToValue(S,ObjPtr->soft_dead_shapes);
3729 StreamToValue(S,ObjPtr->stationary_shape);
3730 StreamToValue(S,ObjPtr->moving_shape);
3731 StreamToValue(S,ObjPtr->teleport_in_shape);
3732 StreamToValue(S,ObjPtr->teleport_out_shape);
3733
3734 StreamToValue(S,ObjPtr->attack_frequency);
3735 StreamToAttackDef(S,ObjPtr->melee_attack);
3736 StreamToAttackDef(S,ObjPtr->ranged_attack);
3737 }
3738
3739 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_monster_definition));
3740 return S;
3741 }
3742
unpack_m1_monster_definition(uint8 * Stream,size_t Count)3743 uint8* unpack_m1_monster_definition(uint8 *Stream, size_t Count)
3744 {
3745 uint8* S = Stream;
3746 monster_definition* ObjPtr = monster_definitions;
3747
3748 for (size_t k = 0; k < Count; k++, ObjPtr++)
3749 {
3750 StreamToValue(S, ObjPtr->collection);
3751
3752 StreamToValue(S, ObjPtr->vitality);
3753 StreamToValue(S, ObjPtr->immunities);
3754 StreamToValue(S, ObjPtr->weaknesses);
3755 StreamToValue(S, ObjPtr->flags);
3756
3757 StreamToValue(S, ObjPtr->_class);
3758 StreamToValue(S, ObjPtr->friends);
3759 StreamToValue(S, ObjPtr->enemies);
3760
3761 ObjPtr->sound_pitch = FIXED_ONE;
3762 StreamToValue(S, ObjPtr->activation_sound);
3763 S += 2; // ignore conversation sound
3764
3765 // Marathon doesn't have these
3766 ObjPtr->friendly_activation_sound = NONE;
3767 ObjPtr->clear_sound = NONE;
3768 ObjPtr->kill_sound = NONE;
3769 ObjPtr->apology_sound = NONE;
3770 ObjPtr->friendly_fire_sound = NONE;
3771
3772 StreamToValue(S, ObjPtr->flaming_sound);
3773 StreamToValue(S, ObjPtr->random_sound);
3774 StreamToValue(S, ObjPtr->random_sound_mask);
3775
3776 StreamToValue(S, ObjPtr->carrying_item_type);
3777
3778 StreamToValue(S, ObjPtr->radius);
3779 StreamToValue(S, ObjPtr->height);
3780 StreamToValue(S, ObjPtr->preferred_hover_height);
3781 StreamToValue(S, ObjPtr->minimum_ledge_delta);
3782 StreamToValue(S, ObjPtr->maximum_ledge_delta);
3783 StreamToValue(S, ObjPtr->external_velocity_scale);
3784
3785 StreamToValue(S, ObjPtr->impact_effect);
3786 StreamToValue(S, ObjPtr->melee_impact_effect);
3787 ObjPtr->contrail_effect = NONE;
3788
3789 StreamToValue(S,ObjPtr->half_visual_arc);
3790 StreamToValue(S,ObjPtr->half_vertical_visual_arc);
3791 StreamToValue(S,ObjPtr->visual_range);
3792 StreamToValue(S,ObjPtr->dark_visual_range);
3793 StreamToValue(S,ObjPtr->intelligence);
3794 StreamToValue(S,ObjPtr->speed);
3795 StreamToValue(S,ObjPtr->gravity);
3796 StreamToValue(S,ObjPtr->terminal_velocity);
3797 StreamToValue(S,ObjPtr->door_retry_mask);
3798 StreamToValue(S,ObjPtr->shrapnel_radius);
3799
3800 S = unpack_damage_definition(S, &ObjPtr->shrapnel_damage, 1);
3801
3802 StreamToValue(S,ObjPtr->hit_shapes);
3803 StreamToValue(S,ObjPtr->hard_dying_shape);
3804 StreamToValue(S,ObjPtr->soft_dying_shape);
3805 StreamToValue(S,ObjPtr->hard_dead_shapes);
3806 StreamToValue(S,ObjPtr->soft_dead_shapes);
3807 StreamToValue(S,ObjPtr->stationary_shape);
3808 StreamToValue(S,ObjPtr->moving_shape);
3809
3810 ObjPtr->teleport_in_shape = ObjPtr->stationary_shape;
3811 ObjPtr->teleport_out_shape = ObjPtr->teleport_out_shape;
3812
3813 StreamToValue(S, ObjPtr->attack_frequency);
3814 StreamToAttackDef(S, ObjPtr->melee_attack);
3815 StreamToAttackDef(S, ObjPtr->ranged_attack);
3816
3817 ObjPtr->flags |= _monster_weaknesses_cause_soft_death;
3818 ObjPtr->flags |= _monster_screams_when_crushed;
3819 ObjPtr->flags |= _monster_makes_sound_when_activated;
3820 ObjPtr->flags |= _monster_can_grenade_climb;
3821 }
3822
3823 return S;
3824 }
3825
pack_monster_definition(uint8 * Stream,size_t Count)3826 uint8 *pack_monster_definition(uint8 *Stream, size_t Count)
3827 {
3828 return pack_monster_definition(Stream,monster_definitions,Count);
3829 }
3830
pack_monster_definition(uint8 * Stream,monster_definition * Objects,size_t Count)3831 uint8 *pack_monster_definition(uint8 *Stream, monster_definition *Objects, size_t Count)
3832 {
3833 uint8* S = Stream;
3834 monster_definition* ObjPtr = Objects;
3835
3836 for (size_t k = 0; k < Count; k++, ObjPtr++)
3837 {
3838 ValueToStream(S,ObjPtr->collection);
3839
3840 ValueToStream(S,ObjPtr->vitality);
3841 ValueToStream(S,ObjPtr->immunities);
3842 ValueToStream(S,ObjPtr->weaknesses);
3843 ValueToStream(S,ObjPtr->flags);
3844
3845 ValueToStream(S,ObjPtr->_class);
3846 ValueToStream(S,ObjPtr->friends);
3847 ValueToStream(S,ObjPtr->enemies);
3848
3849 ValueToStream(S,ObjPtr->sound_pitch);
3850 ValueToStream(S,ObjPtr->activation_sound);
3851 ValueToStream(S,ObjPtr->friendly_activation_sound);
3852 ValueToStream(S,ObjPtr->clear_sound);
3853 ValueToStream(S,ObjPtr->kill_sound);
3854 ValueToStream(S,ObjPtr->apology_sound);
3855 ValueToStream(S,ObjPtr->friendly_fire_sound);
3856 ValueToStream(S,ObjPtr->flaming_sound);
3857 ValueToStream(S,ObjPtr->random_sound);
3858 ValueToStream(S,ObjPtr->random_sound_mask);
3859
3860 ValueToStream(S,ObjPtr->carrying_item_type);
3861
3862 ValueToStream(S,ObjPtr->radius);
3863 ValueToStream(S,ObjPtr->height);
3864 ValueToStream(S,ObjPtr->preferred_hover_height);
3865 ValueToStream(S,ObjPtr->minimum_ledge_delta);
3866 ValueToStream(S,ObjPtr->maximum_ledge_delta);
3867 ValueToStream(S,ObjPtr->external_velocity_scale);
3868 ValueToStream(S,ObjPtr->impact_effect);
3869 ValueToStream(S,ObjPtr->melee_impact_effect);
3870 ValueToStream(S,ObjPtr->contrail_effect);
3871
3872 ValueToStream(S,ObjPtr->half_visual_arc);
3873 ValueToStream(S,ObjPtr->half_vertical_visual_arc);
3874 ValueToStream(S,ObjPtr->visual_range);
3875 ValueToStream(S,ObjPtr->dark_visual_range);
3876 ValueToStream(S,ObjPtr->intelligence);
3877 ValueToStream(S,ObjPtr->speed);
3878 ValueToStream(S,ObjPtr->gravity);
3879 ValueToStream(S,ObjPtr->terminal_velocity);
3880 ValueToStream(S,ObjPtr->door_retry_mask);
3881 ValueToStream(S,ObjPtr->shrapnel_radius);
3882 S = pack_damage_definition(S,&ObjPtr->shrapnel_damage,1);
3883
3884 ValueToStream(S,ObjPtr->hit_shapes);
3885 ValueToStream(S,ObjPtr->hard_dying_shape);
3886 ValueToStream(S,ObjPtr->soft_dying_shape);
3887 ValueToStream(S,ObjPtr->hard_dead_shapes);
3888 ValueToStream(S,ObjPtr->soft_dead_shapes);
3889 ValueToStream(S,ObjPtr->stationary_shape);
3890 ValueToStream(S,ObjPtr->moving_shape);
3891 ValueToStream(S,ObjPtr->teleport_in_shape);
3892 ValueToStream(S,ObjPtr->teleport_out_shape);
3893
3894 ValueToStream(S,ObjPtr->attack_frequency);
3895 AttackDefToStream(S,ObjPtr->melee_attack);
3896 AttackDefToStream(S,ObjPtr->ranged_attack);
3897 }
3898
3899 assert((S - Stream) == static_cast<ptrdiff_t>(Count*SIZEOF_monster_definition));
3900 return S;
3901 }
3902
init_monster_definitions()3903 void init_monster_definitions()
3904 {
3905 memcpy(monster_definitions, original_monster_definitions, sizeof(monster_definitions));
3906 }
3907
3908 struct damage_kick_definition *original_damage_kick_definitions = NULL;
3909
reset_mml_damage_kicks()3910 void reset_mml_damage_kicks()
3911 {
3912 if (original_damage_kick_definitions) {
3913 for (unsigned i = 0; i < NUMBER_OF_DAMAGE_TYPES; i++)
3914 damage_kick_definitions[i] = original_damage_kick_definitions[i];
3915 free(original_damage_kick_definitions);
3916 original_damage_kick_definitions = NULL;
3917 }
3918 }
3919
parse_mml_damage_kicks(const InfoTree & root)3920 void parse_mml_damage_kicks(const InfoTree& root)
3921 {
3922 // back up old values first
3923 if (!original_damage_kick_definitions) {
3924 original_damage_kick_definitions = (struct damage_kick_definition *) malloc(sizeof(struct damage_kick_definition) * NUMBER_OF_DAMAGE_TYPES);
3925 assert(original_damage_kick_definitions);
3926 for (unsigned i = 0; i < NUMBER_OF_DAMAGE_TYPES; i++)
3927 original_damage_kick_definitions[i] = damage_kick_definitions[i];
3928 }
3929
3930 BOOST_FOREACH(InfoTree kick, root.children_named("kick"))
3931 {
3932 int16 index;
3933 if (!kick.read_indexed("index", index, NUMBER_OF_DAMAGE_TYPES))
3934 continue;
3935 damage_kick_definition& def = damage_kick_definitions[index];
3936
3937 kick.read_attr("base", def.base_value);
3938 kick.read_attr("mult", def.delta_vitality_multiplier);
3939 kick.read_attr("vertical", def.is_also_vertical);
3940 kick.read_attr("death_action", def.death_action);
3941 }
3942 }
3943
reset_mml_monsters()3944 void reset_mml_monsters()
3945 {
3946 monster_must_be_exterminated.clear();
3947 monster_must_be_exterminated.resize(NUMBER_OF_MONSTER_TYPES, false);
3948 }
3949
parse_mml_monsters(const InfoTree & root)3950 void parse_mml_monsters(const InfoTree& root)
3951 {
3952 BOOST_FOREACH(InfoTree monster, root.children_named("monster"))
3953 {
3954 int16 index;
3955 if (!monster.read_indexed("index", index, NUMBER_OF_MONSTER_TYPES))
3956 continue;
3957 bool exterminate;
3958 if (monster.read_attr("must_be_exterminated", exterminate))
3959 monster_must_be_exterminated[index] = exterminate;
3960 }
3961 }
3962