/* MONSTERS.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Tuesday, November 10, 1992 1:10:20 PM Friday, May 27, 1994 11:21:07 AM split into MONSTERS.C, PROJECTILES.C and EFFECTS.C; unified active_monster and monster array. Friday, September 30, 1994 5:48:25 PM (Jason) started adding comments again. damage_monsters_in_radius() is less forgiving in z now. Monday, December 5, 1994 9:07:37 PM (Jason) rebellion environment function (all _clients hate all _pfhor). Wednesday, February 1, 1995 2:29:01 AM (Jason') kill_sounds; invisible monsters donÕt move Wednesday, June 14, 1995 10:14:24 AM (Jason) rewrite for marathon2 (halfway done). Monday, July 10, 1995 11:49:06 AM (Jason) rewrite for marathon2 done. my bobs wonÕt listen to your fucking whining. Jan 30, 2000 (Loren Petrich): Added some typecasts Removed some "static" declarations that conflict with "extern" Feb 3, 2000 (Loren Petrich): Treating Jjaro goo like sewage Feb. 4, 2000 (Loren Petrich): Changed halt() to assert(false) for better debugging Feb 6, 2000 (Loren Petrich): Added access to size of monster-definition structure Feb 12, 2000 (Loren Petrich): Suppressed an exposed "dprintf" as an unnecessary interrupt. Feb 16, 2000 (Loren Petrich): Added a check on the polygon index after a line-transparency check; this is in case there is no polygon on the other side. Feb 17, 2000 (Loren Petrich): Fixed stuff near GUESS_HYPOTENUSE() to be long-distance-friendly Feb 19, 2000 (Loren Petrich): Added growable lists of indices of objects to be checked for collisions Feb 24, 2000 (Loren Petrich): Suppressed some asserts about monster speeds Apr 27, 2000 (Loren Petrich): Added some behavior in the case of a monster both floating and flying to handle the map "Aqualung" correctly. May 29, 2000 (Loren Petirch): Fixed side effect of fixing keyframe-never-zero bug: if the keyframe is zero, then a sequence never triggers shrapnel damage. Thus, Hunters die a soft death more harmlessly. Jun 11, 2000 (Loren Petrich): Pegging health and oxygen to maximum values when damaged; takes into account negative damage from healing projectiles. Jul 1, 2000 (Loren Petrich): Inlined the accessors Aug 30, 2000 (Loren Petrich): Added stuff for unpacking and packing Oct 13, 2000 (Loren Petrich) Converted the intersected-objects list into a Standard Template Library vector Oct 26, 2000 (Mark Levin) Revealed a few functions needed by Pfhortran Jan 12, 2003 (Loren Petrich) Added controllable damage kicks */ #include #include #include "cseries.h" #include "map.h" #include "render.h" #include "interface.h" #include "flood_map.h" #include "effects.h" #include "monsters.h" #include "projectiles.h" #include "player.h" #include "platforms.h" #include "scenery.h" #include "SoundManager.h" #include "fades.h" #include "items.h" #include "media.h" #include "Packing.h" #include "lua_script.h" #ifdef env68k #pragma segment objects #endif /* //explosive deaths should cause damage during their key frame */ /* ---------- sounds */ /* ---------- constants */ #define OBSTRUCTION_DEACTIVATION_MASK 0x7 #define EVASIVE_MANOUVER_DISTANCE WORLD_ONE_HALF #define MONSTER_EXTERNAL_DECELERATION (WORLD_ONE/200) #define MONSTER_MINIMUM_EXTERNAL_VELOCITY (10*MONSTER_EXTERNAL_DECELERATION) #define MONSTER_MAXIMUM_EXTERNAL_VELOCITY (TICKS_PER_SECOND*MONSTER_EXTERNAL_DECELERATION) /* the height below which we donÕt bother to float up a ledge (we just run right over it) */ #define MINIMUM_FLOATING_HEIGHT WORLD_ONE_FOURTH #define MINIMUM_ACTIVATION_SEPARATION TICKS_PER_SECOND /* when looking for things under or at this light intensity the monster must use his dark visual range */ #define LOW_LIGHT_INTENSITY 0 /* maximum area we will search out to find a new target */ #define MAXIMUM_TARGET_SEARCH_AREA (7*WORLD_ONE*WORLD_ONE) #define MONSTER_PLATFORM_BUFFER_DISTANCE (WORLD_ONE/8) #define GLUE_TRIGGER_ACTIVATION_RANGE (8*WORLD_ONE) #define MONSTER_ALERT_ACTIVATION_RANGE (5*WORLD_ONE) #define MONSTER_PATHFINDING_OBSTRUCTION_COST (2*WORLD_ONE*WORLD_ONE) #define MONSTER_PATHFINDING_PLATFORM_COST (4*WORLD_ONE*WORLD_ONE) #define MINIMUM_MONSTER_PATHFINDING_POLYGON_AREA (WORLD_ONE) #define TERMINAL_VERTICAL_MONSTER_VELOCITY (WORLD_ONE/5) #define MINIMUM_DYING_EXTERNAL_VELOCITY (WORLD_ONE/8) #define CIVILIANS_KILLED_BY_PLAYER_THRESHHOLD 3 #define CIVILIANS_KILLED_DECREMENT_MASK 0x1ff enum /* monster attitudes, extracted from enemies and friends bitfields by get_monster_attitude() */ { _neutral, _friendly, _hostile }; enum /* returned by find_obstructing_terrain_feature() */ { _standing_on_sniper_ledge, _entering_platform_polygon, _leaving_platform_polygon, _flying_or_floating_transition }; #define MINIMUM_SNIPER_ELEVATION WORLD_ONE_HALF /* ---------- structures */ struct monster_pathfinding_data { struct monster_definition *definition; struct monster_data *monster; bool cross_zone_boundaries; }; // How much external velocity is imparted by some damage? struct damage_kick_definition { short base_value; float delta_vitality_multiplier; bool is_also_vertical; }; /* ---------- definitions */ // LP: implements commented-out damage-kick code struct damage_kick_definition damage_kick_definitions[NUMBER_OF_DAMAGE_TYPES] = { {0, 1, true}, // _damage_explosion, {0, 3, true}, // _damage_electrical_staff, {0, 1, false}, // _damage_projectile, {0, 1, false}, // _damage_absorbed, {0, 1, false}, // _damage_flame, {0, 1, false}, // _damage_hound_claws, {0, 1, false}, // _damage_alien_projectile, {0, 1, false}, // _damage_hulk_slap, {0, 3, true}, // _damage_compiler_bolt, {0, 0, false}, // _damage_fusion_bolt, {0, 1, false}, // _damage_hunter_bolt, {0, 1, false}, // _damage_fist, {250, 0, false}, // _damage_teleporter, {0, 1, false}, // _damage_defender, {0, 3, true}, // _damage_yeti_claws, {0, 1, false}, // _damage_yeti_projectile, {0, 1, false}, // _damage_crushing, {0, 1, false}, // _damage_lava, {0, 1, false}, // _damage_suffocation, {0, 1, false}, // _damage_goo, {0, 1, false}, // _damage_energy_drain, {0, 1, false}, // _damage_oxygen_drain, {0, 1, false}, // _damage_hummer_bolt, {0, 0, true} // _damage_shotgun_projectile, }; /* ---------- globals */ /* import monster definition constants, structures and globals */ #include "monster_definitions.h" // LP addition: growable list of intersected objects static vector IntersectedObjects; /* ---------- private prototypes */ static monster_definition *get_monster_definition( const short type); static void monster_needs_path(short monster_index, bool immediately); static void generate_new_path_for_monster(short monster_index); void advance_monster_path(short monster_index); static short get_monster_attitude(short monster_index, short target_index); void change_monster_target(short monster_index, short target_index); static bool switch_target_check(short monster_index, short attacker_index, short delta_vitality); static bool clear_line_of_sight(short viewer_index, short target_index, bool full_circle); static void handle_moving_or_stationary_monster(short monster_index); static void execute_monster_attack(short monster_index); static void kill_monster(short monster_index); static bool translate_monster(short monster_index, world_distance distance); static bool try_monster_attack(short monster_index); long monster_pathfinding_cost_function(short source_polygon_index, short line_index, short destination_polygon_index, void *data); void set_monster_action(short monster_index, short action); void set_monster_mode(short monster_index, short new_mode, short target_index); static short find_obstructing_terrain_feature(short monster_index, short *feature_index, short *relevant_polygon_index); static short position_monster_projectile(short aggressor_index, short target_index, struct attack_definition *attack, world_point3d *origin, world_point3d *destination, world_point3d *_vector, angle theta); static void update_monster_vertical_physics_model(short monster_index); static void update_monster_physics_model(short monster_index); static long monster_activation_flood_proc(short source_polygon_index, short line_index, short destination_polygon_index, void *data); static bool attempt_evasive_manouvers(short monster_index); static short nearest_goal_polygon_index(short polygon_index); static long nearest_goal_cost_function(short source_polygon_index, short line_index, short destination_polygon_index, void *unused); static void cause_shrapnel_damage(short monster_index); // For external use monster_definition *get_monster_definition_external(const short type); /* ---------- code */ monster_data *get_monster_data( short monster_index) { struct monster_data *monster = GetMemberWithBounds(monsters,monster_index,MAXIMUM_MONSTERS_PER_MAP); vassert(monster, csprintf(temporary, "monster index #%d is out of range", monster_index)); vassert(SLOT_IS_USED(monster), csprintf(temporary, "monster index #%d (%p) is unused", monster_index, monster)); return monster; } monster_definition *get_monster_definition( const short type) { monster_definition *definition = GetMemberWithBounds(monster_definitions,type,NUMBER_OF_MONSTER_TYPES); assert(definition); return definition; } //a non-inlined version for external use monster_definition *get_monster_definition_external( const short type) { return get_monster_definition(type); } /* returns new monster index if successful, NONE otherwise */ short new_monster( struct object_location *location, short monster_type) { struct monster_definition *definition= get_monster_definition(monster_type); short original_monster_type= monster_type; struct monster_data *monster; short drop_mask= NONE; short monster_index= NONE; short flags= _monster_has_never_been_activated; switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: drop_mask= 3; break; /* drop every fourth monster */ case _easy_level: drop_mask= 7; break; /* drop every eighth monster */ /* otherwise, drop no monsters */ } if ((definition->flags&_monster_cannot_be_dropped) || !(definition->flags&_monster_is_alien) || drop_mask==NONE || (++dynamic_world->new_monster_vanishing_cookie&drop_mask)) { /* check to see if we should promote or demote this monster based on difficulty level */ if (definition->flags&_monster_major) { short demote_mask= NONE; switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: demote_mask= 1; break; /* demote every other major */ case _easy_level: demote_mask= 3; break; /* demote every fourth major */ /* otherwise, demote no monsters */ } if (demote_mask!=NONE && !(++dynamic_world->new_monster_mangler_cookie&demote_mask)) definition= get_monster_definition(monster_type-= 1), flags|= _monster_was_demoted; } else { if (definition->flags&_monster_minor) { short promote_mask= NONE; switch (dynamic_world->game_information.difficulty_level) { case _major_damage_level: promote_mask= 1; break; /* promote every other minor */ case _total_carnage_level: promote_mask= 0; break; /* promote every minor */ /* otherwise, promote no monsters */ } if (promote_mask!=NONE && !(++dynamic_world->new_monster_mangler_cookie&promote_mask)) definition= get_monster_definition(monster_type+= 1), flags|= _monster_was_promoted; } } for (monster_index=0,monster=monsters;monster_indexcollection, definition->stationary_shape)); if (object_index!=NONE) { struct object_data *object= get_object_data(object_index); /* not doing this in !DEBUG resulted in sync errors; mmm... random data, so tasty */ obj_set(*monster, 0x80); if (location->flags&_map_object_is_blind) flags|= _monster_is_blind; if (location->flags&_map_object_is_deaf) flags|= _monster_is_deaf; if (location->flags&_map_object_floats) flags|= _monster_teleports_out_when_deactivated; /* initialize the monster_data structure; we donÕt touch most of the fields here because the monster is initially inactive (and they will be initialized when the monster is activated) */ monster->type= monster_type; monster->activation_bias= DECODE_ACTIVATION_BIAS(location->flags); monster->vitality= NONE; /* if a monster is activated with vitality==NONE, it will be properly initialized */ monster->object_index= object_index; monster->flags= flags; monster->goal_polygon_index= monster->activation_bias==_activate_on_goal ? nearest_goal_polygon_index(location->polygon_index) : NONE; monster->sound_polygon_index= object->polygon; monster->sound_location= object->location; MARK_SLOT_AS_USED(monster); /* initialize the monsterÕs object */ if (definition->flags&_monster_is_invisible) object->transfer_mode= _xfer_invisibility; if (definition->flags&_monster_is_subtly_invisible) object->transfer_mode= _xfer_subtle_invisibility; if (definition->flags&_monster_is_enlarged) object->flags|= _object_is_enlarged; if (definition->flags&_monster_is_tiny) object->flags|= _object_is_tiny; SET_OBJECT_SOLIDITY(object, true); SET_OBJECT_OWNER(object, _object_is_monster); object->permutation= monster_index; object->sound_pitch= definition->sound_pitch; /* make sure the object frequency stuff keeps track of how many monsters are on the map */ object_was_just_added(_object_is_monster, original_monster_type); } else { monster_index= NONE; } break; } } if (monster_index==MAXIMUM_MONSTERS_PER_MAP) monster_index= NONE; } /* keep track of how many civilians we drop on this level */ // if (monster_index!=NONE && (definition->_class&_class_human_civilian)) dynamic_world->current_civilian_count+= 1; return monster_index; } /* assumes ¶t==1 tick */ void move_monsters( void) { struct monster_data *monster; bool monster_got_time= false; bool monster_built_path= (dynamic_world->tick_count&3) ? true : false; short monster_index; for (monster_index= 0, monster= monsters; monster_indexobject_index); if (MONSTER_IS_ACTIVE(monster)) { if (!OBJECT_IS_INVISIBLE(object)) { struct monster_definition *definition= get_monster_definition(monster->type); short animation_flags; // AlexJLS patch: effect of dangerous polygons cause_polygon_damage(object->polygon,monster_index); /* clear the recovering from hit flag, mark the monster as not idle */ SET_MONSTER_IDLE_STATUS(monster, false); update_monster_vertical_physics_model(monster_index); /* update our objectÕs animation unless weÕre ÔsufferingÕ from an external velocity or weÕre airborne (if weÕre a flying or floating monster, ignore both of these */ if ((!monster->external_velocity&&!monster->vertical_velocity) || ((monster->action!=_monster_is_being_hit||!monster->external_velocity) && (definition->flags&(_monster_floats|_monster_flys)))) { animate_object(monster->object_index); } animation_flags= GET_OBJECT_ANIMATION_FLAGS(object); /* give this monster time, if we can and he needs it */ if (!monster_got_time && monster_index>dynamic_world->last_monster_index_to_get_time && !MONSTER_IS_DYING(monster)) { switch (monster->mode) { case _monster_unlocked: /* if this monster is unlocked and we havenÕt already given a monster time, call find_closest_appropriate_target() */ change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false)); monster_got_time= true; break; case _monster_lost_lock: case _monster_losing_lock: /* if this monster has lost or is losing lock and we havenÕt already given a monster time, check to see if his target has become visible again */ if (clear_line_of_sight(monster_index, monster->target_index, false)) { change_monster_target(monster_index, monster->target_index); } monster_got_time= true; break; } /* if we gave this guy time, make room for the next guy */ if (monster_got_time) dynamic_world->last_monster_index_to_get_time= monster_index; } /* if this monster needs a path, generate one (unless weÕve already generated a path this frame in which case weÕll wait until next frame, UNLESS the monster has no path in which case it needs one regardless) */ if (MONSTER_NEEDS_PATH(monster) && !MONSTER_IS_DYING(monster) && !MONSTER_IS_ATTACKING(monster) && ((!monster_built_path && monster_index>dynamic_world->last_monster_index_to_build_path) || monster->path==NONE)) { generate_new_path_for_monster(monster_index); if (!monster_built_path) { monster_built_path= true; dynamic_world->last_monster_index_to_build_path= monster_index; } } /* itÕs possible that we couldnÕt get where we wanted to go, or that we arrived there and deactivated ourselves; if this happens we donÕt want to continue processing the monster as if it were active */ if (MONSTER_IS_ACTIVE(monster)) { /* move the monster; check to see if we can attack; resolve modes ending; etc. */ switch (monster->action) { case _monster_is_waiting_to_attack_again: case _monster_is_stationary: case _monster_is_moving: handle_moving_or_stationary_monster(monster_index); break; case _monster_is_attacking_close: case _monster_is_attacking_far: if (animation_flags&_obj_keyframe_started) execute_monster_attack(monster_index); if (animation_flags&_obj_last_frame_animated) { if (((monster->attack_repetitions-=1)<0) || !try_monster_attack(monster_index)) { /* after an attack has been initiated successfully we need to return to _monster_is_moving action, kill our path and ask for a new one (because weÕre pointed in the wrong direction now) */ set_monster_action(monster_index, (monster->attack_repetitions<0 && (definition->flags&_monster_waits_with_clear_shot) && MONSTER_IS_LOCKED(monster)) ? _monster_is_waiting_to_attack_again : _monster_is_moving); monster_needs_path(monster_index, true); monster->ticks_since_attack= 0; } } break; case _monster_is_teleporting_in: if (animation_flags&_obj_last_frame_animated) { monster->action= _monster_is_moving; set_monster_action(monster_index, _monster_is_moving); change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false)); } break; case _monster_is_teleporting_out: if (animation_flags&_obj_keyframe_started) { monster->action= _monster_is_dying_soft; // to prevent aggressors from relocking monster_died(monster_index); teleport_object_out(monster->object_index); remove_map_object(monster->object_index); MARK_SLOT_AS_FREE(monster); } break; case _monster_is_being_hit: update_monster_physics_model(monster_index); if (animation_flags&_obj_last_frame_animated) { monster_needs_path(monster_index, true); set_monster_action(monster_index, _monster_is_moving); monster->external_velocity= 0; } break; case _monster_is_dying_soft: case _monster_is_dying_hard: case _monster_is_dying_flaming: update_monster_physics_model(monster_index); if ((definition->flags&_monster_has_delayed_hard_death) && monster->action==_monster_is_dying_soft) { if (!monster->external_velocity && object->location.z==monster->desired_height) //&& !monster->vertical_velocity) { set_monster_action(monster_index, _monster_is_dying_hard); } else { if (definition->contrail_effect!=NONE) new_effect(&object->location, object->polygon, definition->contrail_effect, object->facing); } } else { // LP change: if keyframe is zero, then a monster should not produce shrapnel damage. // This fixes a side effect of a fix of the keyframe-never-zero bug, // which is that Hunters injure those nearby when they die a soft death. if (animation_flags&_obj_keyframe_started && (GET_SEQUENCE_FRAME(object->sequence) != 0)) cause_shrapnel_damage(monster_index); if (animation_flags&_obj_last_frame_animated) kill_monster(monster_index); } break; default: assert(false); break; } } } } else { /* all inactive monsters get time to scan for targets */ if (!monster_got_time && !MONSTER_IS_BLIND(monster) && monster_index>dynamic_world->last_monster_index_to_get_time) { change_monster_target(monster_index, find_closest_appropriate_target(monster_index, false)); if (MONSTER_HAS_VALID_TARGET(monster)) activate_nearby_monsters(monster->target_index, monster_index, _pass_one_zone_border); monster_got_time= true; dynamic_world->last_monster_index_to_get_time= monster_index; } } } /* WARNING: a large number of unusual things could have happened here, including the monster being dead, his slot being free, and his object having been removed from the map; in other words, itÕs probably not a good idea to do any postprocessing here */ } /* either there are no unlocked monsters or Ôdynamic_world->last_monster_index_to_get_timeÕ is higher than all of them (so we reset it to zero) ... same for paths */ if (!monster_got_time) dynamic_world->last_monster_index_to_get_time= -1; if (!monster_built_path) dynamic_world->last_monster_index_to_build_path= -1; if (dynamic_world->civilians_killed_by_players) { uint32 mask = 0; switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: mask= 0x7f; break; case _easy_level: mask= 0xff; break; case _normal_level: mask= 0x1ff; break; case _major_damage_level: mask= 0x3ff; break; case _total_carnage_level: mask= 0x7ff; break; } if (!(dynamic_world->tick_count&mask)) { dynamic_world->civilians_killed_by_players-= 1; } } } /* when a monster dies, all monsters locked on it need to find something better to do; this function should be called before the given target is expunged from the monster list but after it is marked as dying */ void monster_died( short target_index) { struct monster_data *monster= get_monster_data(target_index); short monster_index; // dprintf("monster #%d is dead;g;", target_index); /* orphan this monsterÕs projectiles if they donÕt belong to a player (playerÕs monster slots are always valid and we want to correctly attribute damage and kills that ocurr after a player dies) */ if (!MONSTER_IS_PLAYER(monster)) orphan_projectiles(target_index); /* active monsters need extant paths deleted and should be marked as unlocked */ if (MONSTER_IS_ACTIVE(monster)) { set_monster_mode(target_index, _monster_unlocked, NONE); if (monster->path!=NONE) delete_path(monster->path); SET_MONSTER_NEEDS_PATH_STATUS(monster, false); monster->path= NONE; } /* anyone locked on this monster needs a clue */ for (monster_index= 0, monster= monsters; monster_indextarget_index==target_index) { short closest_target_index= find_closest_appropriate_target(monster_index, true); monster->target_index= NONE; monster_needs_path(monster_index, false); play_object_sound(monster->object_index, get_monster_definition(monster->type)->kill_sound); if (closest_target_index!=NONE) { change_monster_target(monster_index, closest_target_index); } else { if (monster->action==_monster_is_waiting_to_attack_again) set_monster_action(monster_index, _monster_is_moving); set_monster_mode(monster_index, _monster_unlocked, NONE); } } } } void initialize_monsters( void) { /* initialize our globals to be the same thing on all machines */ dynamic_world->civilians_killed_by_players= 0; dynamic_world->last_monster_index_to_get_time= -1; dynamic_world->last_monster_index_to_build_path= -1; dynamic_world->new_monster_mangler_cookie= global_random(); dynamic_world->new_monster_vanishing_cookie= global_random(); } /* call this when a new level is loaded from disk so the monsters can cope with their new world */ void initialize_monsters_for_new_level( void) { struct monster_data *monster; short monster_index; /* when a level is loaded after being saved all of an active monsterÕs data is still intact, but itÕs path no longer exists. this function resets all monsters so that they recalculate their paths, first thing. */ for (monster_index=0,monster=monsters;monster_indexpath= NONE; } } } static void load_sound(short sound_index) { SoundManager::instance()->LoadSound(sound_index); } void load_monster_sounds( short monster_type) { if (monster_type!=NONE) { struct monster_definition *definition= get_monster_definition(monster_type); process_collection_sounds(definition->collection, load_sound); load_projectile_sounds(definition->ranged_attack.type); load_projectile_sounds(definition->melee_attack.type); SoundManager::instance()->LoadSounds(&definition->activation_sound, 8); } } void mark_monster_collections( short monster_type, bool loading) { if (monster_type!=NONE) { struct monster_definition *definition= get_monster_definition(monster_type); /* mark the monster collection */ mark_collection(definition->collection, loading); /* mark the monsterÕs projectileÕs collection */ mark_projectile_collections(definition->ranged_attack.type, loading); mark_projectile_collections(definition->melee_attack.type, loading); } } enum { MAXIMUM_NEED_TARGET_INDEXES= 32 }; void activate_nearby_monsters( short target_index, /* activate with lock on this target (or NONE for lock-less activation) */ short caller_index, /* start the flood from here */ short flags) { struct monster_data *caller= get_monster_data(caller_index); if (dynamic_world->tick_count-caller->ticks_since_last_activation>MINIMUM_ACTIVATION_SEPARATION || (flags&_activation_cannot_be_avoided)) { short polygon_index= get_object_data(caller->object_index)->polygon; short need_target_indexes[MAXIMUM_NEED_TARGET_INDEXES]; short need_target_count= 0; int32 flood_flags= flags; /* flood out from the target monsterÕs polygon, searching through the object lists of all polygons we encounter */ polygon_index= flood_map(polygon_index, LONG_MAX, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags); while (polygon_index!=NONE) { short object_index; struct object_data *object; /* loop through all objects in this polygon looking for _hostile inactive or unlocked monsters */ for (object_index= get_polygon_data(polygon_index)->first_object; object_index!=NONE; object_index= object->next_object) { object= get_object_data(object_index); if (GET_OBJECT_OWNER(object)==_object_is_monster && (!OBJECT_IS_INVISIBLE(object) || (flags&_activate_invisible_monsters))) { short aggressor_index= object->permutation; struct monster_data *aggressor= get_monster_data(aggressor_index); // bool target_hostile= get_monster_attitude(aggressor_index, target_index)==_hostile; // bool caller_hostile= get_monster_attitude(aggressor_index, caller_index)==_hostile; // deaf monsters are only deaf to players which have always been hostile, so: // bobs are deaf to friendly players but not hostile ones // monsters are deaf to all players // deaf monsters ignore friendly monsters activating on other friendly monsters but // non-deaf ones DO NOT // !MONSTER_IS_PLAYER(caller) || TYPE_IS_FRIEND(get_monster_definition(aggressor->type), caller->type) || caller_hostile /* donÕt activate players or ourselves, and only activate monsters on glue polygons if they have previously been activated or weÕve been explicitly told to */ if (!MONSTER_IS_PLAYER(aggressor) && caller_index!=aggressor_index && target_index!=aggressor_index && (!(flood_flags&_passed_zone_border) || (!(aggressor->flags&_monster_has_never_been_activated))) && ((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) && aggressor->mode!=_monster_locked) { bool monster_was_active= true; /* activate the monster if heÕs inactive */ if (!MONSTER_IS_ACTIVE(aggressor)) { activate_monster(aggressor_index); monster_was_active= false; } if (monster_was_active || !(flags&_use_activation_biases) || (aggressor->activation_bias!=_activate_on_goal && aggressor->activation_bias!=_activate_randomly)) { if (monster_was_active || aggressor->activation_bias!=_activate_on_nearest_hostile) { /* if we have valid target and this monster thinks that target is hostile, lock on */ if (get_monster_attitude(aggressor_index, target_index)==_hostile) { switch_target_check(aggressor_index, target_index, 0); } else { /* but hey, if the target isnÕt hostile, maybe the caller is ... (mostly for the automated defenses and the civilians on the ship) */ if (get_monster_attitude(aggressor_index, caller_index)==_hostile) { switch_target_check(aggressor_index, caller_index, 0); } } } else { // must defer find_closest_appropriate_target; pathfinding is not reentrant if (need_target_count=0) { change_monster_target(need_target_indexes[need_target_count], find_closest_appropriate_target(need_target_indexes[need_target_count], true)); } caller->ticks_since_last_activation= dynamic_world->tick_count; } } static long monster_activation_flood_proc( short source_polygon_index, short line_index, short destination_polygon_index, void *data) { int32 *flags=(int32 *)data; struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index); struct line_data *line= get_line_data(line_index); long cost= 1; // dprintf("P#%d==>P#%d by L#%d", source_polygon_index, destination_polygon_index, line_index); (void) (source_polygon_index); if (destination_polygon->type==_polygon_is_zone_border) { if (((*flags)&_pass_one_zone_border) && !((*flags)&_passed_zone_border)) { *flags|= _passed_zone_border; } else { // canÕt pass this zone border cost= -1; } } if (!((*flags)&_pass_solid_lines) && LINE_IS_SOLID(line)) cost= -1; return cost; } #define LIVE_ALIEN_THRESHHOLD 8 bool live_aliens_on_map( void) { bool found_alien_which_must_be_killed= false; struct monster_data *monster; short live_alien_count= 0; short threshhold= LIVE_ALIEN_THRESHHOLD; short monster_index; for (monster_index= 0, monster= monsters; monster_indextype); #if 0 switch (monster->type) { case _monster_juggernaut_minor: case _monster_juggernaut_major: case _monster_alien_leader: found_alien_which_must_be_killed= true; break; } #endif if ((definition->flags&_monster_is_alien) || ((static_world->environment_flags&_environment_rebellion) && !MONSTER_IS_PLAYER(monster))) { live_alien_count+= 1; } } } if (static_world->environment_flags&_environment_rebellion) threshhold= 0; return live_alien_count<=threshhold ? found_alien_which_must_be_killed : true; } /* activate the given monster (initially unlocked) */ void activate_monster( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); // dprintf("monster #%d activated;g;", monster_index); assert(!MONSTER_IS_ACTIVE(monster)); assert(!MONSTER_IS_PLAYER(monster)); if (OBJECT_IS_INVISIBLE(object)) { struct polygon_data *polygon= get_polygon_data(object->polygon); if (polygon->media_index!=NONE) { struct media_data *media= get_media_data(polygon->media_index); // LP change: idiot-proofed this if (media) { if (media->height>object->location.z+definition->height && !(definition->flags&_monster_can_teleport_under_media)) { return; } } } } CLEAR_MONSTER_RECOVERING_FROM_HIT(monster); SET_MONSTER_IDLE_STATUS(monster, false); SET_MONSTER_ACTIVE_STATUS(monster, true); SET_MONSTER_BERSERK_STATUS(monster, false); SET_MONSTER_HAS_BEEN_ACTIVATED(monster); monster->flags&= ~(_monster_is_blind|_monster_is_deaf); monster->path= NONE; /* we used to set monster->target_index here, but it is invalid when mode==_monster_unlocked */ monster->mode= _monster_unlocked, monster->target_index= NONE; if (definition->attack_frequency == 0) // IP: Avoid division by zero definition->attack_frequency = 1; monster->ticks_since_attack= (definition->flags&_monster_attacks_immediately) ? definition->attack_frequency : global_random()%definition->attack_frequency; monster->desired_height= object->location.z; /* best guess */ monster->random_desired_height= INT16_MAX; // to be out of range and recalculated monster->external_velocity= monster->vertical_velocity= 0; monster->ticks_since_last_activation= 0; /* if vitality is NONE (-1) initialize it from the monster_definition, respecting the difficulty level if necessary */ if (monster->vitality==NONE) { short vitality= definition->vitality; if (definition->flags&_monster_is_alien) { switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: vitality-= vitality>>1; break; case _easy_level: vitality-= vitality>>2; break; case _major_damage_level: vitality+= vitality>>2; break; case _total_carnage_level: vitality+= vitality>>1; break; } } monster->vitality= vitality; } set_monster_action(monster_index, _monster_is_stationary); monster_needs_path(monster_index, true); if (OBJECT_IS_INVISIBLE(object)) { teleport_object_in(monster->object_index); } changed_polygon(object->polygon, object->polygon, NONE); } void deactivate_monster( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); // dprintf("monster #%d deactivated;g;", monster_index); assert(MONSTER_IS_ACTIVE(monster)); if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster)) monster->vertical_velocity= monster->external_velocity= 0; if (!monster->vertical_velocity && !monster->external_velocity) { if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster) && monster->action!=_monster_is_teleporting_out) { set_monster_action(monster_index, _monster_is_teleporting_out); } else { /* assume stationary shape before deactivation */ set_monster_action(monster_index, _monster_is_stationary); /* get rid of this monsterÕs path if he has one */ if (monster->path!=NONE) delete_path(monster->path); SET_MONSTER_ACTIVE_STATUS(monster, false); } } } /* returns a list of object indexes of all monsters in or adjacent to the given polygon, up to maximum_object_count. */ // LP change: called with growable list bool possible_intersecting_monsters( vector *IntersectedObjectsPtr, unsigned maximum_object_count, short polygon_index, bool include_scenery) { struct polygon_data *polygon= get_polygon_data(polygon_index); short *neighbor_indexes= get_map_indexes(polygon->first_neighbor_index, polygon->neighbor_count); bool found_solid_object= false; // Skip this step if neighbor indexes were not found if (!neighbor_indexes) return found_solid_object; for (short i=0;ineighbor_count;++i) { struct polygon_data *neighboring_polygon= get_polygon_data(*neighbor_indexes++); if (!POLYGON_IS_DETACHED(neighboring_polygon)) { short object_index= neighboring_polygon->first_object; while (object_index!=NONE) { struct object_data *object= get_object_data(object_index); bool solid_object= false; if (!OBJECT_IS_INVISIBLE(object)) { switch (GET_OBJECT_OWNER(object)) { case _object_is_monster: { struct monster_data *monster= get_monster_data(object->permutation); if (!MONSTER_IS_DYING(monster) && !MONSTER_IS_TELEPORTING(monster)) { solid_object= true; } break; } case _object_is_scenery: if (include_scenery && OBJECT_IS_SOLID(object)) solid_object= true; break; } if (solid_object) { found_solid_object= true; // LP change: if (IntersectedObjectsPtr && IntersectedObjectsPtr->size()& IntersectedObjects = *IntersectedObjectsPtr; for (j=0; jnext_object; } } } return found_solid_object; } /* when a target changes polygons, all monsters locked on it must recalculate their paths. target is an index into the monster list. */ void monster_moved( short target_index, short old_polygon_index) { struct monster_data *monster= get_monster_data(target_index); struct object_data *object= get_object_data(monster->object_index); short monster_index; if (!MONSTER_IS_PLAYER(monster)) { /* cause lights to light, platforms to trigger, etc.; the player does this differently */ changed_polygon(old_polygon_index, object->polygon, NONE); } for (monster_index=0,monster=monsters;monster_indextarget_index==target_index) { if (clear_line_of_sight(monster_index, target_index, true)) { if (monster->mode==_monster_losing_lock) set_monster_mode(monster_index, _monster_locked, monster->target_index); } else { struct monster_definition *definition= get_monster_definition(monster->type); short intelligence= definition->intelligence; switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: intelligence= intelligence>>2; break; case _easy_level: intelligence= intelligence>>1; break; case _major_damage_level: intelligence= 2*intelligence; break; case _total_carnage_level: intelligence= 4*intelligence; break; } /* we canÕt see our target: if this is first time, change from _monster_locked to _monster_losing_lock, if this isnÕt the first time and our target has switched polygons more times out of our sight than we have intelligence points, go to _lost_lock (which means we wonÕt get any more new paths when our target switches polygons, but we wonÕt clear our last one until we reach the end). */ if (monster->mode==_monster_locked) monster->changes_until_lock_lost= 0; if (monster->mode==_monster_losing_lock) monster->changes_until_lock_lost+= 1; set_monster_mode(monster_index, (monster->changes_until_lock_lost>=definition->intelligence) ? _monster_lost_lock : _monster_losing_lock, NONE); } /* if weÕre losing lock, donÕt recalculate our path (weÕre headed towards the targetÕs last-known location) */ if (monster->mode!=_monster_losing_lock) monster_needs_path(monster_index, false); } } } /* returns NONE or a monster_index that prevented us from moving */ short legal_player_move( short monster_index, world_point3d *new_location, world_distance *object_floor) /* must be set on entry */ { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); world_point3d *old_location= &object->location; size_t monster_count; world_distance radius, height; short obstacle_index= NONE; get_monster_dimensions(monster_index, &radius, &height); IntersectedObjects.clear(); possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, object->polygon, true); monster_count = IntersectedObjects.size(); for (size_t i=0;ipermutation, &obstacle_radius, &obstacle_height); break; case _object_is_scenery: get_scenery_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break; default: assert(false); break; } if (IntersectedObjects[i]!=monster->object_index) /* no self-intersection */ { world_point3d *obstacle_location= &obstacle->location; world_distance separation= radius+obstacle_radius; int32 separation_squared= separation*separation; world_distance new_dx= obstacle_location->x-new_location->x; world_distance new_dy= obstacle_location->y-new_location->y; int32 new_distance_squared= new_dx*new_dx+new_dy*new_dy; if (new_distance_squaredx-old_location->x; world_distance old_dy= obstacle_location->y-old_location->y; int32 old_distance_squared= old_dx*old_dx+old_dy*old_dy; if (old_distance_squared>new_distance_squared) { world_distance this_object_floor= obstacle_location->z+obstacle_height; /* itÕs possible we donÕt intersect in z */ if (new_location->z+heightz) continue; if (new_location->z>this_object_floor) { if (this_object_floor>*object_floor) *object_floor= this_object_floor; continue; } // 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); obstacle_index= IntersectedObjects[i]; break; } } } } return obstacle_index; } /* returns NONE or a monster_index that prevented us from moving */ short legal_monster_move( short monster_index, angle facing, /* could be different than object->facing for players and ÔflyingÕ (heh heh) monsters */ world_point3d *new_location) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); // world_point2d *old_location= (world_point2d *) &object->location; size_t monster_count; world_distance radius, height; short obstacle_index= NONE; get_monster_dimensions(monster_index, &radius, &height); IntersectedObjects.clear(); possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, object->polygon, true); monster_count= IntersectedObjects.size(); for (size_t i=0;ipermutation, &obstacle_radius, &obstacle_height); break; case _object_is_scenery: get_scenery_dimensions(obstacle->permutation, &obstacle_radius, &obstacle_height); break; default: assert(false); break; } // LP change: if (IntersectedObjects[i]!=monster->object_index) /* no self-intersection */ { world_point3d *obstacle_location= &obstacle->location; if (obstacle_location->zz+height && obstacle_location->z+obstacle_height>new_location->z) { world_distance separation= radius+obstacle_radius; world_distance dx= obstacle_location->x-new_location->x; world_distance dy= obstacle_location->y-new_location->y; 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); if (dx>-separation && dx-separation && dyFULL_CIRCLE-EIGHTH_CIRCLE) { // 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); obstacle_index= IntersectedObjects[i]; break; } } } } } return obstacle_index; } void get_monster_dimensions( short monster_index, world_distance *radius, world_distance *height) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); *radius= definition->radius; *height= definition->height; } void 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) { size_t object_count; (void) (primary_target_index); IntersectedObjects.clear(); possible_intersecting_monsters(&IntersectedObjects, LOCAL_INTERSECTING_MONSTER_BUFFER_SIZE, epicenter_polygon_index, false); object_count= IntersectedObjects.size(); struct object_data *aggressor = NULL; for (size_t i=0;ipermutation == aggressor_index) { // damage the aggressor last, so tag suicides are handled correctly aggressor = object; } else { world_distance distance= distance2d((world_point2d*)epicenter, (world_point2d*)&object->location); world_distance monster_radius, monster_height; get_monster_dimensions(object->permutation, &monster_radius, &monster_height); /* make sure we intersect the monsterÕs radius in the x,y-plane and that we intersect his cylinder in z */ if (distancez+radius-distance>object->location.z && epicenter->z-radius+distancelocation.z+monster_height) { if (!line_is_obstructed(epicenter_polygon_index, (world_point2d*)epicenter, object->polygon, (world_point2d*)&object->location)) { damage_monster(object->permutation, aggressor_index, aggressor_type, epicenter, damage, projectile_index); } } } } } // damage the aggressor if (aggressor != NULL) { world_distance distance= distance2d((world_point2d*)epicenter, (world_point2d*)&aggressor->location); world_distance monster_radius, monster_height; get_monster_dimensions(aggressor->permutation, &monster_radius, &monster_height); if (distancez+radius-distance>aggressor->location.z && epicenter->z-radius+distancelocation.z+monster_height) { if (!line_is_obstructed(epicenter_polygon_index, (world_point2d*)epicenter, aggressor->polygon, (world_point2d*)&aggressor->location)) { damage_monster(aggressor->permutation, aggressor_index, aggressor_type, epicenter, damage, projectile_index); } } } } } void damage_monster( short target_index, short aggressor_index, short aggressor_type, world_point3d *epicenter, struct damage_definition *damage, short projectile_index) { struct monster_data *monster= get_monster_data(target_index); struct monster_definition *definition= get_monster_definition(monster->type); struct monster_data *aggressor_monster= aggressor_index!=NONE ? get_monster_data(aggressor_index) : (struct monster_data *) NULL; // dprintf("%d base, %d random, %d scale.\n", damage->base, damage->random, damage->scale); short delta_vitality= calculate_damage(damage); // dprintf("we are doing %d damage\n", delta_vitality); world_distance external_velocity= 0; bool vertical_component= false; if (!(definition->immunities&FLAG(damage->type))) { // double damage for weaknesses if (definition->weaknesses&FLAG(damage->type)) delta_vitality<<= 1; // if this player was shot by a friendly, make him apologise if (aggressor_index!=NONE && get_monster_attitude(aggressor_index, target_index)==_friendly) { play_object_sound(aggressor_monster->object_index, get_monster_definition(aggressor_monster->type)->apology_sound); } if (MONSTER_IS_PLAYER(monster)) { damage_player(target_index, aggressor_index, aggressor_type, damage, projectile_index); } else { struct player_data *aggressor_player= (struct player_data *) NULL; /* only active monsters can take damage */ if (!MONSTER_IS_ACTIVE(monster)) activate_monster(target_index); /* convert aggressor monster index to a player index, if possible, to record damage */ if (aggressor_index!=NONE) { if (MONSTER_IS_PLAYER(aggressor_monster)) { aggressor_player= get_player_data(monster_index_to_player_index(aggressor_index)); aggressor_player->monster_damage_given.damage+= MAX(monster->vitality, delta_vitality); team_monster_damage_given[aggressor_player->team].damage += MAX(monster->vitality, delta_vitality); } } // LP change: pegging to maximum value if ((monster->vitality= MIN(int32(monster->vitality)-int32(delta_vitality),int32(INT16_MAX)))>0) { set_monster_action(target_index, _monster_is_being_hit); if ((definition->flags&_monster_is_berserker) && monster->vitality<(definition->vitality>>2)) SET_MONSTER_BERSERK_STATUS(monster, true); if (aggressor_index!=NONE) switch_target_check(target_index, aggressor_index, delta_vitality); // if a player shoots a monster who thinks the player is friendly; ask him what the fuck is up if (aggressor_player && get_monster_attitude(target_index, aggressor_index)==_friendly) play_object_sound(monster->object_index, definition->friendly_fire_sound); } else { monster->unused[0]=-1; //steal an unused for monster death if (!MONSTER_IS_DYING(monster)) { short action; if ((damage->type==_damage_flame||damage->type==_damage_lava||damage->type==_damage_alien_projectile) && (definition->flags&_monster_can_die_in_flames)) { action= _monster_is_dying_flaming; } else { if ((damage->type==_damage_explosion || damage->type==_damage_crushing || (FLAG(damage->type)&definition->weaknesses) || definition->soft_dying_shape==UNONE) && definition->hard_dying_shape!=UNONE && !(definition->flags&_monster_has_delayed_hard_death)) { action= _monster_is_dying_hard; } else { action= _monster_is_dying_soft; } if (definition->flags&_monster_has_delayed_hard_death) monster->vertical_velocity= -1; } if (action==_monster_is_dying_flaming) play_object_sound(monster->object_index, definition->flaming_sound); set_monster_action(target_index, action); monster_died(target_index); /* orphan projectile, recalculate aggressor paths */ if (aggressor_player) { aggressor_player->monster_damage_given.kills+= 1; team_monster_damage_given[aggressor_player->team].kills += 1; if (definition->_class&_class_human_civilian) dynamic_world->civilians_killed_by_players+= 1; } } // Lua script hook int aggressor_player_index = -1; if (aggressor_index!=NONE) if (MONSTER_IS_PLAYER(aggressor_monster)) aggressor_player_index = monster_index_to_player_index(aggressor_index); L_Call_Monster_Killed (target_index, aggressor_player_index, projectile_index); } } if (damage->type >= 0 && damage->type < NUMBER_OF_DAMAGE_TYPES) { damage_kick_definition& kick_def = damage_kick_definitions[damage->type]; external_velocity = (world_distance)(kick_def.base_value + kick_def.delta_vitality_multiplier*delta_vitality); vertical_component = kick_def.is_also_vertical; } /* switch (damage->type) { case _damage_teleporter: external_velocity= 250; break; case _damage_fusion_bolt: if (delta_vitality>100) vertical_component= true; break; case _damage_electrical_staff: case _damage_yeti_claws: case _damage_compiler_bolt: vertical_component= true; external_velocity= 3*delta_vitality; break; case _damage_shotgun_projectile: vertical_component= true; break; case _damage_explosion: vertical_component= true; external_velocity= delta_vitality; break; default: external_velocity= delta_vitality; break; } */ if (MONSTER_IS_DYING(monster) && external_velocityexternal_velocity_scale)>>FIXED_FRACTIONAL_BITS; if (external_velocity && epicenter) { struct object_data *object= get_object_data(monster->object_index); world_distance dx= object->location.x - epicenter->x; world_distance dy= object->location.y - epicenter->y; world_distance dz= object->location.z + (definition->height>>1) - epicenter->z; angle direction= arctangent(dx, dy); world_distance radius= isqrt(dx*dx+dy*dy+dz*dz); world_distance vertical_velocity= (vertical_component&&radius) ? ((external_velocity*dz)/radius) : 0; accelerate_monster(target_index, vertical_velocity, direction, external_velocity); } } } bool bump_monster( short aggressor_index, short monster_index) { #if 0 #ifdef DEBUG #ifdef env68k if (MONSTER_IS_PLAYER(get_monster_data(aggressor_index))) { dprintf("bumped monster @%p;dm #%d #%d;", get_monster_data(monster_index), get_monster_data(monster_index), sizeof(struct monster_data)); } #endif #endif #endif return switch_target_check(monster_index, aggressor_index, 0); } bool legal_polygon_height_change( short polygon_index, world_distance new_floor_height, world_distance new_ceiling_height, struct damage_definition *damage) { world_distance new_polygon_height= new_ceiling_height-new_floor_height; struct polygon_data *polygon= get_polygon_data(polygon_index); short object_index= polygon->first_object; world_distance minimum_height= dead_player_minimum_polygon_height(polygon_index); bool legal_change= true; while (object_index!=NONE) { struct object_data *object= get_object_data(object_index); if (GET_OBJECT_OWNER(object)==_object_is_monster && OBJECT_IS_VISIBLE(object)) { world_distance radius, height; get_monster_dimensions(object->permutation, &radius, &height); if (height>=new_polygon_height) { if (damage) { damage_monster(object->permutation, NONE, NONE, (world_point3d *) NULL, damage, NONE); play_object_sound(object_index, Sound_Crunched()); } legal_change= false; } } object_index= object->next_object; } return new_polygon_heightobject_index); if (object->location.z==polygon->floor_height) object->location.z= new_floor_height; } } void accelerate_monster( short monster_index, world_distance vertical_velocity, angle direction, world_distance velocity) { struct monster_data *monster= get_monster_data(monster_index); if (MONSTER_IS_PLAYER(monster)) { accelerate_player(monster_index, vertical_velocity, direction, velocity); } else { struct object_data *object= get_object_data(monster->object_index); object->facing= NORMALIZE_ANGLE(direction+HALF_CIRCLE); monster->external_velocity+= velocity; monster->vertical_velocity+= PIN(monster->vertical_velocity+vertical_velocity, -TERMINAL_VERTICAL_MONSTER_VELOCITY, TERMINAL_VERTICAL_MONSTER_VELOCITY); } } short get_monster_impact_effect( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); short impact_effect_index= definition->impact_effect; if (MONSTER_IS_PLAYER(monster)) { struct object_data *object= get_object_data(monster->object_index); switch (object->transfer_mode) { case _xfer_static: impact_effect_index= NONE; break; } } return impact_effect_index; } short get_monster_melee_impact_effect( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); return definition->melee_impact_effect; } #if 0 /* pick a random player; flood out from that player until we find a polygon legal for a monster drop whose center is not player visible. */ void pick_nearby_random_monster_position( world_point2d *p, short *polygon_index) { short player_index= global_random()%dynamic_world->player_count; short flood_polygon_index= get_player_data(player_index)->camera_polygon_index; polygon_index= flood_map(polygon_index, area, monster_activation_flood_proc, _breadth_first, &flood_flags); while (polygon_index!=NONE) { short object_index; struct object_data *object; struct polygon_data *polygon= get_polygon_data(polygon_index); polygon_index= flood_map(NONE, area, monster_activation_flood_proc, _breadth_first, &flood_flags); } } #endif /* ---------- private code */ static void cause_shrapnel_damage( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); if (definition->shrapnel_radius!=NONE) { damage_monsters_in_radius(NONE, NONE, NONE, &object->location, object->polygon, definition->shrapnel_radius, &definition->shrapnel_damage, NONE); } } static void update_monster_vertical_physics_model( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); struct object_data *object= get_object_data(monster->object_index); struct polygon_data *polygon= get_polygon_data(object->polygon); struct media_data *media= polygon->media_index==NONE ? (struct media_data *) NULL : get_media_data(polygon->media_index); uint32 moving_flags= MONSTER_IS_DYING(monster) ? 0 : (definition->flags&(_monster_flys|_monster_floats)); world_distance gravity= (static_world->environment_flags&_environment_low_gravity) ? (definition->gravity>>1) : definition->gravity; world_distance floor_height= polygon->floor_height; world_distance desired_height; world_distance old_height= object->location.z; bool above_ground, below_ground; if (media) { // flying and floating monsters treat media as the floor if (moving_flags && media->height>floor_height) floor_height= media->height + WORLD_ONE/16; // take damage if necessary if (media->height>object->location.z) { struct damage_definition *damage= get_media_damage(polygon->media_index, FIXED_ONE); if (damage) damage_monster(monster_index, NONE, NONE, (world_point3d *) NULL, damage, NONE); } } desired_height= (monster->desired_height==NONE||MONSTER_IS_DYING(monster)) ? polygon->floor_height : monster->desired_height; above_ground= object->location.z>desired_height; below_ground= object->location.zvertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity); if (below_ground) monster->vertical_velocity= 0, object->location.z= desired_height; break; case _monster_flys: if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity); if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity); break; case _monster_floats: if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity); if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity); break; default: /* canÕt fly and float, beavis */ // LP change: this stuff put in to handle the map "Aqualung" correctly if (above_ground && !MONSTER_IS_ATTACKING(monster)) monster->vertical_velocity= FLOOR(monster->vertical_velocity-gravity, -definition->terminal_velocity); if (below_ground) monster->vertical_velocity= CEILING(monster->vertical_velocity+gravity, definition->terminal_velocity); break; } /* add our vertical velocity to z */ object->location.z= PIN(object->location.z+monster->vertical_velocity, polygon->floor_height, polygon->ceiling_height-definition->height); /* if weÕre under the floor moving down, put us on the floor and clear our velocity; if weÕre above the floor moving up, put us on the floor and clear our velocity if we were previously below ground */ switch (moving_flags) { case 0: case _monster_floats: if (object->location.z<=desired_height && monster->vertical_velocity<0) monster->vertical_velocity= 0, object->location.z= desired_height; if (object->location.z>=desired_height && monster->vertical_velocity>0 && below_ground) monster->vertical_velocity= 0, object->location.z= desired_height; break; case _monster_flys: default: // LP: added this case to handle "Aqualung" correctly if (object->location.z<=desired_height && above_ground) monster->vertical_velocity>>= 1, object->location.z= desired_height; if (object->location.z>=desired_height && below_ground) monster->vertical_velocity>>= 1, object->location.z= desired_height; break; } /* reset desired height (flying and floating monsters often change this later) */ if (moving_flags&_monster_flys) { /* weÕre flying!: if we have no target, take the middle ground; if we have a target aim for his midsection */ if (MONSTER_HAS_VALID_TARGET(monster)) { struct monster_data *target= get_monster_data(monster->target_index); struct monster_definition *target_definition= get_monster_definition(target->type); monster->desired_height= get_object_data(target->object_index)->location.z + ((target_definition->height-definition->height)>>1) + definition->preferred_hover_height; monster->desired_height= PIN(monster->desired_height, floor_height+(definition->height>>2), polygon->ceiling_height-definition->height); } else { if (monster->random_desired_heightrandom_desired_height>polygon->ceiling_height) { world_distance delta= polygon->ceiling_height-floor_height-definition->height; monster->random_desired_height= floor_height + ((delta>0) ? (global_random()%delta) : 0); } monster->desired_height= MONSTER_IS_DYING(monster) ? polygon->floor_height : monster->random_desired_height; } } else { monster->desired_height= floor_height; } monster->sound_location= object->location; monster->sound_polygon_index= object->polygon; monster->sound_location.z+= definition->height - (definition->height>>1); if (media) { world_point3d location= object->location; short media_effect_type= NONE; location.z= media->height; if (old_height>=media->height && object->location.zheight) { media_effect_type= _large_media_detonation_effect; } if (old_heightheight && object->location.z>=media->height) { media_effect_type= _large_media_emergence_effect; } if (media_effect_type!=NONE) { short effect_type= NONE; get_media_detonation_effect(polygon->media_index, media_effect_type, &effect_type); new_effect(&location, object->polygon, effect_type, object->facing); } } } static void update_monster_physics_model( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); struct object_data *object= get_object_data(monster->object_index); if (monster->external_velocity) { world_point3d new_location= object->location; world_distance adjusted_floor_height, adjusted_ceiling_height; angle negative_facing= NORMALIZE_ANGLE(HALF_CIRCLE+object->facing); struct polygon_data *polygon; short supporting_polygon_index; /* move the monster */ translate_point2d((world_point2d*)&new_location, monster->external_velocity, negative_facing); keep_line_segment_out_of_walls(object->polygon, &object->location, &new_location, 0, definition->height, &adjusted_floor_height, &adjusted_ceiling_height, &supporting_polygon_index); if (legal_monster_move(monster_index, negative_facing, &new_location)==NONE) { short old_polygon_index= object->polygon; if (translate_map_object(monster->object_index, &new_location, NONE)) monster_moved(monster_index, old_polygon_index); } /* slow him down if heÕs touching the ground or flying */ polygon= get_polygon_data(object->polygon); if (object->location.z<=polygon->floor_height || (definition->flags&(_monster_flys|_monster_floats))) { if ((monster->external_velocity-= MONSTER_EXTERNAL_DECELERATION)external_velocity= 0; } } } } static void monster_needs_path( short monster_index, bool immediately) { struct monster_data *monster= get_monster_data(monster_index); if (monster->path!=NONE && immediately) delete_path(monster->path), monster->path= NONE; if (monster->action==_monster_is_moving && immediately) set_monster_action(monster_index, _monster_is_stationary); SET_MONSTER_NEEDS_PATH_STATUS(monster, true); } void set_monster_mode( short monster_index, short new_mode, short target_index) { struct monster_data *monster= get_monster_data(monster_index); /* if we were locked on a monster in our own polygon and we lost him then we donÕt have a path and going anywhere would be dangerous so we need to ask for a new path */ if (monster->mode==_monster_locked&&new_mode!=_monster_locked&&monster->path==NONE) monster_needs_path(monster_index, false); switch (new_mode) { case _monster_locked: (void)get_monster_data(target_index); /* for bounds checking only */ monster->target_index= target_index; CLEAR_TARGET_DAMAGE_FLAG(monster); // if (target_index==local_player->monster_index) // dprintf("monster #%d is locked on new target #%d;g;", monster_index, target_index); // switch (monster->type) // { // case _civilian_crew: case _civilian_engineering: case _civilian_science: case _civilian_security: // dprintf("monster #%d is locked on new target #%d;g;", monster_index, target_index); // } break; case _monster_losing_lock: /* target_index ignored, but still valid */ case _monster_lost_lock: (void)get_monster_data(monster->target_index); /* for bounds checking only */ break; case _monster_unlocked: monster->target_index= NONE; break; default: assert(false); break; } monster->mode= new_mode; } /* this function decides what the given monster actually wants to do, and then generates a path to get him there; if a monster who has lost lock calls this function, he will be forced to wander randomly or follow a guard path. */ static void generate_new_path_for_monster( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); struct monster_pathfinding_data data; short destination_polygon_index; world_point2d *destination; world_vector2d bias; /* delete this monsterÕs old path, if one exists, and clear the need path flag */ if (monster->path!=NONE) delete_path(monster->path), monster->path= NONE; SET_MONSTER_NEEDS_PATH_STATUS(monster, false); switch (monster->mode) { case _monster_losing_lock: /* our target is out of sight, but weÕre still zen-ing his position until we run out of intelligence points */ case _monster_locked: { struct monster_data *target= get_monster_data(monster->target_index); struct object_data *target_object= get_object_data(target->object_index); if (definition->random_sound_mask && !(global_random()&definition->random_sound_mask)) play_object_sound(monster->object_index, definition->random_sound); /* if we canÕt attack, run away, otherwise go for the target */ if (definition->flags&_monster_cannot_attack) { // LP changed: unnecessary to interrupt for this // dprintf("%p", monster); destination= (world_point2d *) &bias; bias.i= object->location.x - target_object->location.x; bias.j= object->location.y - target_object->location.y; destination_polygon_index= NONE; } else { /* if we still have lock, just build a new path and keep charging */ destination= (world_point2d *) &target_object->location; destination_polygon_index= MONSTER_IS_PLAYER(target) ? get_polygon_index_supporting_player(monster->target_index) : target_object->polygon; } break; } case _monster_lost_lock: /* if we lost lock during this path and we went as far as we could go, unlock */ set_monster_mode(monster_index, _monster_unlocked, NONE); // dprintf("monster #%d lost lock and reached end of path;g;", monster_index); case _monster_unlocked: /* if weÕre unlocked and need a new path, follow our guard path if we have one and run around randomly if we donÕt */ if ((destination_polygon_index= monster->goal_polygon_index)!=NONE) { destination= &get_polygon_data(destination_polygon_index)->center; } else { destination= (world_point2d *) NULL; } break; default: assert(false); break; } // dprintf("#%d: generating new %spath for monster #%d;g;", dynamic_world->tick_count, destination?"":"random ", monster_index); data.definition= definition; data.monster= monster; data.cross_zone_boundaries= destination_polygon_index==NONE ? false : true; monster->path= new_path((world_point2d *)&object->location, object->polygon, destination, destination_polygon_index, 3*definition->radius, monster_pathfinding_cost_function, &data); if (monster->path==NONE) { if (monster->action!=_monster_is_being_hit || MONSTER_IS_DYING(monster)) set_monster_action(monster_index, _monster_is_stationary); set_monster_mode(monster_index, _monster_unlocked, NONE); } else { advance_monster_path(monster_index); } } /* somebody just did damage to us; see if we should start attacking them or not. berserk monsters always switch targets. this is where we check to see if we go berserk, right? monster->vitality has already been changed (a monster who just bumped into another monster also calls this, with a delta_vitality of zero). returns true if an attack was started. */ static bool switch_target_check( short monster_index, short attacker_index, short delta_vitality) { struct monster_data *monster= get_monster_data(monster_index); bool switched_target= false; if (!MONSTER_IS_PLAYER(monster) && !MONSTER_IS_DYING(monster)) /* donÕt mess with players or dying monsters */ { if (MONSTER_HAS_VALID_TARGET(monster) && monster->target_index==attacker_index) { /* if we didnÕt know where our target was and he just shot us, we sort of like, know where he is now */ if (monster->mode==_monster_losing_lock) { set_monster_mode(monster_index, _monster_locked, attacker_index); monster_needs_path(monster_index, false); } /* if weÕre already after this guy and he just did damage to us, remember that */ if (delta_vitality) SET_TARGET_DAMAGE_FLAG(monster); switched_target= true; } else { struct monster_definition *definition= get_monster_definition(monster->type); short target_index= MONSTER_HAS_VALID_TARGET(monster) ? monster->target_index : NONE; struct monster_data *attacker= get_monster_data(attacker_index); short attacker_attitude, target_attitude; CLEAR_TARGET_DAMAGE_FLAG(monster); if (!MONSTER_IS_DYING(attacker) && !(definition->flags&_monster_cannot_attack)) { /* if our attacker is an enemy (or a neutral doing non-zero damage or we are berserk) and a) weÕre inactive, or, b) idle, or, c) unlocked, or, d) our current target has not done any damage, or, // e) attacker is an enemy and our current target is neutral or friendly, or, // f) attacker is a neutral and our current target is friendly, or, g) we canÕt attack and somebody just did damage to us then go kick his ass. */ attacker_attitude= get_monster_attitude(monster_index, attacker_index); if (target_index!=NONE) target_attitude= get_monster_attitude(monster_index, target_index); if (TYPE_IS_ENEMY(definition, attacker->type) || (TYPE_IS_NEUTRAL(definition, attacker->type)&&delta_vitality) || MONSTER_IS_BERSERK(monster)) { if (!MONSTER_IS_ACTIVE(monster) || MONSTER_IS_IDLE(monster) || monster->mode!=_monster_locked || !TARGET_HAS_DONE_DAMAGE(monster)) // (attacker_attitude==_hostile&&target_attitude!=_hostile) || // (attacker_attitude==_neutral&&target_attitude==_friendly) || // (delta_vitality&&(definition->flags&_monster_cannot_attack))) { change_monster_target(monster_index, attacker_index); if (delta_vitality) SET_TARGET_DAMAGE_FLAG(monster); switched_target= true; } } } } } return switched_target; } static short get_monster_attitude( short monster_index, short target_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); struct monster_data *target= get_monster_data(target_index); short target_type= target->type; short attitude; /* berserk monsters are hostile toward everything */ if (TYPE_IS_ENEMY(definition, target_type) || MONSTER_IS_BERSERK(monster) || (MONSTER_HAS_VALID_TARGET(monster) && monster->target_index==target_index) || ((definition->_class&_class_human_civilian) && MONSTER_IS_PLAYER(target) && dynamic_world->civilians_killed_by_players>=CIVILIANS_KILLED_BY_PLAYER_THRESHHOLD)) { attitude= _hostile; } else { attitude= (TYPE_IS_FRIEND(definition, target_type)) ? _friendly : _neutral; } // if ((definition->_class&_class_human_civilian) && MONSTER_IS_PLAYER(target)) // { // dprintf("#%d vs. #%d ==> #%d", monster_index, target_index, attitude); // } return attitude; } /* find_closest_appropriate_target() tries to do just that. it is a little broken in that it treats all monsters in a given polygon as if they were the same distance away, which could result in strange behavior. the assumption is that if there is a more accessable hostile monster nearby, that monster will attack and thus end a possible wild goose chase. if there is a closer hostile target which the aggressor subsequently attempts to move through, he will change lock and attack the obstruction instead, which will help minimize weirdness. full_circle is passed directly to clear_line_of_sight(). */ short find_closest_appropriate_target( short aggressor_index, bool full_circle) { struct monster_data *aggressor= get_monster_data(aggressor_index); struct monster_definition *definition= get_monster_definition(aggressor->type); short closest_hostile_target_index= NONE; if (MONSTER_IS_ACTIVE(aggressor)) { int32 flood_flags= _pass_one_zone_border; short polygon_index= get_object_data(get_monster_data(aggressor_index)->object_index)->polygon; /* flood out from the aggressor monsterÕs polygon, searching through the object lists of all polygons we encounter */ polygon_index= flood_map(polygon_index, LONG_MAX, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags); while (polygon_index!=NONE && closest_hostile_target_index==NONE) { short object_index; struct object_data *object; /* loop through all objects in this polygon looking for hostile monsters we can see */ for (object_index= get_polygon_data(polygon_index)->first_object; object_index!=NONE; object_index= object->next_object) { object= get_object_data(object_index); if (GET_OBJECT_OWNER(object)==_object_is_monster && OBJECT_IS_VISIBLE(object)) { short target_monster_index= object->permutation; struct monster_data *target_monster= get_monster_data(target_monster_index); if (!MONSTER_IS_DYING(target_monster) && target_monster_index!=aggressor_index) { if (get_monster_attitude(aggressor_index, target_monster_index)==_hostile) { if (((definition->flags&_monster_is_omniscent) || clear_line_of_sight(aggressor_index, target_monster_index, full_circle)) && (MONSTER_IS_ACTIVE(target_monster) || MONSTER_IS_PLAYER(target_monster) || (static_world->environment_flags&_environment_rebellion))) { /* found hostile, live, visible monster */ closest_hostile_target_index= target_monster_index; break; } } } } } polygon_index= flood_map(NONE, LONG_MAX, monster_activation_flood_proc, _flagged_breadth_first, &flood_flags); } } else { short player_index; /* if this monster is deactivated, only seeing a player will activate him */ for (player_index= 0; player_indexplayer_count; ++player_index) { struct player_data *player= get_player_data(player_index); if (get_monster_attitude(aggressor_index, player->monster_index)==_hostile && clear_line_of_sight(aggressor_index, player->monster_index, full_circle)) { closest_hostile_target_index= player->monster_index; break; } } } return closest_hostile_target_index; } /* if Ôfull_circleÕ is true, the monster can see in all directions. if Ôfull_circleÕ is false the monster respects his visual_arc and current facing. clear_line_of_sight() is implemented wholly in 2D and only attempts to connect the centers of the two monsters by a line. */ static bool clear_line_of_sight( short viewer_index, short target_index, bool full_circle) { struct monster_data *viewer= get_monster_data(viewer_index); struct object_data *viewer_object= get_object_data(viewer->object_index); struct monster_definition *viewer_definition= get_monster_definition(viewer->type); struct monster_data *target= get_monster_data(target_index); struct object_data *target_object= get_object_data(target->object_index); bool target_visible= true; { world_point3d *origin= &viewer_object->location; world_point3d *destination= &target_object->location; // LP change: made this long-distance friendly int32 dx= int32(destination->x)-int32(origin->x); int32 dy= int32(destination->y)-int32(origin->y); world_distance dz= destination->z-origin->z; int32 distance2d= GUESS_HYPOTENUSE(ABS(dx), ABS(dy)); /* if we canÕt see full circle, make sure the target is in our visual arc */ if (!full_circle) { angle theta= arctangent(dx, dy)-viewer_object->facing; angle phi= arctangent(distance2d, ABS(dz)); if (ABS(theta)>viewer_definition->half_visual_arc) target_visible= false; if (phi>=viewer_definition->half_vertical_visual_arc&&phihalf_vertical_visual_arc) target_visible= false; } /* we canÕt see some transfer modes */ switch (target_object->transfer_mode) { case _xfer_invisibility: case _xfer_subtle_invisibility: if (distance2d>viewer_definition->dark_visual_range) target_visible= false; break; } /* make sure the target is within our visual_range (taking any of his active effects, i.e. invisibility, into account) and that he isnÕt standing in a dark polygon beyond our dark_visual_range. */ if (target_visible) { if (distance2d>viewer_definition->visual_range) // || (distance2d>viewer_definition->dark_visual_range&&get_object_light_intensity(target->object_index)<=LOW_LIGHT_INTENSITY)) { target_visible= false; } } /* make sure there are no non-transparent lines between the viewer and the target */ if (target_visible) { short polygon_index= viewer_object->polygon; short line_index; do { line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)origin, (world_point2d *)destination); if (line_index!=NONE) { if (LINE_IS_TRANSPARENT(get_line_data(line_index))) { /* transparent line, find adjacent polygon */ polygon_index= find_adjacent_polygon(polygon_index, line_index); // LP change: make no polygon act like a non-transparent line if (polygon_index == NONE) target_visible= false; } else { /* non-transparent line, target not visible */ target_visible= false; } } else { /* we got to the targetÕs (x,y) location, but weÕre in a different polygon; heÕs invisible */ if (polygon_index!=target_object->polygon) target_visible= false; } } while (target_visible&&line_index!=NONE); } } return target_visible; } /* lock the given monster onto the given target, playing a locking sound if the monster previously didnÕt have a lock */ void change_monster_target( short monster_index, short target_index) { /* locking on ourselves would be cool, but ... */ if (monster_index!=target_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); if (target_index!=NONE) { /* only active monsters can have lock, so activate inactive monsters */ if (!MONSTER_IS_ACTIVE(monster)) activate_monster(monster_index); /* play activation sounds (including activating on a friendly) */ if (monster->target_index!=target_index && TYPE_IS_FRIEND(definition, get_monster_data(target_index)->type)) { play_object_sound(monster->object_index, definition->friendly_activation_sound); } else { if (monster->mode==_monster_unlocked) play_object_sound(monster->object_index, definition->activation_sound); } /* instantiate the new target and ask for a new path */ if (MONSTER_HAS_VALID_TARGET(monster) && target_index!=monster->target_index) CLEAR_TARGET_DAMAGE_FLAG(monster); monster_needs_path(monster_index, false); set_monster_mode(monster_index, _monster_locked, target_index); } else { if (MONSTER_IS_ACTIVE(monster)) { /* no target, if weÕre not unlocked mark us as unlocked and ask for a new path */ if (monster->mode!=_monster_unlocked) { // dprintf("monster #%d was locked on NONE;g;", monster_index); set_monster_mode(monster_index, _monster_unlocked, NONE); monster_needs_path(monster_index, false); } } } } } static void handle_moving_or_stationary_monster( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); if (monster->path==NONE && monster->mode!=_monster_locked && monster->action==_monster_is_stationary) { /* stationary, unlocked monsters without paths cannot move */ monster_needs_path(monster_index, false); } else { world_distance distance_moved= definition->speed; /* base speed on difficulty level (for aliens) and berserk status */ if (definition->flags&_monster_is_alien) { switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: distance_moved-= distance_moved>>3; break; case _easy_level: distance_moved-= distance_moved>>4; break; case _major_damage_level: distance_moved+= distance_moved>>3; break; case _total_carnage_level: distance_moved+= distance_moved>>2; break; } } if (MONSTER_IS_BERSERK(monster)) distance_moved+= (distance_moved>>1); if (monster->action!=_monster_is_waiting_to_attack_again) { if (translate_monster(monster_index, distance_moved)) { /* we moved: _monster_is_stationary becomes _monster_is_moving */ if (monster->action==_monster_is_stationary) set_monster_action(monster_index, _monster_is_moving); } else { /* we couldnÕt move: _monster_is_moving becomes _monster_is_stationary */ if (monster->action==_monster_is_moving) set_monster_action(monster_index, _monster_is_stationary); monster->ticks_since_attack+= 1; /* attacks occur twice as frequently if we canÕt move (damnit!) */ } } else { monster->ticks_since_attack+= 1; } /* whether we moved or not, see if we can attack if we have lock */ monster->ticks_since_attack+= MONSTER_IS_BERSERK(monster) ? 3 : 1; if (OBJECT_WAS_ANIMATED(object) && monster->mode==_monster_locked) { short attack_frequency= definition->attack_frequency; if (definition->flags&_monster_is_alien) { switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: attack_frequency= 3*attack_frequency; break; case _easy_level: attack_frequency= 2*attack_frequency; break; case _major_damage_level: attack_frequency= attack_frequency>>1; break; case _total_carnage_level: attack_frequency= attack_frequency>>2; break; } } if (monster->ticks_since_attack>attack_frequency) { if (try_monster_attack(monster_index)) { /* activate with lock nearby monsters on our target */ activate_nearby_monsters(monster->target_index, monster_index, _pass_one_zone_border); } else { if (monster->action==_monster_is_waiting_to_attack_again) { set_monster_action(monster_index, _monster_is_stationary); monster_needs_path(monster_index, true); } } } } } } void set_monster_action( short monster_index, short action) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); shape_descriptor shape; /* what shape should we use? */ if (action==_monster_is_dying_flaming) { shape= FLAMING_DYING_SHAPE; } else { switch (action) { case _monster_is_waiting_to_attack_again: case _monster_is_stationary: shape= definition->stationary_shape; break; case _monster_is_moving: shape= definition->moving_shape; break; case _monster_is_attacking_close: shape= definition->melee_attack.attack_shape; break; case _monster_is_attacking_far: shape= definition->ranged_attack.attack_shape; break; case _monster_is_being_hit: shape= definition->hit_shapes; break; case _monster_is_dying_hard: shape= definition->hard_dying_shape; break; case _monster_is_dying_soft: shape= definition->soft_dying_shape; break; case _monster_is_teleporting_in: shape= definition->teleport_in_shape; break; case _monster_is_teleporting_out: shape= definition->teleport_out_shape; break; default: dprintf("what is monster action #%d?", action); assert(false); break; } shape= shape==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, shape); } if (shape!=UNONE) { /* only set the action of the shape is not UNONE */ monster->action= action; set_object_shape_and_transfer_mode(monster->object_index, shape, NONE); /* if this monster does shrapnel damage, do it */ if (action==_monster_is_dying_hard && (definition->flags&_monster_has_delayed_hard_death)) { cause_shrapnel_damage(monster_index); } if ((definition->flags&_monster_has_nuclear_hard_death) && action==_monster_is_dying_hard) { start_fade(_fade_long_bright); SoundManager::instance()->PlayLocalSound(Sound_Exploding()); } } } /* do whatever needs to be done when this monster dies and remove it from the monster list */ static void kill_monster( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); struct object_data *object= get_object_data(monster->object_index); shape_descriptor shape; switch (monster->action) { case _monster_is_dying_soft: shape= definition->soft_dead_shapes==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, definition->soft_dead_shapes); break; case _monster_is_dying_hard: shape= definition->hard_dead_shapes==UNONE ? UNONE : BUILD_DESCRIPTOR(definition->collection, definition->hard_dead_shapes); break; case _monster_is_dying_flaming: shape= FLAMING_DEAD_SHAPE; break; default: assert(false); break; } /* add an item if weÕre supposed to be carrying something */ if (definition->carrying_item_type!=NONE && monster->action==_monster_is_dying_soft) { world_distance radius, height; world_point3d random_point; short random_polygon_index; get_monster_dimensions(monster_index, &radius, &height); random_point_on_circle(&object->location, object->polygon, radius, &random_point, &random_polygon_index); if (random_polygon_index!=NONE) { struct polygon_data *random_polygon= get_polygon_data(random_polygon_index); struct object_location location; switch (random_polygon->type) { case _polygon_is_platform: case _polygon_is_item_impassable: case _polygon_is_monster_impassable: case _polygon_is_teleporter: break; default: location.polygon_index= random_polygon_index; location.p.x= random_point.x, location.p.y= random_point.y, location.p.z= 0; location.yaw= 0; location.flags= 0; new_item(&location, definition->carrying_item_type); break; } } } /* stuff in an appropriate dead shape (or remove our object if we donÕt have a dead shape) */ if (shape==UNONE) { remove_map_object(monster->object_index); } else { turn_object_to_shit(monster->object_index); randomize_object_sequence(monster->object_index, shape); } /* recover original type and notify the object stuff a monster died */ if (monster->flags&_monster_was_promoted) monster->type-= 1; if (monster->flags&_monster_was_demoted) monster->type+= 1; object_was_just_destroyed(_object_is_monster, monster->type); MARK_SLOT_AS_FREE(monster); } /* move the monster along his current heading; if he reaches the center of his destination square, then point him at the next square and send him off. this used to chuck if the monster moved too far during a certain turn (which was completely possible when the player was wearing the red cloak in Pathways), but that was fixed. i just recoded this for marathon and it looks a hell of a lot better now. */ static bool translate_monster( short monster_index, world_distance distance) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); world_point3d new_location; short obstacle_index; bool legal_move= false; new_location= object->location; translate_point2d((world_point2d *)&new_location, distance, object->facing); /* find out where weÕre going and see if we could actually move there */ if ((obstacle_index= legal_monster_move(monster_index, object->facing, &new_location))==NONE) { /* legal move: see if there is a platform that we have to open or wait for, if not move, if so, wait */ short feature_index; short relevant_polygon_index; legal_move= true; switch (find_obstructing_terrain_feature(monster_index, &feature_index, &relevant_polygon_index)) { case _entering_platform_polygon: switch (monster_can_enter_platform(feature_index, relevant_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta)) { case _platform_will_never_be_accessable: monster_needs_path(monster_index, true); break; case _platform_will_be_accessable: /* we avoid vidding the door by only trying to open it every door_retry_mask+1 ticks */ if (!(dynamic_world->tick_count&definition->door_retry_mask)) try_and_change_platform_state(feature_index, true); SET_MONSTER_IDLE_STATUS(monster, true); legal_move= false; break; /* _platform_is_accessable */ } break; case _leaving_platform_polygon: switch (monster_can_leave_platform(feature_index, relevant_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta)) { case _exit_will_never_be_accessable: monster_needs_path(monster_index, true); break; case _exit_will_be_accessable: SET_MONSTER_IDLE_STATUS(monster, true); legal_move= false; break; /* _exit_is_accessable, ignored */ } break; case _flying_or_floating_transition: /* there is a wall in our way which we have to rise (or fall) along, so donÕt go anywhere unless weÕre over it (or under it) */ if (ABS(object->location.z-monster->desired_height)>MINIMUM_FLOATING_HEIGHT) legal_move= false; break; case _standing_on_sniper_ledge: /* weÕve been told to freeze on a sniper ledge (no saving throw) */ legal_move= false; break; } if (legal_move) { if ((monster->path_segment_length-= distance)<=0) { advance_monster_path(monster_index); } else { short old_polygon_index= object->polygon; /* update the monsterÕs object to reflect his new position */ if (translate_map_object(monster->object_index, &new_location, NONE)) monster_moved(monster_index, old_polygon_index); } legal_move= true; } } else { struct object_data *obstacle_object= get_object_data(obstacle_index); if (GET_OBJECT_OWNER(obstacle_object)==_object_is_monster) { struct monster_data *obstacle_monster= get_monster_data(obstacle_object->permutation); /* we collided with another monster: see if we want to attack him; if not, see if we can attack his current target (if he is locked or losing_lock); if not, drop lock and ask for a new path. */ if (!TYPE_IS_ENEMY(definition, obstacle_monster->type) && !(MONSTER_HAS_VALID_TARGET(monster)&&monster->target_index==obstacle_object->permutation) && !MONSTER_IS_BERSERK(monster)) { if (!MONSTER_IS_PLAYER(obstacle_monster)) { if (monster->mode!=_monster_locked) { if (!MONSTER_HAS_VALID_TARGET(obstacle_monster) || !switch_target_check(monster_index, obstacle_monster->target_index, 0)) { if (monster->mode==_monster_unlocked && !(global_random()&OBSTRUCTION_DEACTIVATION_MASK) && (monster->goal_polygon_index==NONE || monster->goal_polygon_index==object->polygon)) { deactivate_monster(monster_index); } else { monster_needs_path(monster_index, false); if (monster->mode!=_monster_locked) { /* if weÕre not locked, we might want to think about deactivating here, but for now we just build a new random path by forcing our state to _unlocked. */ set_monster_mode(monster_index, _monster_unlocked, NONE); // dprintf("monster #%d going unlocked by obstruction;g;", monster_index); } } } } else { attempt_evasive_manouvers(monster_index); } } SET_MONSTER_IDLE_STATUS(monster, true); } else { struct monster_definition *obstacle_definition= get_monster_definition(obstacle_monster->type); world_distance key_height= obstacle_object->location.z+(obstacle_definition->height>>1); change_monster_target(monster_index, obstacle_object->permutation); /* if weÕre a kamakazi and weÕre within range, pop */ if ((definition->flags&_monster_is_kamakazi) && object->location.z+definition->height>key_height && object->location.zflags&_monster_floats) { monster->desired_height= obstacle_object->location.z; } } } else { attempt_evasive_manouvers(monster_index); // to avoid the scenery } } return legal_move; } static bool attempt_evasive_manouvers( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); world_point2d destination= *((world_point2d*)&object->location); angle new_facing= NORMALIZE_ANGLE(object->facing + ((global_random()&1) ? QUARTER_CIRCLE : -QUARTER_CIRCLE)); world_distance original_floor_height= get_polygon_data(object->polygon)->floor_height; short polygon_index= object->polygon; bool successful= true; translate_point2d(&destination, EVASIVE_MANOUVER_DISTANCE, new_facing); do { short line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)&object->location, &destination); if (line_index==NONE) { polygon_index= NONE; } else { /* if we ran off the map, we failed */ if (LINE_IS_SOLID(get_line_data(line_index)) || (polygon_index= find_adjacent_polygon(polygon_index, line_index))==NONE) { polygon_index= NONE; successful= false; } else { struct polygon_data *polygon= get_polygon_data(polygon_index); if (polygon->floor_height!=original_floor_height || polygon->type==_polygon_is_monster_impassable) { polygon_index= NONE; successful= false; } } } } while (polygon_index!=NONE); if (successful) { object->facing= new_facing; if (monster->path!=NONE) delete_path(monster->path), monster->path= NONE; monster->path_segment_length= EVASIVE_MANOUVER_DISTANCE; } return successful; } void advance_monster_path( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); world_point2d path_goal; bool done= true; if (monster->path==NONE) { /* only locked monsters in their targetÕs polygon can advance without paths */ if (monster->mode!=_monster_locked || object->polygon!=get_object_data(get_monster_data(monster->target_index)->object_index)->polygon) { monster_needs_path(monster_index, true); return; } } else { done= move_along_path(monster->path, &path_goal); if (done) monster->path= NONE; } /* if weÕre locked without a path, head right for the bastard (heÕs in our polygon) */ if ((done||monster->path==NONE) && monster->mode==_monster_locked) { struct monster_data *target= get_monster_data(monster->target_index); struct object_data *target_object= get_object_data(target->object_index); if (object->polygon==target_object->polygon) { path_goal= *(world_point2d *)&get_object_data(get_monster_data(monster->target_index)->object_index)->location; done= false; } } if (done) { /* ask for a new path (never happens to locked monsters) */ monster_needs_path(monster_index, false); if (monster->mode==_monster_unlocked) { monster->goal_polygon_index= NONE; if (MONSTER_TELEPORTS_OUT_WHEN_DEACTIVATED(monster)) deactivate_monster(monster_index); } } else { /* point ourselves at this new point in the path */ object->facing= arctangent(path_goal.x-object->location.x, path_goal.y-object->location.y); monster->path_segment_length= distance2d(&path_goal, (world_point2d *)&object->location); } } static bool try_monster_attack( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); struct object_data *object= get_object_data(monster->object_index); struct monster_definition *definition= get_monster_definition(monster->type); short repetitions= NONE; short new_action= NONE, obstruction_index= NONE; angle theta = 0; if (MONSTER_HAS_VALID_TARGET(monster)) { struct object_data *target_object= get_object_data(get_monster_data(monster->target_index)->object_index); world_point3d origin= object->location, destination= target_object->location; world_distance range= distance2d((world_point2d *)&origin, (world_point2d *)&destination); short polygon_index; world_point3d _vector; theta= arctangent(destination.x-origin.x, destination.y-origin.y); angle delta_theta= NORMALIZE_ANGLE(theta-object->facing); if (!(definition->flags&_monster_cant_fire_backwards) || (delta_thetaFULL_CIRCLE-QUARTER_CIRCLE-QUARTER_CIRCLE/2)) { switch (monster->action) { case _monster_is_attacking_close: case _monster_is_attacking_far: new_action= monster->action; break; default: if (definition->ranged_attack.type!=NONE && rangeranged_attack.range) new_action= _monster_is_attacking_far; if (definition->melee_attack.type!=NONE && rangemelee_attack.range) { new_action= _monster_is_attacking_close; if (definition->flags&_monster_chooses_weapons_randomly) { if (global_random()&1) new_action= _monster_is_attacking_far; } } break; } /* if we have a melee attack and we're at short range, use it */ if (new_action==_monster_is_attacking_close) { /* make sure this is a valid projectile, that we donÕt hit any walls and that whatever we did hit is _hostile. */ polygon_index= position_monster_projectile(monster_index, monster->target_index, &definition->melee_attack, &origin, &destination, &_vector, theta); if (preflight_projectile(&origin, polygon_index, &destination, definition->melee_attack.error, definition->melee_attack.type, monster_index, monster->type, &obstruction_index)) { if ((obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_hostile) || !line_is_obstructed(object->polygon, (world_point2d *) &object->location, target_object->polygon, (world_point2d *) &target_object->location)) { repetitions= definition->melee_attack.repetitions; } } } else { /* make sure we have a ranged attack and our target is within range */ if (new_action==_monster_is_attacking_far) { /* make sure this is a valid projectile, that we donÕt hit any walls and that whatever we did hit is _hostile. */ polygon_index= position_monster_projectile(monster_index, monster->target_index, &definition->ranged_attack, &origin, &destination, &_vector, theta); if (preflight_projectile(&origin, polygon_index, &destination, definition->ranged_attack.error, definition->ranged_attack.type, monster_index, monster->type, &obstruction_index)) { if ((obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_hostile) || (obstruction_index==NONE && !line_is_obstructed(object->polygon, (world_point2d *) &object->location, target_object->polygon, (world_point2d *) &target_object->location))) { repetitions= definition->ranged_attack.repetitions; } } } } } } if (repetitions!=NONE) { /* we can attack; set monster facing, start the attack action and reset ticks_since_attack */ object->facing= theta; if (monster->action!=new_action) /* if weÕre already attacking, this is a chained attack */ { switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: case _easy_level: repetitions>>= 1; case _normal_level: repetitions= (repetitions<=1) ? repetitions : repetitions-1; break; } set_monster_action(monster_index, new_action); monster->attack_repetitions= repetitions; } /* on the highest level, hitting a monster in the middle of an attack doesnÕt really stop him from continuing to attack because ticks_since_attack is never reset */ switch (dynamic_world->game_information.difficulty_level) { case _total_carnage_level: break; default: monster->ticks_since_attack= 0; break; } } else { /* we canÕt attack (for whatever reason), halve ticks_since_attack so we try again soon */ monster->ticks_since_attack= 0; if (obstruction_index!=NONE && get_monster_attitude(monster_index, obstruction_index)==_friendly && MONSTER_IS_PLAYER(get_monster_data(obstruction_index))) { play_object_sound(monster->object_index, definition->clear_sound); } } return new_action==NONE ? false : true; } static void execute_monster_attack( short monster_index) { struct monster_data *monster= get_monster_data(monster_index); /* we used to assert that the attacking monster was locked, but monsters can be deactivated or lose lock during an attack (!) so we just abort if we no longer have a valid target */ if (MONSTER_HAS_VALID_TARGET(monster)) { struct monster_definition *definition= get_monster_definition(monster->type); struct object_data *object= get_object_data(monster->object_index); struct attack_definition *attack= (monster->action==_monster_is_attacking_close) ? &definition->melee_attack : &definition->ranged_attack; short projectile_polygon_index; world_point3d origin= object->location; world_point3d _vector; projectile_polygon_index= position_monster_projectile(monster_index, monster->target_index, attack, &origin, (world_point3d *) NULL, &_vector, object->facing); if (projectile_polygon_index != NONE) new_projectile(&origin, projectile_polygon_index, &_vector, attack->error, attack->type, monster_index, monster->type, monster->target_index, FIXED_ONE); if (definition->flags&_monster_fires_symmetrically) { attack->dy= -attack->dy; projectile_polygon_index= position_monster_projectile(monster_index, monster->target_index, attack, &origin, (world_point3d *) NULL, &_vector, object->facing); if (projectile_polygon_index != NONE) new_projectile(&origin, projectile_polygon_index, &_vector, attack->error, attack->type, monster_index, monster->type, monster->target_index, FIXED_ONE); attack->dy= -attack->dy; } } } long monster_pathfinding_cost_function( short source_polygon_index, short line_index, short destination_polygon_index, void *vdata) { struct monster_pathfinding_data *data=(struct monster_pathfinding_data *)vdata; struct monster_definition *definition= data->definition; struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index); struct polygon_data *source_polygon= get_polygon_data(source_polygon_index); struct line_data *line= get_line_data(line_index); bool respect_polygon_heights= true; struct object_data *object; short object_index; long cost; /* base cost is the area of the polygon weÕre leaving */ cost= source_polygon->area; /* no solid lines (baby) */ if (LINE_IS_SOLID(line) && !LINE_IS_VARIABLE_ELEVATION(line)) cost= -1; /* count up the monsters in destination_polygon and add a constant cost, MONSTER_PATHFINDING_OBSTRUCTION_PENALTY, for each of them to discourage overcrowding */ for (object_index= destination_polygon->first_object; object_index!=NONE; object_index= object->next_object) { object= get_object_data(object_index); if (GET_OBJECT_OWNER(object)==_object_is_monster) cost+= MONSTER_PATHFINDING_OBSTRUCTION_COST; } /* if weÕre trying to move into a polygon with an area smaller than MINIMUM_MONSTER_PATHFINDING_POLYGON_AREA, disallow the move */ if (source_polygon->area0) { if (destination_polygon->type==_polygon_is_platform) { switch (monster_can_enter_platform(destination_polygon->permutation, source_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta)) { case _platform_will_never_be_accessable: cost= -1; break; default: cost+= MONSTER_PATHFINDING_PLATFORM_COST; respect_polygon_heights= false; break; } } if (source_polygon->type==_polygon_is_platform) { switch (monster_can_leave_platform(source_polygon->permutation, destination_polygon_index, definition->height, definition->minimum_ledge_delta, definition->maximum_ledge_delta)) { case _exit_will_never_be_accessable: cost= -1; break; default: respect_polygon_heights= false; break; } } } /* if the ledge between polygons is too high, the fall is too far, or there just isnÕt enough vertical space, disallow the move (and ignore this if weÕre dealing with platforms or doors) */ if (respect_polygon_heights) { world_distance delta_height= destination_polygon->floor_height-source_polygon->floor_height; if (delta_heightminimum_ledge_delta||delta_height>definition->maximum_ledge_delta) cost= -1; if (line->lowest_adjacent_ceiling-line->highest_adjacent_floorheight) cost= -1; if (cost>0) cost+= delta_height*delta_height; /* prefer not to change heights */ } /* if this line not wide enough, disallow the move */ if (line->length<2*definition->radius) cost= -1; if (cost>0) { /* if weÕre trying to move into an impassable polygon, disallow the move */ switch (destination_polygon->type) { case _polygon_is_zone_border: if (!data->cross_zone_boundaries) cost= -1; break; case _polygon_is_monster_impassable: case _polygon_is_teleporter: cost= -1; break; } } if (cost>0) { /* if weÕre trying to move into media, pay the penalty */ if (destination_polygon->media_index!=NONE) { struct media_data *media= get_media_data(destination_polygon->media_index); // LP change: idiot-proofed this if (media) { if (media->height>destination_polygon->floor_height) { cost+= 2*destination_polygon->area; } } } } return cost; } /* returns the type and index of any interesting terrain feature (platform or door) in front of the given monster in his current direction; this lets us open doors and wait for platforms. relevant_polygon_index is the polygon_index we have to pass to platform_is_accessable */ static short find_obstructing_terrain_feature( short monster_index, short *feature_index, short *relevant_polygon_index) { struct monster_data *monster= get_monster_data(monster_index); struct monster_definition *definition= get_monster_definition(monster->type); struct object_data *object= get_object_data(monster->object_index); short polygon_index, feature_type; world_point2d p1; ray_to_line_segment((world_point2d *)&object->location, &p1, object->facing, MONSTER_PLATFORM_BUFFER_DISTANCE+definition->radius); feature_type= NONE; *relevant_polygon_index= polygon_index= object->polygon; do { struct polygon_data *polygon= get_polygon_data(polygon_index); short line_index= find_line_crossed_leaving_polygon(polygon_index, (world_point2d *)&object->location, &p1); switch (polygon->type) { case _polygon_is_platform: if (object->polygon==polygon_index) { /* weÕre standing on the platform: find out where weÕre headed (if weÕre going nowhere then pretend like everything is o.k.) */ polygon_index= line_index==NONE ? NONE : find_adjacent_polygon(polygon_index, line_index); if (polygon_index!=NONE) { *relevant_polygon_index= polygon_index; *feature_index= polygon->permutation; feature_type= _leaving_platform_polygon; assert(*feature_index!=NONE); } } else { feature_type= _entering_platform_polygon; *feature_index= polygon->permutation; assert(*feature_index!=NONE); } break; default: if (((definition->flags&_monster_floats) && polygon->floor_height>monster->desired_height) || object->location.z+definition->height>polygon->ceiling_height) { monster->desired_height= polygon->floor_height; feature_type= _flying_or_floating_transition; *feature_index= 0; } if (definition->flags&_monster_flys) { if ((polygon->floor_height>monster->desired_height) || (polygon->ceiling_heightdesired_height+definition->height)) { monster->desired_height= (polygon->floor_height>monster->desired_height) ? polygon->floor_height : (polygon->ceiling_height - definition->height); feature_type= _flying_or_floating_transition; *feature_index= 0; } if (object->location.zfloor_height || object->location.z+definition->height>polygon->ceiling_height) { feature_type= _flying_or_floating_transition; *feature_index= 0; } } if (definition->flags&_monster_uses_sniper_ledges) { if ((polygon->floor_height+MINIMUM_SNIPER_ELEVATIONdesired_height) && monster->mode==_monster_locked) { feature_type= _standing_on_sniper_ledge; } } if (!(definition->flags&(_monster_floats|_monster_flys)) && polygon->media_index!=NONE) { struct media_data *media= get_media_data(polygon->media_index); // Monster will normally wade to half-height world_distance height= definition->height>>1; // LP change: idiot-proofing if (media) { // In dangerous media, wade only to zero height (that is, don't wade at all) if (IsMediaDangerous(media->type)) height= 0; switch (media->type) { case _media_water: if (definition->flags&_monster_is_not_afraid_of_water) media= (struct media_data *) NULL; break; case _media_jjaro: // LP addition: monsters treat Jjaro goo like sewage case _media_sewage: if (definition->flags&_monster_is_not_afraid_of_sewage) media= (struct media_data *) NULL; break; case _media_lava: /* height= 0; */ if (definition->flags&_monster_is_not_afraid_of_lava) media= (struct media_data *) NULL; break; case _media_goo: /* height= 0; */ if (definition->flags&_monster_is_not_afraid_of_goo) media= (struct media_data *) NULL; break; } } if (media && media->height-polygon->floor_height>height) { if (get_polygon_data(object->polygon)->floor_height>polygon->floor_height) { feature_type= _standing_on_sniper_ledge; if (monster->mode!=_monster_locked) monster_needs_path(monster_index, false); } } } polygon_index= line_index==NONE ? NONE : find_adjacent_polygon(polygon_index, line_index); break; } if (line_index!=NONE && polygon_index==NONE) { if (monster->path_segment_lengthpath_segment_length= 0; } else { /* weÕre headed for a wall solid; freeze and get a new path, pronto */ feature_type= _standing_on_sniper_ledge; monster_needs_path(monster_index, true); } } } while (polygon_index!=NONE&&(feature_type==NONE||feature_type==_flying_or_floating_transition)); return feature_type; } /* returns new polygon index; if destination is NULL then we fire along the monsterÕs facing and elevation, if destination is not NULL then we set it correctly and save the elevation angle */ static short position_monster_projectile( short aggressor_index, short target_index, struct attack_definition *attack, world_point3d *origin, world_point3d *destination, world_point3d *_vector, angle theta) { struct monster_data *aggressor= get_monster_data(aggressor_index); struct monster_data *target= get_monster_data(target_index); struct object_data *aggressor_object= get_object_data(aggressor->object_index); struct object_data *target_object= get_object_data(target->object_index); world_distance radius, height; // dprintf("positioning #%d to #%d", aggressor_index, target_index); /* adjust origin */ *origin= aggressor_object->location; origin->z+= attack->dz; translate_point2d((world_point2d *)origin, attack->dy, NORMALIZE_ANGLE(theta+QUARTER_CIRCLE)); translate_point2d((world_point2d *)origin, attack->dx, theta); if (destination) { world_distance distance; /* adjust destination */ get_monster_dimensions(target_index, &radius, &height); *destination= target_object->location; destination->z+= (height>>1) + (height>>2); /* shoot 3/4ths up the target */ /* calculate outbound vector */ _vector->x= destination->x-origin->x; _vector->y= destination->y-origin->y; _vector->z= destination->z-origin->z; distance= isqrt(_vector->x*_vector->x + _vector->y*_vector->y); aggressor->elevation= distance ? (_vector->z*TRIG_MAGNITUDE)/distance : 0; } else { _vector->x= cosine_table[theta]; _vector->y= sine_table[theta]; _vector->z= aggressor->elevation; } /* return polygon_index of the new origin point */ return find_new_object_polygon((world_point2d *)&aggressor_object->location, (world_point2d *)origin, aggressor_object->polygon); } short nearest_goal_polygon_index( short polygon_index) { polygon_index= flood_map(polygon_index, LONG_MAX, nearest_goal_cost_function, _breadth_first, (void *) NULL); while (polygon_index!=NONE) { struct polygon_data *polygon= get_polygon_data(polygon_index); if (polygon->type==_polygon_is_goal) break; polygon_index= flood_map(NONE, LONG_MAX, nearest_goal_cost_function, _breadth_first, (void *) NULL); } return polygon_index; } static long nearest_goal_cost_function( short source_polygon_index, short line_index, short destination_polygon_index, void *unused) { struct polygon_data *destination_polygon= get_polygon_data(destination_polygon_index); // struct polygon_data *source_polygon= get_polygon_data(source_polygon_index); // struct line_data *line= get_line_data(line_index); long cost= 1; (void) (unused); (void) (source_polygon_index); (void) (line_index); if (destination_polygon->type==_polygon_is_zone_border) cost= -1; return cost; } // LP: will set player view attributes when trying to shoot a guided projectile. void SetPlayerViewAttribs(int16 half_visual_arc, int16 half_vertical_visual_arc, world_distance visual_range, world_distance dark_visual_range) { // Added a modified version of AlexJS's changes: change only if necessary // Restoring AlexJLS's changes monster_definition& PlayerAsMonster = monster_definitions[_monster_marine]; if (half_visual_arc > 0 || PlayerAsMonster.half_visual_arc > 0) PlayerAsMonster.half_visual_arc = half_visual_arc; if (half_vertical_visual_arc > 0 || PlayerAsMonster.half_vertical_visual_arc > 0) PlayerAsMonster.half_vertical_visual_arc = half_vertical_visual_arc; if (visual_range > 0 || PlayerAsMonster.visual_range > 0) PlayerAsMonster.visual_range = visual_range; if (dark_visual_range > 0 || PlayerAsMonster.dark_visual_range > 0) PlayerAsMonster.dark_visual_range = dark_visual_range; } uint8 *unpack_monster_data(uint8 *Stream, monster_data *Objects, size_t Count) { uint8* S = Stream; monster_data* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { StreamToValue(S,ObjPtr->type); StreamToValue(S,ObjPtr->vitality); StreamToValue(S,ObjPtr->flags); StreamToValue(S,ObjPtr->path); StreamToValue(S,ObjPtr->path_segment_length); StreamToValue(S,ObjPtr->desired_height); StreamToValue(S,ObjPtr->mode); StreamToValue(S,ObjPtr->action); StreamToValue(S,ObjPtr->target_index); StreamToValue(S,ObjPtr->external_velocity); StreamToValue(S,ObjPtr->vertical_velocity); StreamToValue(S,ObjPtr->ticks_since_attack); StreamToValue(S,ObjPtr->attack_repetitions); StreamToValue(S,ObjPtr->changes_until_lock_lost); StreamToValue(S,ObjPtr->elevation); StreamToValue(S,ObjPtr->object_index); StreamToValue(S,ObjPtr->ticks_since_last_activation); StreamToValue(S,ObjPtr->activation_bias); StreamToValue(S,ObjPtr->goal_polygon_index); StreamToValue(S,ObjPtr->sound_location.x); StreamToValue(S,ObjPtr->sound_location.y); StreamToValue(S,ObjPtr->sound_location.z); StreamToValue(S,ObjPtr->sound_polygon_index); StreamToValue(S,ObjPtr->random_desired_height); S += 7*2; } assert((S - Stream) == static_cast(Count*SIZEOF_monster_data)); return S; } uint8 *pack_monster_data(uint8 *Stream, monster_data *Objects, size_t Count) { uint8* S = Stream; monster_data* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { ValueToStream(S,ObjPtr->type); ValueToStream(S,ObjPtr->vitality); ValueToStream(S,ObjPtr->flags); ValueToStream(S,ObjPtr->path); ValueToStream(S,ObjPtr->path_segment_length); ValueToStream(S,ObjPtr->desired_height); ValueToStream(S,ObjPtr->mode); ValueToStream(S,ObjPtr->action); ValueToStream(S,ObjPtr->target_index); ValueToStream(S,ObjPtr->external_velocity); ValueToStream(S,ObjPtr->vertical_velocity); ValueToStream(S,ObjPtr->ticks_since_attack); ValueToStream(S,ObjPtr->attack_repetitions); ValueToStream(S,ObjPtr->changes_until_lock_lost); ValueToStream(S,ObjPtr->elevation); ValueToStream(S,ObjPtr->object_index); ValueToStream(S,ObjPtr->ticks_since_last_activation); ValueToStream(S,ObjPtr->activation_bias); ValueToStream(S,ObjPtr->goal_polygon_index); ValueToStream(S,ObjPtr->sound_location.x); ValueToStream(S,ObjPtr->sound_location.y); ValueToStream(S,ObjPtr->sound_location.z); ValueToStream(S,ObjPtr->sound_polygon_index); ValueToStream(S,ObjPtr->random_desired_height); S += 7*2; } assert((S - Stream) == static_cast(Count*SIZEOF_monster_data)); return S; } inline void StreamToAttackDef(uint8* &S, attack_definition& Object) { StreamToValue(S,Object.type); StreamToValue(S,Object.repetitions); StreamToValue(S,Object.error); StreamToValue(S,Object.range); StreamToValue(S,Object.attack_shape); StreamToValue(S,Object.dx); StreamToValue(S,Object.dy); StreamToValue(S,Object.dz); } inline void AttackDefToStream(uint8* &S, attack_definition& Object) { ValueToStream(S,Object.type); ValueToStream(S,Object.repetitions); ValueToStream(S,Object.error); ValueToStream(S,Object.range); ValueToStream(S,Object.attack_shape); ValueToStream(S,Object.dx); ValueToStream(S,Object.dy); ValueToStream(S,Object.dz); } uint8 *unpack_monster_definition(uint8 *Stream, size_t Count) { return unpack_monster_definition(Stream,monster_definitions,Count); } uint8 *unpack_monster_definition(uint8 *Stream, monster_definition* Objects, size_t Count) { uint8* S = Stream; monster_definition* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { StreamToValue(S,ObjPtr->collection); StreamToValue(S,ObjPtr->vitality); StreamToValue(S,ObjPtr->immunities); StreamToValue(S,ObjPtr->weaknesses); StreamToValue(S,ObjPtr->flags); StreamToValue(S,ObjPtr->_class); StreamToValue(S,ObjPtr->friends); StreamToValue(S,ObjPtr->enemies); StreamToValue(S,ObjPtr->sound_pitch); StreamToValue(S,ObjPtr->activation_sound); StreamToValue(S,ObjPtr->friendly_activation_sound); StreamToValue(S,ObjPtr->clear_sound); StreamToValue(S,ObjPtr->kill_sound); StreamToValue(S,ObjPtr->apology_sound); StreamToValue(S,ObjPtr->friendly_fire_sound); StreamToValue(S,ObjPtr->flaming_sound); StreamToValue(S,ObjPtr->random_sound); StreamToValue(S,ObjPtr->random_sound_mask); StreamToValue(S,ObjPtr->carrying_item_type); StreamToValue(S,ObjPtr->radius); StreamToValue(S,ObjPtr->height); StreamToValue(S,ObjPtr->preferred_hover_height); StreamToValue(S,ObjPtr->minimum_ledge_delta); StreamToValue(S,ObjPtr->maximum_ledge_delta); StreamToValue(S,ObjPtr->external_velocity_scale); StreamToValue(S,ObjPtr->impact_effect); StreamToValue(S,ObjPtr->melee_impact_effect); StreamToValue(S,ObjPtr->contrail_effect); StreamToValue(S,ObjPtr->half_visual_arc); StreamToValue(S,ObjPtr->half_vertical_visual_arc); StreamToValue(S,ObjPtr->visual_range); StreamToValue(S,ObjPtr->dark_visual_range); StreamToValue(S,ObjPtr->intelligence); StreamToValue(S,ObjPtr->speed); StreamToValue(S,ObjPtr->gravity); StreamToValue(S,ObjPtr->terminal_velocity); StreamToValue(S,ObjPtr->door_retry_mask); StreamToValue(S,ObjPtr->shrapnel_radius); S = unpack_damage_definition(S,&ObjPtr->shrapnel_damage,1); StreamToValue(S,ObjPtr->hit_shapes); StreamToValue(S,ObjPtr->hard_dying_shape); StreamToValue(S,ObjPtr->soft_dying_shape); StreamToValue(S,ObjPtr->hard_dead_shapes); StreamToValue(S,ObjPtr->soft_dead_shapes); StreamToValue(S,ObjPtr->stationary_shape); StreamToValue(S,ObjPtr->moving_shape); StreamToValue(S,ObjPtr->teleport_in_shape); StreamToValue(S,ObjPtr->teleport_out_shape); StreamToValue(S,ObjPtr->attack_frequency); StreamToAttackDef(S,ObjPtr->melee_attack); StreamToAttackDef(S,ObjPtr->ranged_attack); } assert((S - Stream) == static_cast(Count*SIZEOF_monster_definition)); return S; } uint8 *pack_monster_definition(uint8 *Stream, size_t Count) { return pack_monster_definition(Stream,monster_definitions,Count); } uint8 *pack_monster_definition(uint8 *Stream, monster_definition *Objects, size_t Count) { uint8* S = Stream; monster_definition* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { ValueToStream(S,ObjPtr->collection); ValueToStream(S,ObjPtr->vitality); ValueToStream(S,ObjPtr->immunities); ValueToStream(S,ObjPtr->weaknesses); ValueToStream(S,ObjPtr->flags); ValueToStream(S,ObjPtr->_class); ValueToStream(S,ObjPtr->friends); ValueToStream(S,ObjPtr->enemies); ValueToStream(S,ObjPtr->sound_pitch); ValueToStream(S,ObjPtr->activation_sound); ValueToStream(S,ObjPtr->friendly_activation_sound); ValueToStream(S,ObjPtr->clear_sound); ValueToStream(S,ObjPtr->kill_sound); ValueToStream(S,ObjPtr->apology_sound); ValueToStream(S,ObjPtr->friendly_fire_sound); ValueToStream(S,ObjPtr->flaming_sound); ValueToStream(S,ObjPtr->random_sound); ValueToStream(S,ObjPtr->random_sound_mask); ValueToStream(S,ObjPtr->carrying_item_type); ValueToStream(S,ObjPtr->radius); ValueToStream(S,ObjPtr->height); ValueToStream(S,ObjPtr->preferred_hover_height); ValueToStream(S,ObjPtr->minimum_ledge_delta); ValueToStream(S,ObjPtr->maximum_ledge_delta); ValueToStream(S,ObjPtr->external_velocity_scale); ValueToStream(S,ObjPtr->impact_effect); ValueToStream(S,ObjPtr->melee_impact_effect); ValueToStream(S,ObjPtr->contrail_effect); ValueToStream(S,ObjPtr->half_visual_arc); ValueToStream(S,ObjPtr->half_vertical_visual_arc); ValueToStream(S,ObjPtr->visual_range); ValueToStream(S,ObjPtr->dark_visual_range); ValueToStream(S,ObjPtr->intelligence); ValueToStream(S,ObjPtr->speed); ValueToStream(S,ObjPtr->gravity); ValueToStream(S,ObjPtr->terminal_velocity); ValueToStream(S,ObjPtr->door_retry_mask); ValueToStream(S,ObjPtr->shrapnel_radius); S = pack_damage_definition(S,&ObjPtr->shrapnel_damage,1); ValueToStream(S,ObjPtr->hit_shapes); ValueToStream(S,ObjPtr->hard_dying_shape); ValueToStream(S,ObjPtr->soft_dying_shape); ValueToStream(S,ObjPtr->hard_dead_shapes); ValueToStream(S,ObjPtr->soft_dead_shapes); ValueToStream(S,ObjPtr->stationary_shape); ValueToStream(S,ObjPtr->moving_shape); ValueToStream(S,ObjPtr->teleport_in_shape); ValueToStream(S,ObjPtr->teleport_out_shape); ValueToStream(S,ObjPtr->attack_frequency); AttackDefToStream(S,ObjPtr->melee_attack); AttackDefToStream(S,ObjPtr->ranged_attack); } assert((S - Stream) == static_cast(Count*SIZEOF_monster_definition)); return S; } void init_monster_definitions() { memcpy(monster_definitions, original_monster_definitions, sizeof(monster_definitions)); } struct damage_kick_definition *original_damage_kick_definitions = NULL; class XML_DamageKickParser: public XML_ElementParser { short Index; damage_kick_definition Data; // What is present? bool IndexPresent; enum {NumberOfValues = 3}; bool IsPresent[NumberOfValues]; public: bool Start(); bool HandleAttribute(const char *Tag, const char *Value); bool AttributesDone(); bool ResetValues(); XML_DamageKickParser(): XML_ElementParser("kick") {} }; bool XML_DamageKickParser::Start() { // back up old values first if (!original_damage_kick_definitions) { original_damage_kick_definitions = (struct damage_kick_definition *) malloc(sizeof(struct damage_kick_definition) * NUMBER_OF_DAMAGE_TYPES); assert(original_damage_kick_definitions); for (unsigned i = 0; i < NUMBER_OF_DAMAGE_TYPES; i++) original_damage_kick_definitions[i] = damage_kick_definitions[i]; } IndexPresent = false; for (int k=0; k