/* * Descent 3 * Copyright (C) 2024 Parallax Software * * 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // AIGame3.cpp // 0.1 #include #include #include #include "osiris_import.h" #include "osiris_common.h" #include "osiris_vector.h" #include "DallasFuncs.h" #include "AIGame3_External.h" #include "module.h" #ifdef __cplusplus extern "C" { #endif DLLEXPORT char STDCALL InitializeDLL(tOSIRISModuleInit *func_list); DLLEXPORT void STDCALL ShutdownDLL(void); DLLEXPORT int STDCALL GetGOScriptID(const char *name, uint8_t isdoor); DLLEXPORT void STDCALLPTR CreateInstance(int id); DLLEXPORT void STDCALL DestroyInstance(int id, void *ptr); DLLEXPORT int16_t STDCALL CallInstanceEvent(int id, void *ptr, int event, tOSIRISEventInfo *data); DLLEXPORT int STDCALL SaveRestoreState(void *file_ptr, uint8_t saving_state); #ifdef __cplusplus } #endif static int String_table_size = 0; static char **String_table = NULL; static const char *_Error_string = "!!ERROR MISSING STRING!!"; static const char *_Empty_string = ""; const char *GetStringFromTable(int index) { if ((index < 0) || (index >= String_table_size)) return _Error_string; if (!String_table[index]) return _Empty_string; return String_table[index]; } #define TXT(x) GetStringFromTable(x) //------------------- // Function prototypes //------------------- static int TurnOnSpew(int objref, int gunpoint, int effect_type, float mass, float drag, int gravity_type, uint8_t isreal, float lifetime, float interval, float longevity, float size, float speed, uint8_t random); // Returns the new child's handle static int CreateAndAttach(int me, const char *child_name, uint8_t child_type, char parent_ap, char child_ap, bool f_aligned = true, bool f_set_parent = false); static int FindClosestPlayer(int objhandle); // Returns true if object current has an active Dallas low priority goal static bool HasLowPriorityGoal(int obj_handle); // Returns true if object current has an active Dallas high priority goal static bool HasHighPriorityGoal(int obj_handle); // Wipes out all goals except slots 0 and 3 (used by Dallas) static void SafeGoalClearAll(int obj_handle); static void AI_SafeSetType(int obj_handle, int ai_type); //---------------- // Name lookups //---------------- // Name lookup globals static uint16_t energy_effect_id; // weapon ID for the energy charge effect static uint16_t frag_burst_effect_id; // weapon ID for the frag burst effect static uint16_t boss_frag_burst_id; // weapon ID for the boss frag burst effect static uint16_t transfer_effect_id; // texture ID for the energy transfer lightning effect static uint16_t heal_effect_id; // texture ID for the heal lightning effect static uint16_t boss_heal_effect_id; // texture ID for the boss heal lightning effect static uint16_t tractor_beam_effect_id; // texture ID for the tractor beam effect static uint16_t alien_organism_id; // object type ID for the alien organism robot static uint16_t shield_blast_id; // weapon ID for the HT shield blast effect static uint16_t ht_grenade_id; // weapon ID for the HT grenade static uint16_t ht_grenade_effect_id; // weapon ID for the HT grenade launch effect static uint16_t lifter_blast_effect_id; // weapon ID for the lifter blast effect static uint16_t lifter_stick_effect_id; // texture ID for lifter's night-stick lightning effect static uint16_t teleport_effect_id; // weapon ID for teleporting effect static uint16_t ht_grenade_sound_id; // sound ID for firing the grenade static uint16_t powerup_id; // invisible powerup id static uint16_t boss_flapping_id; // flapping sound id static uint16_t boss_turf_id; // turf id static uint16_t boss_see_id; static uint16_t boss_hurt_id; static uint16_t lifter_pull_sound_id; static uint16_t lifter_amb_sound_id; // ========================== // AI Goal Related Functions // ========================== #define DALLAS_LOW_PRIORITY_GOAL_SLOT 0 #define DALLAS_HIGH_PRIORITY_GOAL_SLOT 3 // Returns true if object current has an active Dallas low priority goal bool HasLowPriorityGoal(int obj_handle) { bool used; AI_GoalValue(obj_handle, DALLAS_LOW_PRIORITY_GOAL_SLOT, VF_GET, AIGV_B_USED, &used); return used; } // Returns true if object current has an active Dallas high priority goal bool HasHighPriorityGoal(int obj_handle) { bool used; AI_GoalValue(obj_handle, DALLAS_HIGH_PRIORITY_GOAL_SLOT, VF_GET, AIGV_B_USED, &used); return used; } // Returns True if given index implies that a goal is finished static inline bool IsGoalFinishedNotify(int index) { return (index == AIN_GOAL_COMPLETE || index == AIN_GOAL_INVALID || index == AIN_GOAL_FAIL || index == AIN_GOAL_ERROR); } // Wipes out all goals except slots 0 and 3 (used by Dallas) void SafeGoalClearAll(int obj_handle) { int i; for (i = 0; i < MAX_GOALS; i++) { if (i != DALLAS_LOW_PRIORITY_GOAL_SLOT && i != DALLAS_HIGH_PRIORITY_GOAL_SLOT) { AI_ClearGoal(obj_handle, i); } } } // Works like AI_SetType(), but won't clear out Dallas' goal slots 0 or 3 void AI_SafeSetType(int obj_handle, int ai_type) { float aware = AWARE_BARELY; float time = 25.0f; SafeGoalClearAll(obj_handle); switch (ai_type) { case AIT_STALKER: { vector goal_pos; int goal_room; Obj_Value(obj_handle, VF_GET, OBJV_V_POS, &goal_pos); Obj_Value(obj_handle, VF_GET, OBJV_I_ROOMNUM, &goal_room); AI_AddGoal(obj_handle, AIG_WANDER_AROUND, 1, 1.0f, -1, GF_NONFLUSHABLE | GF_KEEP_AT_COMPLETION, &goal_pos, goal_room); } break; case AIT_EVADER2: { vector goal_pos; int goal_room; float size, circle_distance; Obj_Value(obj_handle, VF_GET, OBJV_V_POS, &goal_pos); Obj_Value(obj_handle, VF_GET, OBJV_I_ROOMNUM, &goal_room); Obj_Value(obj_handle, VF_GET, OBJV_F_SIZE, &size); AI_Value(obj_handle, VF_GET, AIV_F_CIRCLE_DIST, &circle_distance); AI_AddGoal(obj_handle, AIG_GUARD_AREA, 1, 1.0f, -1, GF_NONFLUSHABLE, &goal_pos, goal_room); AI_SetGoalCircleDist(obj_handle, 1, size * 2.0f); AI_GoalAddEnabler(obj_handle, 1, AIE_GT_AWARENESS, 1.0, 0.0, &aware); AI_GoalAddEnabler(obj_handle, 1, AIE_LTE_LAST_SEE_TARGET_INTERVAL, 1.0, 0.0, &time); int gi = AI_AddGoal(obj_handle, AIG_GET_AROUND_OBJ, ACTIVATION_BLEND_LEVEL, 3.0f, -1, GF_NONFLUSHABLE | GF_OBJ_IS_TARGET, OBJECT_HANDLE_NONE); AI_GoalAddEnabler(obj_handle, gi, AIE_NEAR, 1.0, 0.0, &circle_distance); AI_SetGoalCircleDist(obj_handle, gi, circle_distance); } break; case AIT_EVADER1: { float circle_distance; AI_Value(obj_handle, VF_GET, AIV_F_CIRCLE_DIST, &circle_distance); AI_AddGoal(obj_handle, AIG_MOVE_RELATIVE_OBJ, 1, 1.0f, -1, GF_OBJ_IS_TARGET | GF_SPEED_ATTACK | GF_NONFLUSHABLE, OBJECT_HANDLE_NONE); AI_GoalAddEnabler(obj_handle, 1, AIE_GT_AWARENESS, 1.0, 0.0, &aware); AI_GoalAddEnabler(obj_handle, 1, AIE_LTE_LAST_SEE_TARGET_INTERVAL, 1.0, 0.0, &time); if (circle_distance > 0.0f) { int gi = AI_AddGoal(obj_handle, AIG_GET_AROUND_OBJ, ACTIVATION_BLEND_LEVEL, 3.0f, -1, GF_NONFLUSHABLE | GF_OBJ_IS_TARGET, OBJECT_HANDLE_NONE); float temp = circle_distance * 0.55f; AI_GoalAddEnabler(obj_handle, gi, AIE_NEAR, 1.0, 0.0, &temp); AI_SetGoalCircleDist(obj_handle, gi, circle_distance * 0.55f); } } break; case AIT_MELEE1: { int flags = AISR_MELEE; int m_goal; AI_AddGoal(obj_handle, AIG_MOVE_RELATIVE_OBJ, 1, 1.0f, -1, GF_NONFLUSHABLE | GF_OBJ_IS_TARGET | GF_KEEP_AT_COMPLETION, OBJECT_HANDLE_NONE); AI_GoalAddEnabler(obj_handle, 1, AIE_GT_AWARENESS, 1.0, 0.0, &aware); AI_GoalAddEnabler(obj_handle, 1, AIE_LTE_LAST_SEE_TARGET_INTERVAL, 1.0, 0.0, &time); AI_AddGoal(obj_handle, AIG_GET_TO_OBJ, 2, 1.0f, -1, GF_SPEED_ATTACK | GF_NONFLUSHABLE | GF_OBJ_IS_TARGET | GF_USE_BLINE_IF_SEES_GOAL | GF_KEEP_AT_COMPLETION, OBJECT_HANDLE_NONE); AI_GoalAddEnabler(obj_handle, 2, AIE_AI_STATUS_FLAG, 1.0, 0.0, &flags); AI_SetGoalCircleDist(obj_handle, 2, -100.0f); AI_GoalAddEnabler(obj_handle, 2, AIE_GT_AWARENESS, 1.0, 0.0, &aware); AI_GoalAddEnabler(obj_handle, 2, AIE_LTE_LAST_SEE_TARGET_INTERVAL, 1.0, 0.0, &time); m_goal = AI_AddGoal(obj_handle, AIG_MELEE_TARGET, ACTIVATION_BLEND_LEVEL, 1.0f, -1, GF_NONFLUSHABLE | GF_OBJ_IS_TARGET | GF_KEEP_AT_COMPLETION, OBJECT_HANDLE_NONE); AI_GoalAddEnabler(obj_handle, m_goal, AIE_GT_AWARENESS, 1.0, 0.0, &aware); AI_GoalAddEnabler(obj_handle, m_goal, AIE_LTE_LAST_SEE_TARGET_INTERVAL, 1.0, 0.0, &time); } break; case AIT_BIRD_FLOCK1: { } break; case AIT_HERD1: { } break; case AIT_STATIONARY_TURRET: break; case AIT_AIS: break; case AIT_FLYLANDER: break; default: { mprintf(0, "Invalid AI type passed into AI_SafeSetType()\n"); } } } // ============================ // Miscellaneous Utility Funcs // ============================ int TurnOnSpew(int objref, int gunpoint, int effect_type, float mass, float drag, int gravity_type, uint8_t isreal, float lifetime, float interval, float longevity, float size, float speed, uint8_t random) { msafe_struct mstruct; mstruct.objhandle = objref; mstruct.gunpoint = gunpoint; mstruct.effect_type = effect_type; mstruct.mass = mass; mstruct.drag = drag; mstruct.phys_info = gravity_type; mstruct.is_real = isreal; mstruct.lifetime = lifetime; mstruct.interval = interval; mstruct.longevity = longevity; mstruct.size = size; mstruct.speed = speed; mstruct.random = (random) ? SPEW_RAND_SIZE | SPEW_RAND_LIFETIME | SPEW_RAND_SPEED : 0; MSafe_CallFunction(MSAFE_OBJECT_START_SPEW, &mstruct); return mstruct.id; } // Returns the new child's handle int CreateAndAttach(int me, const char *child_name, uint8_t child_type, char parent_ap, char child_ap, bool f_aligned, bool f_set_parent) { int child_handle = OBJECT_HANDLE_NONE; int child_id = Obj_FindID(child_name); msafe_struct m; m.objhandle = me; MSafe_GetValue(MSAFE_OBJECT_POS, &m); MSafe_GetValue(MSAFE_OBJECT_ROOMNUM, &m); if (child_id >= 0) { int parent; if (f_set_parent) parent = me; else parent = OBJECT_HANDLE_NONE; child_handle = Obj_Create(child_type, child_id, m.roomnum, &m.pos, NULL, parent); if (child_handle != OBJECT_HANDLE_NONE) { if (!Obj_AttachObjectAP(me, parent_ap, child_handle, child_ap, f_aligned)) { // chrishack (we need a way to instantly kill scripted objects) } } } return child_handle; } int FindClosestPlayer(int objhandle) { vector objpos, playerpos; float closest_dist = FLT_MAX; int closest_player = OBJECT_HANDLE_NONE; msafe_struct mstruct; // Get the object position mstruct.objhandle = objhandle; MSafe_GetValue(MSAFE_OBJECT_POS, &mstruct); objpos = mstruct.pos; // Loop though all possible players and compute distance for (int p = 0; p < MAX_PLAYERS; p++) { // Get player position mstruct.slot = p; MSafe_GetValue(MSAFE_OBJECT_PLAYER_HANDLE, &mstruct); // See if this player active if (mstruct.objhandle == OBJECT_HANDLE_NONE) continue; // Get the target position MSafe_GetValue(MSAFE_OBJECT_POS, &mstruct); // Get the normalized vector from the source to the target float dist = vm_VectorDistanceQuick(&objpos, &mstruct.pos); if (dist < closest_dist) { closest_dist = dist; closest_player = mstruct.objhandle; } } return closest_player; } // ================== // Clear Shot System // ================== // Clear shot constants #define MAX_SCAN_SHOT_OBJECTS 30 #define MAX_SHOT_PATH_POSITIONS 256 // Reasons why not a clear shot #define CS_ALL_CLEAR 0 #define CS_SHOOTER_IN_WAY 1 #define CS_FRIEND_IN_WAY 2 #define CS_NO_ENEMY 3 #define CS_NO_PATH 4 // Shot data structure struct tShotData { int object_handle; // IN: the object taking the shot vector start_point; // IN: the starting point of the shot int start_room; // IN: the room that the starting point is in vector dir; // IN: the direction the shot is aimed float max_dist; // IN: the maximum distance of the shot float shot_radius; // IN: the radius of the projectile (used for the ray thickness) float danger_radius; // IN: the initial danger radius for the scans float radius_inc; // IN: the danger radius increase multiplier float risk_factor; // IN: the amount of risk a robot is willing to take }; // Shot scan position data structure struct tShotPathPositionData { int num_friends; // Number of friends found at this path position int num_enemies; // Number of enemies found at this path position int num_neutrals; // Number of neutrals found at this path position bool contains_shooter; // Whether or not this path position contains the shooter float path_dist; // The shot path distance of this scan position from the start float scan_radius; // The radius of the scan that was done at this shot position }; // Clear shot globals static tShotPathPositionData ShotPathPositions[MAX_SHOT_PATH_POSITIONS]; static int num_shot_path_positions; static bool ScanShotPathPosition(int obj_handle, vector *pos, int room, float radius, float dist); static float TraverseShotPath(tShotData *shot_data); static int ShotIsClear(float risk_factor); static int HasClearShot(tShotData *shot_data); // Scans area around given shot position for object data bool ScanShotPathPosition(int obj_handle, vector *pos, int room, float radius, float dist) { int scan_objs[MAX_SCAN_SHOT_OBJECTS]; int n_scan; int n, i; int type; // Make sure there is still a shot scan position available if (num_shot_path_positions >= MAX_SHOT_PATH_POSITIONS) { return false; } // Init this shot path position's data n = num_shot_path_positions; ShotPathPositions[n].contains_shooter = false; ShotPathPositions[n].path_dist = dist; ShotPathPositions[n].scan_radius = radius; ShotPathPositions[n].num_enemies = 0; ShotPathPositions[n].num_friends = 0; ShotPathPositions[n].num_neutrals = 0; // Scan for any nearby objects // NOTE: check the func below to see what other params do!!! n_scan = AI_GetNearbyObjs(pos, room, radius, scan_objs, MAX_SCAN_SHOT_OBJECTS, false, true, false, true); // Go through the objects that have been found and store tallies for (i = 0; i < n_scan; i++) { Obj_Value(scan_objs[i], VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_ROBOT || type == OBJ_PLAYER) { // Make sure it's not cloaked if (qObjectCloakTime(scan_objs[i]) == 0.0f) { // Tallie the object accordingly if (scan_objs[i] == obj_handle) { // It's the shooter ShotPathPositions[n].contains_shooter = true; } else if (AI_IsObjEnemy(obj_handle, scan_objs[i])) { // It's an enemy ShotPathPositions[n].num_enemies++; } else if (AI_IsObjFriend(obj_handle, scan_objs[i])) { // It's a friend ShotPathPositions[n].num_friends++; } else { // It's a neutral ShotPathPositions[n].num_neutrals++; } } } } num_shot_path_positions++; return true; } // Traverses the straight-line shot path and gathers object data float TraverseShotPath(tShotData *shot_data) { float next_scan_dist, scan_radius; float current_path_dist, dist; vector start_pos, end_pos, target_pos; vector shot_dir; int start_room, end_room; int flags, fate; ray_info ray; bool done, max_distance_reached; // Clear out any existing shot scan positions num_shot_path_positions = 0; // Setup the shot's starting data start_pos = shot_data->start_point; start_room = shot_data->start_room; shot_dir = shot_data->dir; next_scan_dist = shot_data->danger_radius; scan_radius = shot_data->danger_radius; // Start scanning current_path_dist = 0.0f; max_distance_reached = false; done = false; while (!done) { // Check the max shot distance if ((current_path_dist + next_scan_dist) >= shot_data->max_dist) { next_scan_dist = shot_data->max_dist - current_path_dist; max_distance_reached = true; } // Calculate shot's target position target_pos = start_pos + (shot_dir * next_scan_dist); // Cast a ray to see if a wall or any clutter objects lie in the target path flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(shot_data->object_handle, &start_pos, &target_pos, start_room, shot_data->shot_radius, flags, &ray); // Store the ray hit point and room end_pos = ray.hit_point; end_room = ray.hit_room; // If necessary, calculate the distance from start point to the hit point if (fate == HIT_NONE) { dist = next_scan_dist; } else { dist = vm_VectorDistance(&start_pos, &end_pos); } // Add the new distance to our total shot path distance current_path_dist += dist; // If it hit a wall, change the direction (perfect reflection) if (fate == HIT_WALL) { // Calculate the bounce dir from the current dir and face normal vector new_dir = (-2.0f * (shot_dir * ray.hit_wallnorm)) * ray.hit_wallnorm + shot_dir; shot_dir = new_dir; // Now, if necessary, cast another ray to finish off the scan dist next_scan_dist -= dist; if (next_scan_dist > 0.0f) { start_pos = end_pos; start_room = end_room; continue; } } // Do an object scan at this point if (!ScanShotPathPosition(shot_data->object_handle, &end_pos, end_room, scan_radius, current_path_dist)) { done = true; continue; } // Obj_Create(OBJ_WEAPON, energy_effect_id, end_room, &end_pos, NULL, shot_data->object_handle); // If max distance was reached or we hit a clutter object, we're done if (max_distance_reached || fate == HIT_OBJECT) { done = true; continue; } // Update data for next run next_scan_dist = scan_radius * 2.0f; scan_radius *= shot_data->radius_inc; start_pos = end_pos; start_room = end_room; } return true; } // Analyzes data to determine if shot is clear int ShotIsClear(float risk_factor) { int j; if (num_shot_path_positions == 0) return CS_NO_PATH; // If final shot path position contains shooter, don't shoot! if (ShotPathPositions[num_shot_path_positions - 1].contains_shooter) return CS_SHOOTER_IN_WAY; for (j = 0; j < num_shot_path_positions; j++) { // Check and see if shooter is in the way (ok if it's the first position) if (ShotPathPositions[j].contains_shooter && j != 0) return CS_SHOOTER_IN_WAY; // Check if there are any friendlies or neutrals in the way if ((ShotPathPositions[j].num_friends + ShotPathPositions[j].num_neutrals) > 0) return CS_FRIEND_IN_WAY; // Check if any enemies are in the way if (ShotPathPositions[j].num_enemies > 0 && !ShotPathPositions[j].contains_shooter) return CS_ALL_CLEAR; } return CS_NO_ENEMY; } // Checks to see if object has a clear shot int HasClearShot(tShotData *shot_data) { // Traverse the shot trajectory, gathering data TraverseShotPath(shot_data); // Analyze the shot data return (ShotIsClear(shot_data->risk_factor)); } // ========================== // Script class definitions // ========================== #define NUM_IDS 6 // maximum number of IDs #define ID_ALIENORGANISM 1 // Alien organism robot #define ID_HEAVYTROOPER 2 // CED Heavy Trooper robot #define ID_LIFTER 3 // Lifter robot #define ID_ALIENBOSS 4 // Alien Mini-boss #define ID_SECURITYCAMERA 5 // Security Camera #define ID_CROWDCONTROL 6 // Crowd Control for Merc6 Barges struct tScriptInfo { int id; const char *name; }; static tScriptInfo ScriptInfo[NUM_IDS] = { {ID_ALIENORGANISM, "AlienOrganism"}, {ID_HEAVYTROOPER, "HeavyTrooper"}, {ID_LIFTER, "Lifter"}, {ID_ALIENBOSS, "AlienBoss"}, {ID_SECURITYCAMERA, "SecurityCamera"}, {ID_CROWDCONTROL, "CrowdControl"}}; class BaseObjScript { public: BaseObjScript(); ~BaseObjScript(); virtual int16_t CallEvent(int event, tOSIRISEventInfo *data); }; // Priority Constants #define LOW_PRIORITY 0 #define MEDIUM_PRIORITY 1 #define HIGH_PRIORITY 2 //--------------------- // AlienOrganism class //--------------------- // Alien Mode Types #define ALIEN_LANDED 0 #define ALIEN_LANDED_RESTING 1 #define ALIEN_LANDED_HEALING 2 #define ALIEN_LANDED_DEPOSITING 3 #define ALIEN_LANDED_WITHDRAWING 4 #define ALIEN_LANDING_AT_HOME 10 #define ALIEN_GOING_HOME 11 #define ALIEN_FLEEING 12 #define ALIEN_FINDING_BIRTHPLACE 13 #define ALIEN_GIVING_BIRTH 14 #define ALIEN_HUNTING 20 #define ALIEN_SCAVENGING 21 #define ALIEN_ATTACKING_MELEE 31 #define ALIEN_ATTACKING_RANGED 32 #define ALIEN_MODE_NONE 50 // General Alien Constants #define ALIEN_MEMORY_ID 4 #define ALIEN_TYPE_NAME "AlienOrganism" #define ALIEN_MAX_ENERGY_CHARGES 20.0f #define ALIEN_ENERGY_SUCK_CHARGE 20.0f #define ALIEN_ENERGY_BOLT_COST 2.0f #define ALIEN_HEAL_PERCENTAGE 0.50f #define ALIEN_LANDED_ACTION_TIME 2.0f #define ALIEN_BEAM_UPDATE_TIME 0.5f #define ALIEN_SQUAD_RECRUIT_RADIUS 100.0f #define ALIEN_MAX_TAKEOFF_TIME 0.7f // Alien Lookup Names #define ALIEN_ENERGY_EFFECT_NAME "AlienEnergyEffect" #define ALIEN_TRANSFER_EFFECT_NAME "FunkyEffect1" #define ALIEN_HEAL_EFFECT_NAME "FunkyEffectGreen" // Alien Flee Constants #define ALIEN_ON_FIRE_FLEE_CHANCE 80 #define ALIEN_NEAR_FIRE_FLEE_CHANCE 25 #define ALIEN_SQUADIE_FLEE_SAVING_THROW 5 #define ALIEN_LEADER_FLEE_SAVING_THROW 10 // Alien goal complete IDs #define ALIEN_GUID_GOT_HOME 0x10100001 #define ALIEN_GUID_LANDED 0x10100002 #define ALIEN_GUID_FOUND_BIRTHPLACE 0x10100003 #define ALIEN_GUID_RAN_AWAY 0x10100004 #define ALIEN_GUID_GOT_TO_DEST_OBJ 0x10100005 // Alien Communication IDs #define ALIEN_COM_JOIN_ME 0 #define ALIEN_COM_LEAVE_ME 1 #define ALIEN_COM_PROTECT_ME 2 #define ALIEN_COM_BREAK_AND_ATTACK 3 #define ALIEN_COM_REGROUP 4 #define ALIEN_COM_SET_MODE 5 #define ALIEN_COM_SET_PENDING_MODE 6 #define ALIEN_COM_GET_MODE 7 #define ALIEN_COM_SET_SQUAD_FLAGS 8 #define ALIEN_COM_CLEAR_SQUAD_FLAGS 9 #define ALIEN_COM_REQUEST_LEAVE 10 #define ALIEN_COM_LEAVING_YOU 11 #define ALIEN_COM_GET_GOAL_POS 12 #define ALIEN_COM_GET_GOAL_ROOM 13 #define ALIEN_COM_I_WAS_HIT 14 #define ALIEN_COM_CAN_YOU_SEE_ME 15 #define ALIEN_COM_LEAVE_ME_FOR_MODE 16 // Squad Constants #define ALIEN_MAX_TEAMMATES 2 #define ALIEN_CATCHUP_DIST 4.0f #define ALIEN_APPROACH_DIST 10.0f #define ALIEN_ENGAGE_DIST 200.0f #define ALIEN_BREAKUP_DIST 50.0f #define ALIEN_REGROUP_DIST 120.0f // Alien Squad Flags #define ALIEN_LEADER 0x01 #define ALIEN_SQUADIE 0x02 #define ALIEN_CATCHUP 0x04 #define ALIEN_BROKEN 0x08 // Energy Beam Flags #define ALIEN_BEAM1 0x01 #define ALIEN_BEAM2 0x02 #define ALIEN_BEAM3 0x04 // Auto avoid friends distances #define ALIEN_NORMAL_AVOID_DIST 4.0f #define ALIEN_SQUAD_AVOID_DIST 0.0f // Circle Distance (also for avoiding friends) #define ALIEN_SQUAD_CIRC_DIST_MOD 0.4f // Formation Offset Constants #define ALIEN_FO_HUNT_RVEC 8.0f #define ALIEN_FO_HUNT_FVEC 10.0f #define ALIEN_FO_SCAV_RVEC 3.0f #define ALIEN_FO_SCAV_FVEC 15.0f #define ALIEN_FO_ATTK_RVEC 7.0f #define ALIEN_FO_ATTK_UVECP 7.0f #define ALIEN_FO_ATTK_UVECN 5.0f #define ALIEN_FO_ATTK_FVEC 3.0f // Speed Modifier Constants #define ALIEN_ENGAGE_SPEED_MOD 1.4f #define ALIEN_ATTACK_SPEED_MOD 1.8f #define ALIEN_RETURN_SPEED_MOD 1.0f #define ALIEN_LAND_SPEED_MOD 0.5f #define ALIEN_HUNT_SPEED_MOD 1.0f #define ALIEN_SCAV_SPEED_MOD 0.7f #define ALIEN_FLEE_SPEED_MOD 2.0f #define ALIEN_CATCHUP_SPEED_MOD 0.23f #define ALIEN_SQUADIE_INTERVAL 0.25f // Teammate data struct struct teammate_data { int handle; // the object handle for this teammate bool is_visible; // whether this teammate can see me or not }; // Alien memory data structure struct alienorganism_data { // Mode Data int mode; // indicates what mode alien is currently in int pending_mode; // mode to switch to when current task(s) are completed int previous_mode; // mode alien was in before starting current task(s) float mode_time; // time spent in current mode float next_activity_time; // next time resting alien should think about doing something float max_wander_time; // maximum time to be spent out hunting/scavenging before returning float next_update_squad_time; // the next time to do a squad update float next_update_energy_time; // the next time to update the energy effect float next_update_beam_time; // the next time to update the energy beams float next_generic_check_time; // the next time to do generic checks float next_squadie_update_time; // the next time to update the squadie's catchup speed float next_special_damage_time; // the next time alien can take special damage float next_vis_check_time; // the next time to do a squad visibility check // General Information float base_speed; // the robot's base speed float base_circle_distance; // the robot's base circle distance float base_shields; // the robot's base shields float energy_charges; // the number of energy charges currently stored float ok_to_deposit; // indicates whether alien is allowed to deposit charge float takeoff_time; // time since takeoff (used for clearing axes lock, etc.) int dest_object_handle; // destination handle for object to go to instead of wander // Indicators bool done_turning; bool done_moving; bool hit_by_player; bool energy_beams_on; // Home Data vector home_pos; int home_room; vector home_fvec; vector home_uvec; // Energy effect props int pos1_handle; int pos2_handle; int pos3_handle; int enabled_beams; // Squad Data int squad_flags; int leader_handle; int num_teammates; teammate_data teammate[ALIEN_MAX_TEAMMATES]; vector squad_goal_pos; int squad_goal_room; }; // Alien Organism class definition class AlienOrganism : public BaseObjScript { private: alienorganism_data *memory; void SetMode(int me, char mode); void DoTakeoff(int me, float takeoff_speed, float speed_variance); void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); void DoDamage(int me, tOSIRISEVTDAMAGED *damage_data); void DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data); bool SendCommand(int me, int it, char command, void *ptr); bool ReceiveCommand(int me, int it, char command, void *ptr); bool IssueSquadOrder(int leader_handle, char command, void *ptr); bool IsLandedMode(int mode); bool IsEnergyRelatedLandedMode(int mode); bool ModeIsAttackSensitive(int mode, int pending_mode); bool ModeSupportsSquadEnlistment(int mode, int pending_mode, int recruiter_mode); bool ModeSupportsSquadRecruitment(int mode); bool ModeSupportsSquadMovement(int mode); void DisperseSquad(int me); void DisperseSquadToMode(int me, int mode); bool LeaveLeader(int me); bool Flee(int me, bool force_flee = false); bool Heal(int me); bool MeleeAttack(int me, bool force_attack = false); bool RangedAttack(int me); bool Deposit(int me); bool Withdraw(int me); bool WillJoinLeader(int me, int leader_handle, int priority); int FindTeammateID(int tm_handle); int CalcFormationPosition(int me, int tm_ID, vector *pos); void SetSquadieFormationGoal(int me); void UpdateSquadieFormationGoal(int me); void SetWanderGoal(int me); void RemoveTeammate(int tm_id); bool IsNearFire(int me); bool IsOnFire(int me); bool FindHome(int me); void ProjectBeam(int me, int pos_handle, vector *start_pos, int start_room, vector *beam_dir, matrix *orient, int beam_flag); void CalcEnergyBeamPositions(int me); void UpdateEnergyBeams(int me); void UpdateEnergyEffect(int me); void SetMaxSpeed(int me, float speed); void SetCircleDist(int me, float dist); void UpdateSquadList(int me); void UpdateSquad(int me); void UpdateSquadVisibility(int me); void DoSquadieFrame(int me); public: AlienOrganism() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //--------------------- // Heavy Trooper class //--------------------- // Heavy Trooper Mode Types #define HT_ATTACK_NORMAL 0 #define HT_PREPARING_GRENADE 1 #define HT_FIRING_GRENADE 2 #define HT_RECOVERING 3 #define HT_PREPARING_FOR_CURL 10 #define HT_CURLING_UP 11 #define HT_UNCURLING 12 #define HT_ARMORED_ATTACK 13 #define HT_ARMORED_CIRCLE_BACK 14 // General Heavy Trooper Constants #define HT_MEMORY_ID 4 #define HT_PARTICLE_SPEW_TIME 0.4f #define HT_MAX_PARTICLE_TIME 0.5f #define HT_CURLUP_DIST 10.0f #define HT_CURLUP_DIST_VARIANCE 20.0f #define HT_CURLUP_DAMAGE 0.2f #define HT_GRENADE_DIST 40.0f #define HT_GRENADE_DIST_MAX 130.0f #define HT_RECHARGE_TIME 1.0f #define HT_DECHARGE_TIME 0.8f #define HT_MAX_CHARGE 8.0f #define HT_MAX_CHARGE_VARIANCE 6.0f #define HT_CHARGE_INC 1.0f #define HT_CIRCLE_BACK_TIME 1.0f #define HT_SHIELD_BLAST_DELAY 1.5f #define HT_ARMOR_DAMAGED_MOD 0.02f #define HT_CURLING_DAMAGED_MOD 0.2f #define HT_GRENADE_CHECK_INTERVAL 1.0f #define HT_GRENADE_CHECK_VARIANCE 1.5f #define HT_GRENADE_RELOAD_TIME 3.0f #define HT_GRENADE_GUNPOINT 2 // Heavy Trooper Custom Anim Frames #define HT_CURLUP_START_FRAME 202.0f #define HT_CURLUP_END_FRAME 216.0f #define HT_CURLUP_ANIM_TIME 2.0f #define HT_UNCURL_START_FRAME 146.0f #define HT_UNCURL_END_FRAME 173.0f #define HT_UNCURL_ANIM_TIME 3.5f #define HT_ROLL_START_FRAME 185.0f #define HT_ROLL_END_FRAME 195.0f #define HT_ROLL_ANIM_TIME 0.7f #define HT_GUNFIRE_START_FRAME 95.0f #define HT_GUNFIRE_END_FRAME 105.0f #define HT_GRENADE_START_FRAME 105.0f #define HT_GRENADE_FIRE_FRAME 119.0f #define HT_GRENADE_END_FRAME 126.0f #define HT_GRENADE_FIRE_ANIM_TIME 1.2f #define HT_GRENADE_RECOIL_ANIM_TIME 1.3f #define HT_GRENADE_MISFIRE_ANIM_TIME 1.0f // Heavy Trooper Speed modifiers #define HT_GRENADE_FIRING_SPEED_MOD 0.2f #define HT_GRENADE_AIMING_SPEED_MOD 0.6f #define HT_CURLING_SPEED_MOD 0.8f #define HT_RAMMING_SPEED_MOD 2.5f #define HT_CIRCLE_BACK_SPEED_MOD 2.5f #define HT_RAMMING_DELTA_SPEED_MOD 3.0f #define HT_RAMMING_TURN_RATE_MOD 1.1f #define HT_RAMMING_DELTA_TURN_RATE_MOD 3.0f // Heavy Trooper ID's #define HT_SHIELD_BLAST_NAME "HTShieldBlast" #define HT_GRENADE_NAME "Impact Mortar" #define HT_GRENADE_EFFECT_NAME "AlienEnergyEffect" // Heavy Trooper memory data structure struct heavytrooper_data { // Mode Data int mode; // indicates what mode trooper is currently in float mode_time; // time spent in current mode float base_shields; // original shields float base_speed; // original max speed float base_delta_speed; // original max delta speed float base_turn_rate; // original turn rate float base_delta_turn_rate; // original delta turn rate float charge; // stores energy used by curl-up mode float curlup_dist; // stores this robot's max curlup dist float max_charge; // stores this robot's max charge float last_grenade_time; // last time a grenade was fired float next_grenade_check_time; // next time to check if we have a clear grenade shot float next_charge_time; // next time to re-charge or deplete charge float next_blast_time; // next time a shield collision blast can be created float next_particle_time; // next time to create a casing particle spew }; // Heavy Trooper class definition class HeavyTrooper : public BaseObjScript { private: heavytrooper_data *memory; void SetMode(int me, char mode); void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); void DoDamage(int me, tOSIRISEVTDAMAGED *damage_data); void DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data); void LaunchGrenade(int me); bool HasAClearGrenadeShot(int me, bool use_grenade_gunpoint = false); void SetMaxSpeed(int me, float speed, bool accel_faster = false); void EnableGunAttack(int me, bool enable = true); public: HeavyTrooper() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //--------------------- // Lifter class //--------------------- // Lifter Mode Types #define LIFTER_PASSIVE 0 #define LIFTER_AGITATED 1 #define LIFTER_HOSTILE 2 #define LIFTER_PULLING 3 #define LIFTER_PASSIFYING 4 // General Lifter Constants #define LIFTER_MEMORY_ID 4 #define LIFTER_MAX_PULL_TIME 8.0f #define LIFTER_PULL_CHECK_INTERVAL 0.5f #define LIFTER_PULL_VALIDATE_INTERVAL 0.25f #define LIFTER_PULL_TARGET_INTERVAL 0.15f #define LIFTER_NEXT_PULL_DELAY 3.0f #define LIFTER_NEXT_PULL_VARIANCE 5.0f #define LIFTER_MAX_PULLING_DIST 250.0f #define LIFTER_MIN_PULLING_DIST 20.0f #define LIFTER_MIN_HOSTILE_MODE_TIME 8.0f #define LIFTER_MIN_AGITATED_MODE_TIME 2.0f #define LIFTER_AGITATED_INTEREST_TIME 8.0f #define LIFTER_HOSTILE_INTEREST_TIME 18.0f #define LIFTER_AGITATED_SHIELD_PERCENT 0.95f #define LIFTER_HOSTILE_SHIELD_PERCENT 0.85f #define LIFTER_LOSE_PULL_SHIELD_PERCENT 0.20f #define LIFTER_TARGET_PULL_SPEED 35.0f #define LIFTER_LIFT_BEAM_UPDATE_TIME 0.25f // Lifter speed constants #define LIFTER_AGITATED_SPEED_MOD 1.2f #define LIFTER_HOSTILE_SPEED_MOD 2.0f #define LIFTER_PULLING_SPEED_MOD 0.6f // Lifter memory data structure struct lifter_data { // Mode Data int mode; // indicates what mode lifter is currently in float mode_time; // time spent in current mode float base_shields; // original shields float base_speed; // original max speed float shield_loss_margin; // amount of shields that must be lost before pull is lost float shields_at_pull_start; // shield total at start of pull int pull_target; // the target being pulled int pull_target_prop; // invisible powerup target pos int pull_source_prop; // invisible powerup source pos bool not_hostile_yet; // whether or not it's been in hostile mode yet float last_damaged_time; // last time we were damaged float next_pull_check_time; // the next time we can check if a pull can be done float last_target_pull_time; // the last time the target was pulled float next_lift_beam_time; // the next time to update the lift beam }; // Lifter class definition class Lifter : public BaseObjScript { private: lifter_data *memory; void SetMode(int me, char mode); void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); void DoDamage(int me, tOSIRISEVTDAMAGED *damage_data); void DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data); void DoCleanUp(int me); void SetMaxSpeed(int me, float speed); bool OkToPull(int me, bool initial_check = true); void PullTarget(int me); void UpdateTractorBeam(int me); void ConfineTarget(int me); void ReleaseTarget(int me); void StopPulling(int me); void MoveTargetProp(int target_handle); void SwitchToAlert(int me); void UpdateLiftBeam(int me); public: Lifter() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //--------------------- // AlienBoss class //--------------------- // Alien Boss Mode Types #define AB_WANDERING 0 #define AB_HUNTING 1 #define AB_ATTACKING 2 #define AB_PREPARE_SPECIAL_ATTACK 3 #define AB_SPECIAL_ATTACK 4 #define AB_SPECIAL_ATTACK_RECOIL 5 #define AB_MELEE_ATTACK 6 #define AB_GOING_TO_NEST 10 #define AB_LANDING_AT_NEST 11 #define AB_HEALING 12 #define AB_FLEEING 13 #define AB_HIDING_FROM_THRUSTER 20 #define AB_GOING_TO_PROTECT_NEST 21 #define AB_DYING 22 #define AB_WAITING 23 #define AB_TELEPORTING 24 // Alien Boss State Types #define AB_NORMAL 0 #define AB_PROTECTIVE 1 #define AB_ANGRY 2 // General Alien Boss Constants #define AB_MEMORY_ID 4 #define AB_HEALING_ACTION_TIME 1.0f #define AB_HEAL_AMOUNT_PERCENT 0.05f #define AB_BEAM_UPDATE_TIME 0.25f #define AB_NORMAL_DAMAGE_LEVEL 0.25f #define AB_PROTECTIVE_DAMAGE_LEVEL 0.4f #define AB_ANGRY_DAMAGE_LEVEL 0.6f #define AB_MAX_FLEE_TIME 10.0f #define AB_STARTING_FLEE_CHANCE 90 #define AB_FLEE_CHANCE_DECREMENT 10 #define AB_ATTACK_CHECK_INTERVAL 0.5f #define AB_DAMAGE_EFFECT_INTERVAL 0.5f #define AB_MAX_DAMAGE_SPARK_RATE 20.0f #define AB_MAX_SPECIAL_ATTACK_DIST 70.0f #define AB_MAX_STING_DIST 40.0f #define AB_STING_DAMAGE 20.0f #define AB_SPECIAL_ATTACK_DELAY 6.0f #define AB_SPECIAL_ATTACK_VARIANCE 14.0f #define AB_MELEE_ATTACK_DELAY 5.0f #define AB_MELEE_ATTACK_VARIANCE 5.0f #define AB_MELEE_DIST 40.0f #define AB_MAX_MELEE_TIME 5.0f #define AB_CHECK_IF_STUCK_INTERVAL 10.0f #define AB_CHECK_IF_STUCK_DIST 10.0f // Alien Boss goal complete IDs #define AB_GUID_GOT_TO_NEST 0x10100001 #define AB_GUID_LANDED 0x10100002 #define AB_GUID_GOT_TO_DEST_OBJ 0x10100003 #define AB_GUID_GOT_TO_WANDER_ROOM 0x10100004 #define AB_GUID_GOT_TO_NEST_ROOM 0x10100005 #define AB_GUID_GOT_TO_HIDE_ROOM 0x10100006 // Alien Boss Custom Anim Frames #define AB_HEALING_START_FRAME 1.0f #define AB_HEALING_END_FRAME 10.0f #define AB_HEALING_ANIM_TIME 2.0f #define AB_LANDING_START_FRAME 50.0f #define AB_LANDING_END_FRAME 53.0f #define AB_LANDING_ANIM_TIME 1.0f #define AB_TAKEOFF_START_FRAME 10.0f #define AB_TAKEOFF_END_FRAME 25.0f #define AB_TAKEOFF_ANIM_TIME 1.0f #define AB_ATTACK_START_FRAME 36.0f #define AB_ATTACK_END_FRAME 45.0f #define AB_ATTACK_ANIM_TIME 0.5f #define AB_RECOIL_START_FRAME 45.0f #define AB_RECOIL_END_FRAME 50.0f #define AB_RECOIL_ANIM_TIME 0.5f // Speed Modifier Constants #define AB_WANDER_SPEED_MOD 1.0f #define AB_ATTACK_SPEED_MOD 0.9f #define AB_MELEE_ATTACK_SPEED_MOD 1.1f #define AB_SPEC_ATTACK_SPEED_MOD 0.3f #define AB_RETURN_SPEED_MOD 1.2f #define AB_LAND_SPEED_MOD 0.4f #define AB_FLEE_SPEED_MOD 1.0f #define AB_GUARD_SPEED_MOD 0.9f #define AB_HIDE_SPEED_MOD 1.0f #define AB_DIE_SPEED_MOD 1.0f // Alien Boss Lookup Globals #define AB_NUM_WANDER_ROOMS 11 static const char *const AB_WanderRoomNames[AB_NUM_WANDER_ROOMS] = { "BossRoomA", "BossTunnelAB", "BossRoomB", "BossTunnelBC", "BossRoomC", "BossTunnelCE", "BossRoomD", "BossTunnelAE", "BossRoomE", "BossTunnelCE", "BossRoomD"}; // Alien Boss memory data structure struct alienboss_data { // Mode Data int mode; // indicates what mode alien boss is currently in int state; // indicates the boss's current state float mode_time; // time spent in current mode float next_activity_time; // next time to do something float max_wander_time; // maximum time to be spent wandering float next_update_beam_time; // the next time to update the energy beams float next_generic_check_time; // the next time to do generic checks float next_special_damage_time; // the next time alien boss can take special damage float next_special_attack_time; // the next time boss can do a special attack float next_melee_attack_time; // the next time boss can do a melee attack float next_damage_effect_time; // the next time to do the damage effect float intro_end_time; // the time intro ends // General Information float base_speed; // the robot's base speed float base_shields; // the robot's base shields int dest_object_handle; // destination handle for object to go to instead of wander float damage; // the amount of damage currently sustained float damage_threshold; // the damage that can be sustained before returning to heal int fire_flee_chance; // the chance to flee when caught on fire int wander_rooms[AB_NUM_WANDER_ROOMS]; // the rooms alien wanders through int curr_wander_room_index; // the room currently wandering to bool wander_forward; // whether to follow list normally or in reverse bool waiting_effect_enabled; // whether waiting effect should be done yet bool intro_ended; // whether intro is over yet // Indicators bool done_turning; bool done_moving; bool hit_by_player; bool protected_nest; // Home Data vector home_pos; int home_room; vector home_fvec; vector home_uvec; // Energy effect props int pos1_handle; int pos2_handle; int pos3_handle; int tail_pos_handle; // Scenario objects int nest_handle; // Hiding from thruster rooms int left_hide_room; int right_hide_room; // Teleport values vector dest_pos; int dest_room; int mode_prior_teleporting; bool did_transfer; // Getting Stuck Prevention float squared_dist_moved; float next_check_if_stuck_time; vector last_pos; }; // Alien Boss class definition class AlienBoss : public BaseObjScript { private: alienboss_data *memory; void SetMode(int me, char mode); void DoTakeoff(int me, float takeoff_speed, float speed_variance); void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); void DoDamage(int me, tOSIRISEVTDAMAGED *damage_data); void DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data); void DoCleanUp(int me); bool SendCommand(int me, int it, char command, void *ptr); bool ReceiveCommand(int me, int it, char command, void *ptr); bool IsOnFire(int me); void SetMaxSpeed(int me, float speed); void SetWanderGoal(int me); void SetWanderRoomGoal(int me); int DetermineClosestRoom(int me); void SetProtectNestGoal(int me); void SetHideRoomGoal(int me); bool OkToStartSpecialAttack(int me); bool DoStingAttack(int me); void AbortCurrentMode(int me); void UpdateHealingEnergyBeams(int me); void UpdateDamageEffect(int me); void StartHealing(int me); void StopHealing(int me); bool ModeIsStuckSensitive(int mode); void DoCustomLookups(void); public: AlienBoss() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //--------------------- // Security Camera class //--------------------- // Security Camera Mode Types #define SC_PANNING 0 #define SC_TRACKING 1 // Security Camera Panning types #define SC_LOOPING 0 #define SC_WARMING_UP 1 // General Security Camera Constants #define SC_MEMORY_ID 4 #define SC_TRACK_TIME_TO_ALERT 3.0f #define SC_TIME_TO_LOSE_TARGET 1.0f #define SC_TIME_TO_GAIN_TARGET 1.0f #define SC_PAN_SPEED 26.0f // frames per second #define SC_TRACK_SPEED 52.0f // frames per second #define SC_START_FRAME 0.0f #define SC_MID_FRAME 90.0f #define SC_END_FRAME 180.0f #define SC_MAX_FRAME 360.0f // end frame to max frame are reverse of start to end #define SC_MAX_FRAME_OFFSET 90.0f #define SC_FRAME_DELTA_ERROR 1.5f #define SC_TRACK_INTERVAL 0.25f // Security camera memory data structure struct securitycamera_data { // Mode Data int mode; // indicates what mode security camera is currently in float mode_time; // time spent in current mode bool alerted; // whether camera is on alert int panning_state; // whether it is looping or warming up float next_activity_time; // the next time to do tracking float last_update_anim_time; // the last time tracking was done float curr_track_anim_frame; // the current tracking animation frame }; // Security Camera class definition class SecurityCamera : public BaseObjScript { private: securitycamera_data *memory; void SetMode(int me, char mode); void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); void DoDamage(int me, tOSIRISEVTDAMAGED *damage_data); void DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data); bool SendCommand(int me, int it, char command, void *ptr); bool ReceiveCommand(int me, int it, char command, void *ptr); public: SecurityCamera() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //--------------------- // CrowdControl Class //--------------------- // General Crowd Control Constants #define CC_MEMORY_ID 4 #define CC_CHECK_INTERVAL 0.2f #define CC_MIN_STOPPING_SPEED 0.0000001f // we want to preserve the direction of velocity // Crowd Control memory data structure struct crowdcontrol_data { int follow_handle; // handle of object to not crowd float stop_dist; // dist at which to stop completely float slowdown_offset; // dist at which to start slowing down (added to stop dist) bool disable_check; // allows slowdown check to be turned on/off dynamically float base_speed; // the base speed of the object float next_check_time; // the next time to do closeness check }; // Security Camera class definition class CrowdControl : public BaseObjScript { private: crowdcontrol_data *memory; void DoInit(int me_handle); void DoFrame(int me_handle); bool DoNotify(int me, tOSIRISEventInfo *data); bool SendCommand(int me, int it, char command, void *ptr); bool ReceiveCommand(int me, int it, char command, void *ptr); public: CrowdControl() {} int16_t CallEvent(int event, tOSIRISEventInfo *data); }; //----------------------- // Name lookup functions //----------------------- // Looks up any names and stores the returned ID's void DoNameLookups(void) { energy_effect_id = Wpn_FindID(ALIEN_ENERGY_EFFECT_NAME); frag_burst_effect_id = Wpn_FindID("FragBurstEffect"); boss_frag_burst_id = Wpn_FindID("AlienBossFragBurst"); transfer_effect_id = Scrpt_FindTextureName(ALIEN_TRANSFER_EFFECT_NAME); heal_effect_id = Scrpt_FindTextureName(ALIEN_HEAL_EFFECT_NAME); boss_heal_effect_id = Scrpt_FindTextureName("FunkyEffect4"); tractor_beam_effect_id = Scrpt_FindTextureName("FunkyEffectGreen"); alien_organism_id = Obj_FindID(ALIEN_TYPE_NAME); shield_blast_id = Wpn_FindID(HT_SHIELD_BLAST_NAME); ht_grenade_id = Wpn_FindID(HT_GRENADE_NAME); ht_grenade_effect_id = Wpn_FindID(HT_GRENADE_EFFECT_NAME); lifter_blast_effect_id = Wpn_FindID("LifterHitBlast"); lifter_stick_effect_id = Scrpt_FindTextureName("FunkyEffect2"); teleport_effect_id = Wpn_FindID("TeleportEffect"); ht_grenade_sound_id = Sound_FindId("Drop weapon"); powerup_id = Obj_FindID("Invisiblepowerup"); boss_flapping_id = Sound_FindId("Merc4BossBuzz"); boss_turf_id = Sound_FindId("Merc4BossTurf"); boss_see_id = Sound_FindId("Merc4BossSee"); boss_hurt_id = Sound_FindId("Merc4BossHurt"); lifter_pull_sound_id = Sound_FindId("RbtLifterBeam"); lifter_amb_sound_id = Sound_FindId("RbtLifterAmb1"); } //---------------- // Standard stuff //---------------- // InitializeDLL // Purpose: // This function gets called when the DLL first gets loaded. It will only be called once (until the // DLL is unloaded). Passed in is a struct of data passed from the game needed for the DLL to interact // with D3. Usually this function will just call osicommon_Initialize(), which sets up the imported // functions. However, you can alloc some memory or whatever in this function, and free it in ShutdownDLL(). // Note: You cannot call any imported functions until osicommon_Initialize() is called. // Returns 1 if initialization went ok, 0 if there was an error and the DLL should not be loaded. char STDCALL InitializeDLL(tOSIRISModuleInit *func_list) { osicommon_Initialize((tOSIRISModuleInit *)func_list); String_table_size = func_list->string_count; String_table = func_list->string_table; // Do name lookups DoNameLookups(); InitMathTables(); if (func_list->game_checksum != CHECKSUM) { mprintf(0, "Game-Checksum FAIL!!! (%ul!=%ul)\n", func_list->game_checksum, CHECKSUM); mprintf(0, "RECOMPILE YOUR SCRIPTS!!!\n"); return 0; } return 1; } // ShutdownDLL // Purpose: // This function is called right before a DLL is about to be unloaded from memory. You can free // any unfree'd memory, or anything else you need to do. Don't worry about destroying any instances // of scripts, as they will all be automatically destroyed before this function is called. The // same goes for any memory allocated with Scrpt_MemAlloc(), as this will automatically be freed // when a scripts instance is destroyed. void STDCALL ShutdownDLL(void) {} // GetGOScriptID // Purpose: // Given the name of the object (from its pagename), this function will search through its // list of General Object Scripts for a script with a matching name (to see if there is a script // for that type/id of object within this DLL). If a matching scriptname is found, a UNIQUE ID // is to be returned back to Descent 3. This ID will be used from here on out for all future // interaction with the DLL. Since doors are not part of the generic object's, it's possible // for a door to have the same name as a generic object (OBJ_POWERUP, OBJ_BUILDING, OBJ_CLUTTER // or OBJ_ROBOT), therefore, a 1 is passed in for isdoor if the given object name refers to a // door, else it is a 0. The return value is the unique identifier, else -1 if the script // does not exist in the DLL. int STDCALL GetGOScriptID(const char *name, uint8_t isdoor) { for (int i = 0; i < NUM_IDS; i++) { if (!stricmp(name, ScriptInfo[i].name)) { return ScriptInfo[i].id; } } return -1; } // CreateInstance // Purpose: // Given an ID from a call to GetGOScriptID(), this function will create a new instance for that // particular script (by allocating and initializing memory, etc.). A pointer to this instance // is to be returned back to Descent 3. This pointer will be passed around, along with the ID // for CallInstanceEvent() and DestroyInstance(). Return NULL if there was an error. void STDCALLPTR CreateInstance(int id) { switch (id) { case ID_ALIENORGANISM: return new AlienOrganism; break; case ID_HEAVYTROOPER: return new HeavyTrooper; break; case ID_LIFTER: return new Lifter; break; case ID_ALIENBOSS: return new AlienBoss; break; case ID_SECURITYCAMERA: return new SecurityCamera; break; case ID_CROWDCONTROL: return new CrowdControl; break; default: mprintf(0, "SCRIPT: Illegal ID (%d)\n", id); break; }; return NULL; } // DestroyInstance // Purpose: // Given an ID, and a pointer to a particular instance of a script, this function will delete and // destruct all information associated with that script, so it will no longer exist. void STDCALL DestroyInstance(int id, void *ptr) { switch (id) { case ID_ALIENORGANISM: delete ((AlienOrganism *)ptr); break; case ID_HEAVYTROOPER: delete ((HeavyTrooper *)ptr); break; case ID_LIFTER: delete ((Lifter *)ptr); break; case ID_ALIENBOSS: delete ((AlienBoss *)ptr); break; case ID_SECURITYCAMERA: delete ((SecurityCamera *)ptr); break; case ID_CROWDCONTROL: delete ((CrowdControl *)ptr); break; default: mprintf(0, "SCRIPT: Illegal ID (%d)\n", id); break; } } // CallInstanceEvent // Purpose: // Given an ID, a pointer to a script instance, an event and a pointer to the struct of // information about the event, this function will translate who this event belongs to and // passes the event to that instance of the script to be handled. Return a combination of // CONTINUE_CHAIN and CONTINUE_DEFAULT, to give instructions on what to do based on the // event. CONTINUE_CHAIN means to continue through the chain of scripts (custom script, level // script, mission script, and finally default script). If CONTINUE_CHAIN is not specified, // than the chain is broken and those scripts of lower priority will never get the event. Return // CONTINUE_DEFAULT in order to tell D3 if you want process the normal action that is built into // the game for that event. This only pertains to certain events. If the chain continues // after this script, than the CONTINUE_DEFAULT setting will be overridden by lower priority // scripts return value. int16_t STDCALL CallInstanceEvent(int id, void *ptr, int event, tOSIRISEventInfo *data) { return ((BaseObjScript *)ptr)->CallEvent(event, data); } // SaveRestoreState // Purpose: // This function is called when Descent 3 is saving or restoring the game state. In this function // you should save/restore any global data that you want preserved through load/save (which includes // demos). You must be very careful with this function, corrupting the file (reading or writing too // much or too little) may be hazardous to the game (possibly making it impossible to restore the // state). It would be best to use version information to keep older versions of saved states still // able to be used. IT IS VERY IMPORTANT WHEN SAVING THE STATE TO RETURN THE NUMBER OF _BYTES_ WROTE // TO THE FILE. When restoring the data, the return value is ignored. saving_state is 1 when you should // write data to the file_ptr, 0 when you should read in the data. int STDCALL SaveRestoreState(void *file_ptr, uint8_t saving_state) { return 0; } //============================================ // Script Implementation //============================================ BaseObjScript::BaseObjScript() {} BaseObjScript::~BaseObjScript() {} int16_t BaseObjScript::CallEvent(int event, tOSIRISEventInfo *data) { return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //--------------------- // AlienOrganism class //--------------------- // Sends a command out to another alien bool AlienOrganism::SendCommand(int me, int it, char command, void *ptr) { bot_com com; com.action = command; com.ptr = ptr; tOSIRISEventInfo ei; ei.me_handle = it; ei.extra_info = (void *)&com; ei.evt_ai_notify.notify_type = AIN_USER_DEFINED; ei.evt_ai_notify.it_handle = me; return Obj_CallEvent(it, EVT_AI_NOTIFY, &ei); } // Processes a command from another alien bool AlienOrganism::ReceiveCommand(int me, int it, char command, void *ptr) { switch (command) { // Leader is asking alien to join squad case ALIEN_COM_JOIN_ME: if (WillJoinLeader(me, it, *(int *)ptr)) { return true; } break; // Leader is telling squadie to leave the squad case ALIEN_COM_LEAVE_ME: memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; SetMode(me, memory->mode); return true; break; // Leader is telling squadie to leave the squad for the given mode case ALIEN_COM_LEAVE_ME_FOR_MODE: memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; SetMode(me, *(int *)ptr); return true; break; case ALIEN_COM_PROTECT_ME: break; // Leader is telling squadie to break formation and attack case ALIEN_COM_BREAK_AND_ATTACK: memory->squad_flags |= ALIEN_BROKEN; SetMode(me, memory->mode); return true; break; // Leader is telling squadie to resume formation case ALIEN_COM_REGROUP: memory->squad_flags &= ~ALIEN_BROKEN; SetMode(me, memory->mode); return true; break; // Leader is telling squadie to switch to given mode case ALIEN_COM_SET_MODE: SetMode(me, *(int *)ptr); return true; break; // Leader is telling squadie to set pending mode case ALIEN_COM_SET_PENDING_MODE: memory->pending_mode = *(int *)ptr; return true; break; // Alien is asked to return current mode case ALIEN_COM_GET_MODE: *(int *)ptr = memory->mode; return true; break; // Alien is told to set the given squad flags case ALIEN_COM_SET_SQUAD_FLAGS: { int flags = *(int *)ptr; memory->squad_flags |= flags; return true; } break; // Alien is told to clear the given squad flags case ALIEN_COM_CLEAR_SQUAD_FLAGS: { int flags = *(int *)ptr; memory->squad_flags &= ~flags; return true; } break; // Squadie is asking leader if it can leave case ALIEN_COM_REQUEST_LEAVE: break; // Squadie is telling leader that he's leaving case ALIEN_COM_LEAVING_YOU: { int tm_id = FindTeammateID(it); if (tm_id >= 0) { RemoveTeammate(tm_id); if (memory->num_teammates == 0) { memory->squad_flags = 0; SetMode(me, memory->mode); } return true; } } break; // Squadie is asking for its formation position case ALIEN_COM_GET_GOAL_POS: { int tm_id = FindTeammateID(it); if (tm_id >= 0) { CalcFormationPosition(me, tm_id, (vector *)ptr); return true; } } break; // Squadie is asking for its formation goal room case ALIEN_COM_GET_GOAL_ROOM: { Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, (int *)ptr); return true; } break; // I'm being informed that a squadmate has been hit case ALIEN_COM_I_WAS_HIT: { // mprintf(0,"I was hit message received!\n"); if (MeleeAttack(me)) { return true; } } break; // I'm being asked if I can see a squadie or not case ALIEN_COM_CAN_YOU_SEE_ME: { int tm_id = FindTeammateID(it); if (tm_id >= 0) { *(bool *)ptr = memory->teammate[tm_id].is_visible; return true; } } break; // I'm being told to go hunt to a certain object case ALIEN_COM_HUNT_TO_OBJ: { // Make sure we're in a mode that is ok to switch from if (memory->mode != ALIEN_LANDED_RESTING) return false; // If we're a squadie, forward this to the leader and let him take care of it if (memory->squad_flags & ALIEN_SQUADIE) { return SendCommand(me, memory->leader_handle, ALIEN_COM_HUNT_TO_OBJ, ptr); } // Otherwise, set the destination object memory->dest_object_handle = *(int *)ptr; // Switch to hunting mode SetMode(me, ALIEN_HUNTING); // If leading, tell the squad to do the same if (memory->squad_flags & ALIEN_LEADER) { int mode = ALIEN_HUNTING; IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } return true; } break; // I'm being told to go hunt to a certain object case ALIEN_COM_SCAV_TO_OBJ: { // Make sure we're in a mode that is ok to switch from if (memory->mode != ALIEN_LANDED_RESTING) return false; // If we're a squadie, forward this to the leader and let him take care of it if (memory->squad_flags & ALIEN_SQUADIE) { return SendCommand(me, memory->leader_handle, ALIEN_COM_SCAV_TO_OBJ, ptr); } // Otherwise, set the destination object memory->dest_object_handle = *(int *)ptr; // Switch to hunting mode SetMode(me, ALIEN_SCAVENGING); // If leading, tell the squad to do the same if (memory->squad_flags & ALIEN_LEADER) { int mode = ALIEN_SCAVENGING; IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } return true; } break; } // mprintf(0, "AlienOrganism action %d failed\n"); return false; } // Sends a command (rhetorical) to all of a leader's teammates bool AlienOrganism::IssueSquadOrder(int leader_handle, char command, void *ptr) { for (int j = 0; j < memory->num_teammates; j++) SendCommand(leader_handle, memory->teammate[j].handle, command, ptr); return true; } // Returns true if mode is one of the landed modes bool AlienOrganism::IsLandedMode(int mode) { if (mode == ALIEN_LANDED || mode == ALIEN_LANDED_RESTING || mode == ALIEN_LANDED_HEALING || mode == ALIEN_LANDED_DEPOSITING || mode == ALIEN_LANDED_WITHDRAWING) return true; return false; } // Returns true if mode is one of the energy related landed modes bool AlienOrganism::IsEnergyRelatedLandedMode(int mode) { if (mode == ALIEN_LANDED_HEALING || mode == ALIEN_LANDED_DEPOSITING || mode == ALIEN_LANDED_WITHDRAWING) return true; return false; } // Returns true if current mode warrants fighting back when hit by player bool AlienOrganism::ModeIsAttackSensitive(int mode, int pending_mode) { // Make sure we're not already in an attack mode if (mode != ALIEN_ATTACKING_MELEE && mode != ALIEN_ATTACKING_RANGED) { // Make sure we're not healing, giving birth, or fleeing if (mode != ALIEN_FLEEING && mode != ALIEN_LANDING_AT_HOME && mode != ALIEN_LANDED_HEALING && (mode != ALIEN_LANDED_DEPOSITING || rand() % 100 < 2) && (mode != ALIEN_LANDED_WITHDRAWING || rand() % 100 < 5) && pending_mode != ALIEN_LANDED_HEALING && pending_mode != ALIEN_LANDED_DEPOSITING && pending_mode != ALIEN_LANDED_WITHDRAWING && mode != ALIEN_FINDING_BIRTHPLACE && mode != ALIEN_GIVING_BIRTH) { return true; } } return false; } // These are the modes from which a leader may attempt to recruit squadies bool AlienOrganism::ModeSupportsSquadRecruitment(int mode) { if (mode == ALIEN_LANDED_RESTING || mode == ALIEN_HUNTING || mode == ALIEN_SCAVENGING || mode == ALIEN_ATTACKING_MELEE) return true; return false; } // These are the modes from which an alien may enlist in a squad bool AlienOrganism::ModeSupportsSquadEnlistment(int mode, int pending_mode, int recruiter_mode) { // If recruiter is landed resting, only join if we are too if (recruiter_mode == ALIEN_LANDED_RESTING && mode != ALIEN_LANDED_RESTING) return false; if (mode == ALIEN_LANDED_RESTING || (mode == ALIEN_GOING_HOME && pending_mode == ALIEN_LANDED_RESTING) || mode == ALIEN_HUNTING || mode == ALIEN_SCAVENGING || mode == ALIEN_ATTACKING_MELEE) return true; return false; } // These are the modes in which a squadie may follow formation movement bool AlienOrganism::ModeSupportsSquadMovement(int mode) { if (mode == ALIEN_HUNTING || mode == ALIEN_SCAVENGING || mode == ALIEN_ATTACKING_MELEE) return true; return false; } // Tells all current teammates that they are on their own again void AlienOrganism::DisperseSquad(int me) { if (!(memory->squad_flags & ALIEN_LEADER)) return; for (int i = 0; i < memory->num_teammates; i++) { SendCommand(me, memory->teammate[i].handle, ALIEN_COM_LEAVE_ME, NULL); } memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; } // Tells all current teammates that they are to leave the squad and enter the given mode void AlienOrganism::DisperseSquadToMode(int me, int mode) { if (!(memory->squad_flags & ALIEN_LEADER)) return; for (int i = 0; i < memory->num_teammates; i++) { SendCommand(me, memory->teammate[i].handle, ALIEN_COM_LEAVE_ME_FOR_MODE, &mode); } memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; } bool AlienOrganism::LeaveLeader(int me) { if (!(memory->squad_flags & ALIEN_SQUADIE)) return true; SendCommand(me, memory->leader_handle, ALIEN_COM_LEAVING_YOU, NULL); memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; return true; } // Attempts to make an alien flee (returns true if it does, false otherwise) bool AlienOrganism::Flee(int me, bool force_flee /*=false*/) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { if (!force_flee) { // do squadie saving throw if ((rand() % 100) < ALIEN_SQUADIE_FLEE_SAVING_THROW) return false; } LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { if (!force_flee) { // do leader saving throw if ((rand() % 100) < ALIEN_LEADER_FLEE_SAVING_THROW) return false; } DisperseSquad(me); } // Ok, let's flee SetMode(me, ALIEN_FLEEING); // mprintf(0,"Alien is fleeing!\n"); return true; } // Attempts to make an alien attack (and his squad if leading one) bool AlienOrganism::MeleeAttack(int me, bool force_attack /*=false*/) { // If leading squad, tell squad to switch to melee mode if (memory->squad_flags & ALIEN_LEADER) { int mode = ALIEN_ATTACKING_MELEE; IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } // Attack target! SetMode(me, ALIEN_ATTACKING_MELEE); // mprintf(0,"Alien is switching to Melee Attack mode!\n"); return true; } // Makes an alien go into ranged attack mode bool AlienOrganism::RangedAttack(int me) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { DisperseSquad(me); } SetMode(me, ALIEN_ATTACKING_RANGED); // mprintf(0,"Alien is switching to ranged attack mode!\n"); return true; } // Makes an alien go into ranged attack mode bool AlienOrganism::Heal(int me) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { DisperseSquad(me); } // mprintf(0,"Heading home to heal...\n"); // Ok, let's go home and heal memory->pending_mode = ALIEN_LANDED_HEALING; SetMode(me, ALIEN_GOING_HOME); return true; } // Makes an alien go into ranged attack mode bool AlienOrganism::Deposit(int me) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { DisperseSquad(me); } // mprintf(0,"Alien is going home to deposit...\n"); memory->pending_mode = ALIEN_LANDED_DEPOSITING; SetMode(me, ALIEN_GOING_HOME); return true; } // Makes an alien go into ranged attack mode bool AlienOrganism::Withdraw(int me) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { DisperseSquad(me); } // mprintf(0,"Alien is going home to withdraw...\n"); memory->pending_mode = ALIEN_LANDED_WITHDRAWING; SetMode(me, ALIEN_GOING_HOME); return true; } // Checks to see if we can join the leader bool AlienOrganism::WillJoinLeader(int me, int leader_handle, int priority) { // Get the recruiter's current mode int recruiter_mode; SendCommand(me, leader_handle, ALIEN_COM_GET_MODE, &recruiter_mode); // If can't join a squad in current mode, decline offer if (!ModeSupportsSquadEnlistment(memory->mode, memory->pending_mode, recruiter_mode)) return false; // If not currently in a squad, possibly join up if (memory->squad_flags == 0) { // Only join 60% of the time (to help prevent mass join slowdowns) if (rand() % 100 > 60) return false; memory->leader_handle = leader_handle; memory->squad_flags |= ALIEN_SQUADIE; return true; } return false; } // Returns the Teammate ID for a given handle (returns -1 if not on team) int AlienOrganism::FindTeammateID(int tm_handle) { for (int i = 0; i < memory->num_teammates; i++) { if (memory->teammate[i].handle == tm_handle) return i; } return -1; } // Calculates a squad position for given teammate based on leader's mode int AlienOrganism::CalcFormationPosition(int me, int tm_ID, vector *pos) { vector me_pos, goal_pos; matrix orient; Obj_Value(me, VF_GET, OBJV_V_POS, &me_pos); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); goal_pos = me_pos; switch (memory->mode) { // When hunting, form a horizontal diamond case ALIEN_HUNTING: { switch (tm_ID) { case 0: goal_pos -= orient.fvec * ALIEN_FO_HUNT_FVEC; goal_pos += orient.rvec * ALIEN_FO_HUNT_RVEC; break; case 1: goal_pos -= orient.fvec * ALIEN_FO_HUNT_FVEC; goal_pos -= orient.rvec * ALIEN_FO_HUNT_RVEC; break; case 2: goal_pos -= orient.fvec * ALIEN_FO_HUNT_FVEC * 2.0f; break; } } break; // When scavenging, form a scavenge line case ALIEN_SCAVENGING: { switch (tm_ID) { case 0: goal_pos -= orient.fvec * ALIEN_FO_SCAV_FVEC; goal_pos -= orient.rvec * ALIEN_FO_SCAV_RVEC; break; case 1: goal_pos -= orient.fvec * ALIEN_FO_SCAV_FVEC * 2.0f; goal_pos += orient.rvec * ALIEN_FO_SCAV_RVEC; break; case 2: goal_pos -= orient.fvec * ALIEN_FO_SCAV_FVEC * 3.0f; break; } } break; // When attacking, form a vertical triangle case ALIEN_ATTACKING_MELEE: case ALIEN_ATTACKING_RANGED: { switch (tm_ID) { case 0: goal_pos += orient.fvec * ALIEN_FO_ATTK_FVEC; goal_pos -= orient.uvec * ALIEN_FO_ATTK_UVECN; goal_pos += orient.rvec * ALIEN_FO_ATTK_RVEC; break; case 1: goal_pos += orient.fvec * ALIEN_FO_ATTK_FVEC; goal_pos -= orient.uvec * ALIEN_FO_ATTK_UVECN; goal_pos -= orient.rvec * ALIEN_FO_ATTK_RVEC; break; case 2: goal_pos += orient.fvec * ALIEN_FO_ATTK_FVEC; goal_pos += orient.uvec * ALIEN_FO_ATTK_UVECP; break; } } break; } // Return the goal position (*pos) = goal_pos; return true; } void AlienOrganism::SetSquadieFormationGoal(int me) { float cd = .1f; float temp = 0.0f; // Do the get to position goal for when squadie can't see leader SendCommand(me, memory->leader_handle, ALIEN_COM_GET_GOAL_POS, &memory->squad_goal_pos); AI_GoalValue(me, 1, VF_SET, AIGV_V_POS, &memory->squad_goal_pos); SendCommand(me, memory->leader_handle, ALIEN_COM_GET_GOAL_ROOM, &memory->squad_goal_room); AI_GoalValue(me, 1, VF_SET, AIGV_I_ROOMNUM, &memory->squad_goal_room); AI_AddGoal(me, AIG_GET_TO_POS, 1, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, &memory->squad_goal_pos, memory->squad_goal_room); AI_GoalValue(me, 1, VF_SET, AIGV_F_CIRCLE_DIST, &cd); // Do the custom goal for when squadie can see leader (similar to use bline when sees goal) AI_AddGoal(me, AIG_SCRIPTED, 2, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, NULL); AI_GoalAddEnabler(me, 2, AIE_SCRIPTED, 1.0, 0.0, &temp); } void AlienOrganism::UpdateSquadieFormationGoal(int me) { // Update the squadie positions SendCommand(me, memory->leader_handle, ALIEN_COM_GET_GOAL_POS, &memory->squad_goal_pos); AI_GoalValue(me, 1, VF_SET, AIGV_V_POS, &memory->squad_goal_pos); SendCommand(me, memory->leader_handle, ALIEN_COM_GET_GOAL_ROOM, &memory->squad_goal_room); AI_GoalValue(me, 1, VF_SET, AIGV_I_ROOMNUM, &memory->squad_goal_room); } void AlienOrganism::SetWanderGoal(int me) { vector pos; int room = 0; float dist = 15.0f; // If the destination object is valid, set the goals to go to that object if (memory->dest_object_handle != OBJECT_HANDLE_NONE) { int type; Obj_Value(memory->dest_object_handle, VF_GET, OBJV_I_TYPE, &type); if (type != OBJ_NONE) { AI_AddGoal(me, AIG_GET_TO_OBJ, 2, 1.0f, ALIEN_GUID_GOT_TO_DEST_OBJ, GF_ORIENT_VELOCITY | GF_USE_BLINE_IF_SEES_GOAL | GF_KEEP_AT_COMPLETION | GF_NONFLUSHABLE, memory->dest_object_handle); AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); return; } memory->dest_object_handle = OBJECT_HANDLE_NONE; } // Set the goal to wander AI_AddGoal(me, AIG_WANDER_AROUND, 2, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, room, room); AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } // Removes a teammate from the squad void AlienOrganism::RemoveTeammate(int tm_id) { for (int j = tm_id; j < memory->num_teammates - 1; j++) { memory->teammate[j] = memory->teammate[j + 1]; } memory->num_teammates--; } // Called by a leader to update his squad list (removes any that have died) void AlienOrganism::UpdateSquadList(int me) { if (!(memory->squad_flags & ALIEN_LEADER)) return; for (int i = 0; i < memory->num_teammates; i++) { int type; Obj_Value(memory->teammate[i].handle, VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_NONE) { RemoveTeammate(i); i--; } } } // Updates the squad (recruitment, etc.) void AlienOrganism::UpdateSquad(int me) { // If me's a squadie, get outta here if (memory->squad_flags & ALIEN_SQUADIE) return; // Remove any dead teammates if (memory->squad_flags & ALIEN_LEADER) { UpdateSquadList(me); if (memory->num_teammates == 0) memory->squad_flags = 0; if (memory->num_teammates >= ALIEN_MAX_TEAMMATES) return; } // If can't recruit in current mode get outta here if (!ModeSupportsSquadRecruitment(memory->mode)) return; // Try to recruit some teammates int scan_objs[25]; int n_scan; int room; vector pos; int i; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); n_scan = AI_GetNearbyObjs(&pos, room, ALIEN_SQUAD_RECRUIT_RADIUS, scan_objs, 25, false, true, false, true); for (i = 0; i < n_scan; i++) { uint16_t id; Obj_Value(scan_objs[i], VF_GET, OBJV_US_ID, &id); // this is more rare than the types matching; so, do it first if (id == alien_organism_id) { int type; Obj_Value(scan_objs[i], VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_ROBOT && scan_objs[i] != me) { // Make sure this guy isn't already a squadmate bool already_on_team = false; if (memory->squad_flags & ALIEN_LEADER) { if (FindTeammateID(scan_objs[i]) >= 0) already_on_team = true; } if (!already_on_team) { // Try and recruit him int priority = MEDIUM_PRIORITY; if (SendCommand(me, scan_objs[i], ALIEN_COM_JOIN_ME, &priority)) { // If not currently a leader, it's time to become one if (!(memory->squad_flags & ALIEN_LEADER)) { memory->squad_flags = ALIEN_LEADER; memory->num_teammates = 0; // Reset the current mode to take squad grouping into account SetMode(me, memory->mode); } memory->teammate[memory->num_teammates++].handle = scan_objs[i]; // Tell squadie whether he should be in formation or not int sflags = ALIEN_BROKEN; if (memory->squad_flags & ALIEN_BROKEN) SendCommand(me, scan_objs[i], ALIEN_COM_SET_SQUAD_FLAGS, &sflags); else SendCommand(me, scan_objs[i], ALIEN_COM_CLEAR_SQUAD_FLAGS, &sflags); // Tell squadie to set his mode to the leader's mode int mode = memory->mode; SendCommand(me, scan_objs[i], ALIEN_COM_SET_MODE, &mode); } } } } // If squad is full get outta here if (memory->num_teammates >= ALIEN_MAX_TEAMMATES) return; } } // Checks which of my boys I can see void AlienOrganism::UpdateSquadVisibility(int me) { int i, flags, fate; vector start_pos, end_pos; int start_room; ray_info ray; // First, remove any dead squadies UpdateSquadList(me); // Get my room and position Obj_Value(me, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &start_room); // Do the visibility check for each teammate for (i = 0; i < memory->num_teammates; i++) { // Get teammate's position Obj_Value(memory->teammate[i].handle, VF_GET, OBJV_V_POS, &end_pos); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &start_pos, &end_pos, start_room, 1.0f, flags, &ray); // Set whether or not this guy is visible memory->teammate[i].is_visible = (fate == HIT_NONE) ? true : false; } } // Checks to see if any fire blobs are nearby bool AlienOrganism::IsNearFire(int me) { return false; } // Checks to see if alien is currently on fire bool AlienOrganism::IsOnFire(int me) { return (Obj_IsEffect(me, EF_NAPALMED)); } #define ALIEN_MAX_BEAM_DIST 30.0f #define ALIEN_ATTEMPT_BEAM_DIST 40.0f void AlienOrganism::ProjectBeam(int me, int pos_handle, vector *start_pos, int start_room, vector *beam_dir, matrix *orient, int beam_flag) { ray_info ray; int flags, fate; vector end_pos, landing_pos; int landing_room; float dist; // Calculate beam's destination end_pos = (*start_pos) + ((*beam_dir) * ALIEN_ATTEMPT_BEAM_DIST); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, start_pos, &end_pos, start_room, 0.0f, flags, &ray); landing_pos = ray.hit_point; landing_room = ray.hit_room; // If beam is within certain distance, move the destination object to destination position dist = vm_VectorDistance(start_pos, &landing_pos); if (dist < ALIEN_MAX_BEAM_DIST) { msafe_struct mstruct; mstruct.objhandle = pos_handle; mstruct.pos = landing_pos; mstruct.orient = (*orient); mstruct.roomnum = landing_room; MSafe_CallFunction(MSAFE_OBJECT_WORLD_POSITION, &mstruct); // Mark this beam as enabled memory->enabled_beams |= beam_flag; } } // Calculates positions and moves invisible powerups to them void AlienOrganism::CalcEnergyBeamPositions(int me) { int start_room; vector beam1_dir, beam2_dir, beam3_dir; vector start_pos; matrix orient; // Get our object's pos and orientation Obj_Value(me, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &start_room); // If we're in a beam mode, get the appropriate beam directions if (memory->mode == ALIEN_LANDED_DEPOSITING) { beam1_dir = -0.40f * orient.uvec - orient.fvec; // beam1_dir += orient.rvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam1_dir); beam2_dir = -0.40f * orient.uvec + orient.fvec - orient.rvec; // beam2_dir += orient.fvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam2_dir); beam3_dir = -0.40f * orient.uvec + orient.fvec + orient.rvec; // beam3_dir += orient.fvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam3_dir); } else if (memory->mode == ALIEN_LANDED_WITHDRAWING) { beam1_dir = -0.3f * orient.uvec + orient.fvec; // beam1_dir += orient.rvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam1_dir); beam2_dir = -0.3f * orient.uvec - orient.fvec - orient.rvec; // beam2_dir -= orient.fvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam2_dir); beam3_dir = -0.3f * orient.uvec - orient.fvec + orient.rvec; // beam3_dir -= orient.fvec*(0.5f-((float)rand()/(float)D3_RAND_MAX)*1.0f); vm_VectorNormalize(&beam3_dir); } else if (memory->mode == ALIEN_LANDED_HEALING) { beam2_dir = -0.4f * orient.uvec + orient.rvec * 2.0f; vm_VectorNormalize(&beam2_dir); beam3_dir = -0.4f * orient.uvec - orient.rvec * 2.0f; vm_VectorNormalize(&beam3_dir); } else { return; } // All beams off initially memory->enabled_beams = 0; // Project the three beams if (memory->mode != ALIEN_LANDED_HEALING) { ProjectBeam(me, memory->pos1_handle, &start_pos, start_room, &beam1_dir, &orient, ALIEN_BEAM1); } ProjectBeam(me, memory->pos2_handle, &start_pos, start_room, &beam2_dir, &orient, ALIEN_BEAM2); ProjectBeam(me, memory->pos3_handle, &start_pos, start_room, &beam3_dir, &orient, ALIEN_BEAM3); // Set flag so that this calculation is only done once memory->energy_beams_on = true; } // Creates the different lightning beam effects void AlienOrganism::UpdateEnergyBeams(int me) { int b1_obj1, b1_obj2; int b2_obj1, b2_obj2; int b3_obj1, b3_obj2; int b1_texture_id; int b2_texture_id; int b3_texture_id; if (!memory->energy_beams_on) return; // See if it's time to create the next energy effect if (memory->next_update_beam_time <= Game_GetTime()) { float next_duration = ALIEN_BEAM_UPDATE_TIME /*+ ((float)rand()/(float)D3_RAND_MAX)*0.2f*/; memory->next_update_beam_time = Game_GetTime() + next_duration; if (memory->mode == ALIEN_LANDED_DEPOSITING) { b1_obj1 = memory->pos1_handle; b1_obj2 = me; b1_texture_id = transfer_effect_id; b2_obj1 = memory->pos2_handle; b2_obj2 = me; b2_texture_id = transfer_effect_id; b3_obj1 = memory->pos3_handle; b3_obj2 = me; b3_texture_id = transfer_effect_id; } else if (memory->mode == ALIEN_LANDED_WITHDRAWING) { b1_obj1 = me; b1_obj2 = memory->pos1_handle; b1_texture_id = transfer_effect_id; b2_obj1 = me; b2_obj2 = memory->pos2_handle; b2_texture_id = transfer_effect_id; b3_obj1 = me; b3_obj2 = memory->pos3_handle; b3_texture_id = transfer_effect_id; } else if (memory->mode == ALIEN_LANDED_HEALING) { b1_obj1 = memory->pos1_handle; b1_obj2 = me; b1_texture_id = heal_effect_id; b2_obj1 = me; b2_obj2 = memory->pos2_handle; b2_texture_id = heal_effect_id; b3_obj1 = me; b3_obj2 = memory->pos3_handle; b3_texture_id = heal_effect_id; } else { mprintf(0, "Invalid energy beam effect mode!\n"); return; } // Now create the beams if (memory->enabled_beams & ALIEN_BEAM1) { aLightningCreate(b1_obj1, b1_obj2, next_duration, 1.0f, 2, b1_texture_id, next_duration, 1, 255, 255, 255, false); } if (memory->enabled_beams & ALIEN_BEAM2) { aLightningCreate(b2_obj1, b2_obj2, next_duration, 1.0f, 2, b2_texture_id, next_duration, 1, 255, 255, 255, false); } if (memory->enabled_beams & ALIEN_BEAM3) { aLightningCreate(b3_obj1, b3_obj2, next_duration, 1.0f, 2, b3_texture_id, next_duration, 1, 255, 255, 255, false); } } } // Sets the correct energy effect for an alien void AlienOrganism::UpdateEnergyEffect(int me) { int room; vector pos; int weapon_id; // See if this object has an energy charge if (memory->energy_charges > 0.0f) { // See if it's time to create the next energy effect if (memory->next_update_energy_time <= Game_GetTime()) { float slow_down = 0.5f * (ALIEN_MAX_ENERGY_CHARGES - memory->energy_charges) / ALIEN_MAX_ENERGY_CHARGES; memory->next_update_energy_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 0.1f + 0.05f + slow_down; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, energy_effect_id, room, &pos, NULL, me); } } } // Sets the alien's max speed void AlienOrganism::SetMaxSpeed(int me, float speed) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &speed); } // Sets the alien's circle distance void AlienOrganism::SetCircleDist(int me, float dist) { AI_Value(me, VF_GET, AIV_F_CIRCLE_DIST, &dist); } // Sets the Current Mode void AlienOrganism::SetMode(int me, char mode) { // mprintf(0, "From mode %d, ",memory->mode); int flags; char movement_type; vector vel; // Clear out any goals AI_SafeSetType(me, AIT_AIS); // Set FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Turn off ranged firing flags = AIF_DISABLE_FIRING; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Make it persistant flags = AIF_PERSISTANT; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Turn off the energy beams memory->energy_beams_on = false; // Do mode specific setups switch (mode) { case ALIEN_GOING_HOME: { // mprintf(0,"Going home mode set.\n"); ray_info ray; int fate; vector end_pos, landing_pos; int end_room, landing_room; // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn on friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set friend avoidance distance float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Calculate destination home point a int16_t distance from actual landing point end_pos = memory->home_pos + (memory->home_uvec * 20.0f); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &memory->home_pos, &end_pos, memory->home_room, 0.0f, flags, &ray); landing_pos = ray.hit_point; landing_room = ray.hit_room; // Set goal to head home AI_AddGoal(me, AIG_GET_TO_POS, 2, 1.0f, ALIEN_GUID_GOT_HOME, GF_ORIENT_VELOCITY, &landing_pos, landing_room); AI_SetGoalCircleDist(me, 2, 2.0f); // Set returning home speed SetMaxSpeed(me, memory->base_speed * ALIEN_RETURN_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case ALIEN_LANDING_AT_HOME: { // mprintf(0,"Landing at home mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn off friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); SetCircleDist(me, memory->base_circle_distance); // Turn on point-collide-walls and lock axes flags = PF_POINT_COLLIDE_WALLS | PF_LOCK_MASK; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn off auto-level flags = PF_LEVELING; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Make him land at his home point AI_AddGoal(me, AIG_GET_TO_POS, 2, 100.0f, ALIEN_GUID_LANDED, GF_NONFLUSHABLE | GF_KEEP_AT_COMPLETION | GF_ORIENT_SCRIPTED, &memory->home_pos, memory->home_room); AI_SetGoalCircleDist(me, 2, 0.0f); // Play his landing animation Obj_SetCustomAnim(me, 50, 72, 2.5f, 0, -1, -1); // Set landing speed SetMaxSpeed(me, memory->base_speed * ALIEN_LAND_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case ALIEN_LANDED: case ALIEN_LANDED_RESTING: case ALIEN_LANDED_HEALING: case ALIEN_LANDED_DEPOSITING: case ALIEN_LANDED_WITHDRAWING: { // mprintf(0,"Landed mode set.\n"); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn off friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); SetCircleDist(me, memory->base_circle_distance); // Turn off auto-level flags = PF_LEVELING; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn on point-collide-walls and lock axes flags = PF_POINT_COLLIDE_WALLS | PF_LOCK_MASK; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Get rid of forced awareness flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Get rid of persistance flags = AIF_PERSISTANT; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set to barely aware float awareness = AWARE_BARELY; AI_Value(me, VF_SET, AIV_F_AWARENESS, &awareness); // No movement allowed movement_type = MT_NONE; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Set FOV to nearly 0 // float sleep_fov = 1.0f; // AI_Value(me, VF_SET, AIV_F_FOV, &sleep_fov); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // If just landed, set it to the pending landed mode, and play looping idle anim if (mode == ALIEN_LANDED) { mode = memory->pending_mode; if (!IsLandedMode(mode)) { mode = ALIEN_LANDED_RESTING; } // Play his landed animation Obj_SetCustomAnim(me, 1, 10, 1, 0, -1, -1); } memory->pending_mode = ALIEN_LANDED_RESTING; memory->previous_mode = ALIEN_GOING_HOME; // Set landed speed SetMaxSpeed(me, 0.0f); // Set the next activity time if (mode == ALIEN_LANDED_RESTING) { memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 3.0f + 2.0f; memory->ok_to_deposit = true; } else { memory->next_activity_time = Game_GetTime() + ALIEN_LANDED_ACTION_TIME; } // Clear the next wander time memory->max_wander_time = 0.0f; } break; case ALIEN_HUNTING: { // mprintf(0,"Hunting mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn on friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Only avoid friends alot if not in a squad if (memory->squad_flags == 0) { float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); } else { float avoid_dist = ALIEN_SQUAD_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance * ALIEN_SQUAD_CIRC_DIST_MOD); } // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Only set goals if not currently in squad formation if (!(memory->squad_flags & ALIEN_SQUADIE)) { SetWanderGoal(me); } // Set previous mode so alien can return to this if it needs to memory->previous_mode = ALIEN_HUNTING; // Set returning home speed SetMaxSpeed(me, memory->base_speed * ALIEN_HUNT_SPEED_MOD); // Set the next activity check to happen within .4 to .8 seconds memory->next_activity_time = 0.0f; // Set the max time to wander between 20 and 40 seconds memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 20.0f + 40.0f; } break; case ALIEN_SCAVENGING: { // mprintf(0,"Scavenge mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn on friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Only avoid friends alot if not in a squad if (memory->squad_flags == 0) { float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); } else { float avoid_dist = ALIEN_SQUAD_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance * ALIEN_SQUAD_CIRC_DIST_MOD); } // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Only set goals if not currently in squad formation if (!(memory->squad_flags & ALIEN_SQUADIE)) { SetWanderGoal(me); } // Set previous mode so alien can return to this if it needs to memory->previous_mode = ALIEN_SCAVENGING; // Set returning home speed SetMaxSpeed(me, memory->base_speed * ALIEN_SCAV_SPEED_MOD); // Set the next activity check to happen within .4 to .8 seconds memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 3.0f + 3.0f; // Set the max time to wander between 20 and 40 seconds memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 10.0f + 10.0f; } break; case ALIEN_ATTACKING_MELEE: { // mprintf(0,"Melee mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn on friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Only avoid friends alot if not in a squad or if in a broken squad if ((memory->squad_flags == 0) || (memory->squad_flags & ALIEN_BROKEN)) { float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); } else { float avoid_dist = ALIEN_SQUAD_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance * ALIEN_SQUAD_CIRC_DIST_MOD); } // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Only set goals if not currently in squad formation if (!(memory->squad_flags & ALIEN_SQUADIE) || (memory->squad_flags & ALIEN_BROKEN)) { AI_SafeSetType(me, AIT_MELEE1); } // Set Engage and Attack Speeds if ((memory->squad_flags == 0) || (memory->squad_flags & ALIEN_BROKEN)) { SetMaxSpeed(me, memory->base_speed * ALIEN_ATTACK_SPEED_MOD); } else { SetMaxSpeed(me, memory->base_speed * ALIEN_ENGAGE_SPEED_MOD); } // Clear the next activity time memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 0.5f + 0.7f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case ALIEN_ATTACKING_RANGED: { // mprintf(0,"Ranged mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn on friend avoidance flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set friend avoidance distance float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Enable ranged firing flags = AIF_DISABLE_FIRING; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_EVADER1); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * ALIEN_ATTACK_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case ALIEN_FLEEING: { // mprintf(0,"Fleeing mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // avoid friends while fleeing flags = AIF_AUTO_AVOID_FRIENDS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set friend avoidance distance float avoid_dist = ALIEN_NORMAL_AVOID_DIST; AI_Value(me, VF_SET, AIV_F_AVOID_FRIENDS_DIST, &avoid_dist); SetCircleDist(me, memory->base_circle_distance); // Make it aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Clear wall point collision and axes lock flags = PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make sure squad broken flag isn't set memory->squad_flags &= ~ALIEN_BROKEN; // Set Goal to go somewhere else SetWanderGoal(me); // Set Fleeing Speed SetMaxSpeed(me, memory->base_speed * ALIEN_FLEE_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Set the max time to flee (base time somewhat off of damage) float curr_shields, percent_damaged; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &curr_shields); percent_damaged = (memory->base_shields - curr_shields) / memory->base_shields; memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 3.0f + 3.0f + (percent_damaged * 6.0f); } break; default: mprintf(0, "AlienOrganism is all messed up!\n"); } // If following leader in formation and mode supports squads, assign follow goals if ((memory->squad_flags & ALIEN_SQUADIE) && !(memory->squad_flags & ALIEN_BROKEN) && ModeSupportsSquadMovement(mode)) { SetSquadieFormationGoal(me); } // Reset the landing flags memory->done_moving = false; memory->done_turning = false; // Set the current mode and reset the mode time memory->mode = mode; memory->mode_time = 0.0f; } // Handles alien takeoffs void AlienOrganism::DoTakeoff(int me, float takeoff_speed, float speed_variance) { // If currently landed, set take off velocity if (IsLandedMode(memory->mode)) { vector vel; Obj_Value(me, VF_GET, OBJV_V_VELOCITY, &vel); vel += (memory->home_uvec * (takeoff_speed + ((float)rand() / (float)RAND_MAX) * speed_variance)); Obj_Value(me, VF_SET, OBJV_V_VELOCITY, &vel); // Play the takeoff anim and tell it to go to alert next Obj_SetCustomAnim(me, 10, 31, 1, AIAF_NOTIFY, -1, AS_ALERT); // Reset the takeoff time memory->takeoff_time = 0.0f; } } // Processes the AI Initialize event void AlienOrganism::DoInit(int me) { int flags; msafe_struct m; m.objhandle = me; // Setup memory data tOSIRISMEMCHUNK ch; ch.id = ALIEN_MEMORY_ID; ch.size = sizeof(alienorganism_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (alienorganism_data *)Scrpt_MemAlloc(&ch); // Init base values AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); AI_Value(me, VF_GET, AIV_F_CIRCLE_DIST, &memory->base_circle_distance); Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &memory->base_shields); // Init energy charge memory->energy_charges = 0.0f; memory->ok_to_deposit = true; // All beams off initially memory->enabled_beams = 0; // Clear the destination object handle memory->dest_object_handle = OBJECT_HANDLE_NONE; // Alien hasn't been hit by player yet memory->hit_by_player = false; // Alien hasn't taken off yet memory->takeoff_time = ALIEN_MAX_TAKEOFF_TIME; // Setup Squad Information memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; // .1 to 1.1 seconds into the level, do the first squad matching memory->next_update_squad_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 2.0f + 0.1f; // Update the energy effect as soon as charge exists memory->next_update_energy_time = Game_GetTime(); memory->next_update_beam_time = Game_GetTime(); // Update the squadie catchup speed as soon as possible memory->next_squadie_update_time = Game_GetTime(); // Do special damage as soon as relevant memory->next_special_damage_time = Game_GetTime(); // Set the next generic check time memory->next_generic_check_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.0f + 0.5f; memory->next_vis_check_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 2.0f + 1.0f; // Init other times memory->next_activity_time = Game_GetTime(); memory->max_wander_time = 0.0f; // Store Home Information Obj_Value(me, VF_GET, OBJV_V_POS, &memory->home_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &memory->home_room); matrix orient; Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); memory->home_fvec = orient.fvec; vm_VectorNormalize(&memory->home_fvec); memory->home_uvec = orient.uvec; vm_VectorNormalize(&memory->home_uvec); // Check and see whether we've been spawned from a matcen or not bool spawned_from_matcen = false; char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); if (type == AS_BIRTH) { mprintf(0, "Alien organism produced by matcen.\n"); // Note that we came from a matcen spawned_from_matcen = true; // Find a new home FindHome(me); } // Init squadie goal positions memory->squad_goal_pos = memory->home_pos; memory->squad_goal_room = memory->home_room; // Create and setup the energy effect props msafe_struct mstruct; memory->pos1_handle = Obj_Create(OBJ_POWERUP, powerup_id, memory->home_room, &memory->home_pos, NULL, me); mstruct.objhandle = memory->pos1_handle; MSafe_CallFunction(MSAFE_OBJECT_NO_RENDER, &mstruct); memory->pos2_handle = Obj_Create(OBJ_POWERUP, powerup_id, memory->home_room, &memory->home_pos, NULL, me); mstruct.objhandle = memory->pos2_handle; MSafe_CallFunction(MSAFE_OBJECT_NO_RENDER, &mstruct); memory->pos3_handle = Obj_Create(OBJ_POWERUP, powerup_id, memory->home_room, &memory->home_pos, NULL, me); mstruct.objhandle = memory->pos3_handle; MSafe_CallFunction(MSAFE_OBJECT_NO_RENDER, &mstruct); // Set starting mode if (spawned_from_matcen) { // Set our starting mode to landed-resting memory->previous_mode = ALIEN_GOING_HOME; memory->pending_mode = ALIEN_LANDED_RESTING; SetMode(me, ALIEN_ATTACKING_MELEE); } else { // Set our starting mode to landed-resting memory->pending_mode = ALIEN_LANDED_RESTING; SetMode(me, ALIEN_LANDED); } } // Tries to find a home for an alien (returns false if this fails) #define MAX_HOME_FINDING_ATTEMPTS 20 #define MAX_HOME_FINDING_DIST 1000 #define HOME_LOOK_OFFSET 0.18f // for a 40 degree search pyramid #define HOME_PLACE_DISTANCE 1.0f bool AlienOrganism::FindHome(int me) { int num_attempts; bool home_found; vector start_pos, target_pos, landed_pos, home_dir; int start_room, landed_room; matrix start_orient; int flags, fate; ray_info ray; float size; // Find start point Obj_Value(me, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &start_room); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &start_orient); Obj_Value(me, VF_GET, OBJV_F_SIZE, &size); home_found = false; num_attempts = 0; while (!home_found && num_attempts < MAX_HOME_FINDING_ATTEMPTS) { // Determine ray angle home_dir = start_orient.fvec; home_dir += start_orient.uvec * (HOME_LOOK_OFFSET - ((float)rand() / (float)RAND_MAX) * 2.0f * HOME_LOOK_OFFSET); home_dir += start_orient.rvec * (HOME_LOOK_OFFSET - ((float)rand() / (float)RAND_MAX) * 2.0f * HOME_LOOK_OFFSET); // Cast home-finding ray target_pos = start_pos + (home_dir * MAX_HOME_FINDING_DIST); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &start_pos, &target_pos, start_room, size, flags, &ray); // Check if it hit a face if (fate == HIT_WALL) { matrix temp; memory->home_pos = ray.hit_face_pnt; memory->home_room = ray.hit_face_room; memory->home_uvec = ray.hit_wallnorm; vm_VectorToMatrix(&temp, &memory->home_uvec, NULL, NULL); if (rand() % 100 > 50) { memory->home_fvec = temp.uvec; } else { memory->home_fvec = temp.rvec; } if (rand() % 100 > 50) memory->home_fvec = -memory->home_fvec; // Place the object a little away from the wall target_pos = memory->home_pos + (memory->home_uvec * HOME_PLACE_DISTANCE); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; FVI_RayCast(me, &memory->home_pos, &target_pos, memory->home_room, 0.0f, flags, &ray); memory->home_pos = ray.hit_point; memory->home_room = ray.hit_room; home_found = true; } num_attempts++; } return home_found; } void AlienOrganism::DoSquadieFrame(int me) { vector my_pos; int type; // Check if the leader is still alive Obj_Value(memory->leader_handle, VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_NONE) { memory->squad_flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; SetMode(me, memory->mode); return; } // If current mode disallows formation following, get outta here if (!ModeSupportsSquadMovement(memory->mode)) return; // If squad is currently broken, get outta here if (memory->squad_flags & ALIEN_BROKEN) return; // The leader is alive, so set new goal pos UpdateSquadieFormationGoal(me); // Check if it's time to update the catchup speed if (Game_GetTime() >= memory->next_squadie_update_time) { memory->next_squadie_update_time = Game_GetTime() + ALIEN_SQUADIE_INTERVAL; // Get the distance to leader Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); float dist = vm_VectorDistance(&my_pos, &memory->squad_goal_pos); // If squadie is in leader's path, slow down until leader passes float slowdown = 1.0f; if (dist <= ALIEN_APPROACH_DIST) { matrix leader_orient; vector dir, leader_pos; // See if we are in front of leader Obj_Value(memory->leader_handle, VF_GET, OBJV_M_ORIENT, &leader_orient); dir = my_pos - memory->squad_goal_pos; vm_VectorNormalize(&dir); float dot_prod; if ((dot_prod = leader_orient.fvec * dir) < 0.0f) { slowdown = dist / ALIEN_APPROACH_DIST; } } // If squadie is falling behind, let him speed up so he can catch up to the squad if (dist <= ALIEN_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = dist / ALIEN_CATCHUP_DIST * ALIEN_CATCHUP_SPEED_MOD * catchup_speed; catchup_speed += scaled_speed; catchup_speed *= slowdown; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } else if (dist > ALIEN_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = ALIEN_CATCHUP_SPEED_MOD * catchup_speed; catchup_speed += scaled_speed; catchup_speed *= slowdown; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } } } // Processes the AI Frame Interval Event void AlienOrganism::DoFrame(int me) { vector uvec; int flags; float anim_frame; int new_mode; vector vel; float last_see_time, last_see_game_time; float last_hear_time, last_hear_game_time; float last_perceive_time; /* float shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &shields); mprintf(0,"Shields: %.2f\n",shields); */ // Get the last see and hear time AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_game_time); AI_Value(me, VF_GET, AIV_F_LAST_HEAR_TARGET_TIME, &last_hear_game_time); // Calculate the time since target was seen and heard last_hear_time = Game_GetTime() - last_hear_game_time; last_see_time = Game_GetTime() - last_see_game_time; // Perceive time is last time target was seen or heard (whichever is smalled) if (last_hear_time <= last_see_time) { last_perceive_time = last_hear_time; } else { last_perceive_time = last_see_time; } // Check if takeoff time is active if (memory->takeoff_time < ALIEN_MAX_TAKEOFF_TIME) { memory->takeoff_time += Game_GetFrameTime(); // If the time is up, clear the landing lock flags if (memory->takeoff_time >= ALIEN_MAX_TAKEOFF_TIME) { // Make sure we're in a mode that wants these flags cleared if (memory->mode != ALIEN_LANDING_AT_HOME && !IsLandedMode(memory->mode)) { // Clear wall point collision and axes lock flags = PF_LOCK_MASK | PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn on dodging flags = AIF_DODGE; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // enable auto-leveling flags = PF_LEVELING; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // mprintf(0,"Alien landing flags cleared...\n"); } } } // If alien was hit by player, take action if necessary if (memory->hit_by_player) { // See if the mode we're in is sensitive to attacks if (ModeIsAttackSensitive(memory->mode, memory->pending_mode)) { // mprintf(0,"Sensitive to Attack!\n"); if (memory->squad_flags & ALIEN_SQUADIE) { SendCommand(me, memory->leader_handle, ALIEN_COM_I_WAS_HIT, NULL); } else { MeleeAttack(me); } } // reset the hit by player flag memory->hit_by_player = false; } // Periodically update the squad information (remove dead teammates and/or recruit new ones) if (memory->next_update_squad_time <= Game_GetTime()) { memory->next_update_squad_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 3.0f + 3.0f; UpdateSquad(me); } // Periodically update the squad visibility information if (memory->next_vis_check_time <= Game_GetTime()) { memory->next_vis_check_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.0f + 1.0f; // If we're leading, update the squad's visibility flags if (memory->squad_flags & ALIEN_LEADER) { UpdateSquadVisibility(me); } } // Update squadies if (memory->squad_flags & ALIEN_SQUADIE) { DoSquadieFrame(me); } // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &anim_frame); // Handle generic checks (pertains to more than 1 mode) if (memory->next_generic_check_time <= Game_GetTime()) { memory->next_generic_check_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.4f + 0.6f; // If we're not already landed, or heading home to heal, see if we should if (!IsLandedMode(memory->mode) && memory->mode != ALIEN_LANDING_AT_HOME && memory->pending_mode != ALIEN_LANDED_HEALING) { float current_shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, ¤t_shields); // If we're pretty heavily damaged, see if we should return home to heal if ((current_shields < memory->base_shields * ALIEN_HEAL_PERCENTAGE) && (rand() % 100 < 80)) { Heal(me); } } // If we're not already fleeing or going home to heal, check to see if we should flee if (memory->mode != ALIEN_FLEEING && memory->mode != ALIEN_LANDING_AT_HOME && memory->mode != ALIEN_LANDED_HEALING && memory->pending_mode != ALIEN_LANDED_HEALING) { if (IsOnFire(me)) { // If we're on fire, there's a good chance that we will try to flee if ((rand() % 100) < ALIEN_ON_FIRE_FLEE_CHANCE) Flee(me); } else if (IsNearFire(me)) { // If we're near fire, there's a chance that we will try to flee if ((rand() % 100) < ALIEN_NEAR_FIRE_FLEE_CHANCE) Flee(me); } } // If we're landed, see if we want to play the landed animation if (IsLandedMode(memory->mode) && anim_frame == 10 && (rand() % 100) < 15) { Obj_SetCustomAnim(me, 1, 10, 1, 0, -1, -1); } } // Handle mode specific operations switch (memory->mode) { case ALIEN_LANDED_RESTING: { // See if it should consider doing something spontaneously if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to think about doing something (4-6 seconds) memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.0f + 0.5f; // If alien has a target, decide whether it should go hunt it down if (last_perceive_time < 3.0f) { int target_handle; int die_roll = 0; AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle != OBJECT_HANDLE_NONE) { die_roll = (rand() % 90) + 1; float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); // Adjust the random value by distance to target // (ie the closer the target, the more likely it will be attacked) if (dist > 300.0f) die_roll -= 50; else if (dist > 200.0f) die_roll -= 45; else if (dist > 100.0f) die_roll -= 40; else if (dist > 70.0f) die_roll -= 30; else if (dist > 50.0f) die_roll -= 20; else if (dist > 25.0f) die_roll += 5; else die_roll += 50; // Grant bonuses for recent seeing and hearing if (last_see_time < 3.0) die_roll += 5; if (last_hear_time < 4.0f) die_roll += 50; // mprintf(0,"hear: %.1f, see: %.1f, perceive: %.1f\n",last_hear_time,last_see_time,last_perceive_time); // mprintf(0,"Doing check...%d - %.1f\n",die_roll,dist); } if (die_roll > 70) { // If we're a squadie, lie and tell the leader we got hit (so he orders the attack) if (memory->squad_flags & ALIEN_SQUADIE) { SendCommand(me, memory->leader_handle, ALIEN_COM_I_WAS_HIT, NULL); } else { if (MeleeAttack(me)) break; } } } // If we currently have any charges, go ahead and deposit them if (memory->energy_charges > 0.0f) { // If we're a squadie, tell the leader we are leaving if (memory->squad_flags & ALIEN_SQUADIE) { LeaveLeader(me); } // If we're a leader, disperse the squad if (memory->squad_flags & ALIEN_LEADER) { DisperseSquad(me); } SetMode(me, ALIEN_LANDED_DEPOSITING); break; } /* // Only think if it's not a squadie (its leader will decide when/whether to do something) if(!(memory->squad_flags & ALIEN_SQUADIE)) { int die_roll = (rand()%100) + 1; // See what to do if(die_roll <= 20) // Go hunting { // Start hunting SetMode(me, ALIEN_HUNTING); // If it's a leader, order squad to switch to hunting mode if(memory->squad_flags & ALIEN_LEADER) { int mode=ALIEN_HUNTING; IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } } else if(die_roll <= 30) // Go scavenging { // Start scavenging SetMode(me, ALIEN_SCAVENGING); // If it's a leader, order squad to switch to scavenging mode if(memory->squad_flags & ALIEN_LEADER) { int mode=ALIEN_SCAVENGING; IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } } } */ } } break; case ALIEN_LANDED_HEALING: { // See if we should do a round of healing if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + ALIEN_LANDED_ACTION_TIME; // Restore some shields float current_shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, ¤t_shields); current_shields += (memory->base_shields * 0.2f); bool done_healing = false; if (current_shields >= memory->base_shields) { current_shields = memory->base_shields; done_healing = true; } // Set the current shields Obj_Value(me, VF_SET, OBJV_F_SHIELDS, ¤t_shields); // mprintf(0,"Healing...\n"); // If we're all healed up, go to landed resting if (done_healing) { SetMode(me, ALIEN_LANDED_RESTING); // mprintf(0,"Done healing.\n"); } } // Handle the energy beam effects if (!memory->energy_beams_on) CalcEnergyBeamPositions(me); UpdateEnergyBeams(me); } break; case ALIEN_LANDED_DEPOSITING: { // See if we should do a round of energy depositing if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + ALIEN_LANDED_ACTION_TIME; // Deposit some charges memory->energy_charges -= (ALIEN_MAX_ENERGY_CHARGES * 0.25f); bool done_depositing = false; if (memory->energy_charges <= 0.0f) { memory->energy_charges = 0.0f; done_depositing = true; } // mprintf(0,"Depositing...\n"); // If we're all healed up, go to landed resting if (done_depositing) { SetMode(me, ALIEN_LANDED_RESTING); // mprintf(0,"Done depositing.\n"); } } // Handle the energy beam effects if (!memory->energy_beams_on) CalcEnergyBeamPositions(me); UpdateEnergyBeams(me); } break; case ALIEN_LANDED_WITHDRAWING: { // See if we should do a round of energy withdrawing if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + ALIEN_LANDED_ACTION_TIME; // Deposit some charges memory->energy_charges += (ALIEN_MAX_ENERGY_CHARGES * 0.25f); bool done_withdrawing = false; if (memory->energy_charges >= ALIEN_MAX_ENERGY_CHARGES) { memory->energy_charges = ALIEN_MAX_ENERGY_CHARGES; done_withdrawing = true; } // mprintf(0,"Withdrawing...\n"); // If we're all healed up, go to melee mode if (done_withdrawing) { memory->ok_to_deposit = false; memory->previous_mode = ALIEN_GOING_HOME; memory->pending_mode = ALIEN_LANDED_RESTING; SetMode(me, ALIEN_ATTACKING_MELEE); // mprintf(0,"Done withdrawing.\n"); } } // Handle the energy beam effects if (!memory->energy_beams_on) CalcEnergyBeamPositions(me); UpdateEnergyBeams(me); } break; case ALIEN_LANDING_AT_HOME: { // mprintf(0, "AlienOrganism Landed at Home\n"); if (memory->done_turning == true && memory->done_moving == true) { SetMode(me, ALIEN_LANDED); } } break; case ALIEN_GOING_HOME: { char type, next_type; // If we're simply going home to rest, and a target is present, attack it! if (memory->pending_mode == ALIEN_LANDED_RESTING && last_perceive_time < 3.0f) { if (MeleeAttack(me)) break; } // mprintf(0, "AlienOrganism going home\n"); // If we're in alert, or the next anim mode is alert, and we're done moving, land at home if (memory->done_moving == true) { AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); if (type == AS_ALERT || type == AS_IDLE || next_type == AS_ALERT || next_type == AS_IDLE) { SetMode(me, ALIEN_LANDING_AT_HOME); } } } break; case ALIEN_FLEEING: { // See if it is time to stop running if (memory->mode_time >= memory->max_wander_time) { // Go back to melee mode for now SetMode(me, ALIEN_ATTACKING_MELEE); } } break; case ALIEN_FINDING_BIRTHPLACE: break; case ALIEN_GIVING_BIRTH: break; case ALIEN_HUNTING: { // If a target has been spotted, attack it if (!(memory->squad_flags & ALIEN_SQUADIE) && last_perceive_time < 3.0f) { if (MeleeAttack(me)) break; } // See if it is time to go home if (memory->mode_time >= memory->max_wander_time) { // Set the next max wander time memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 10.0f + 10.0f; // If not a squadie, decide to go home if (!(memory->squad_flags & ALIEN_SQUADIE)) { // If leading squad, tell squad to go home and rest if (memory->squad_flags & ALIEN_LEADER) { int pending_mode = ALIEN_LANDED_RESTING; IssueSquadOrder(me, ALIEN_COM_SET_PENDING_MODE, &pending_mode); int mode = ALIEN_GOING_HOME; DisperseSquadToMode(me, mode); } // Go home and rest memory->pending_mode = ALIEN_LANDED_RESTING; SetMode(me, ALIEN_GOING_HOME); break; } } } break; case ALIEN_SCAVENGING: { // See if it is time to go home if (memory->mode_time >= memory->max_wander_time) { // Set the next max wander time memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 10.0f + 10.0f; // If not a squadie, decide to go home if (!(memory->squad_flags & ALIEN_SQUADIE)) { // If leading squad, tell squad to go home and rest if (memory->squad_flags & ALIEN_LEADER) { int pending_mode = ALIEN_LANDED_RESTING; IssueSquadOrder(me, ALIEN_COM_SET_PENDING_MODE, &pending_mode); int mode = ALIEN_GOING_HOME; DisperseSquadToMode(me, mode); } // Go home and rest memory->pending_mode = ALIEN_LANDED_RESTING; SetMode(me, ALIEN_GOING_HOME); break; } } } break; case ALIEN_ATTACKING_MELEE: { // If a target hasn't been spotted for a while, return to previous task if (!(memory->squad_flags & ALIEN_SQUADIE) && last_perceive_time > 7.0f) { int mode = memory->previous_mode; // If leading squad, tell squad to return to previous task's mode if (memory->squad_flags & ALIEN_LEADER) { // If leader is going home, disperse the squad if (mode == ALIEN_GOING_HOME) { int pending_mode = ALIEN_LANDED_RESTING; IssueSquadOrder(me, ALIEN_COM_SET_PENDING_MODE, &pending_mode); DisperseSquadToMode(me, mode); } else { // Otherwise just tell them to switch to the new mode (hunt or scavenge) IssueSquadOrder(me, ALIEN_COM_SET_MODE, &mode); } } // Return to previous mode SetMode(me, mode); break; } // If leading, check target distance for grouping orders if (memory->squad_flags & ALIEN_LEADER) { int target_handle; AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle != OBJECT_HANDLE_NONE) { float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); // See if we need to regroup or break up and attack if ((dist >= ALIEN_REGROUP_DIST) && (memory->squad_flags & ALIEN_BROKEN)) { // Tell Squad to regroup IssueSquadOrder(me, ALIEN_COM_REGROUP, NULL); // Set up leader for regrouping memory->squad_flags &= ~ALIEN_BROKEN; SetMode(me, memory->mode); break; } else if ((dist <= ALIEN_BREAKUP_DIST) && !(memory->squad_flags & ALIEN_BROKEN)) { // Tell Squad to breakup and attack! IssueSquadOrder(me, ALIEN_COM_BREAK_AND_ATTACK, NULL); // Set up leader for regrouping memory->squad_flags |= ALIEN_BROKEN; SetMode(me, memory->mode); break; } } } /* // If we're a non-broken squadie, and target is closer than leader, break on our own if(memory->squad_flags & ALIEN_SQUADIE && !(memory->squad_flags & ALIEN_BROKEN)) { int target_handle; AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if(target_handle != OBJECT_HANDLE_NONE) { } } */ // See if we should decide whether to do any charge related activities if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 0.6f + 0.8f; // If we have some charges, see if we should do something with it if (memory->energy_charges > 0.0f) { int die_roll = rand() % 100; if (die_roll < 70) { // Switch to ranged mode RangedAttack(me); break; } else if (die_roll < 90 && memory->ok_to_deposit) { // Return home and deposit it Deposit(me); break; } } else { // See if we want to return home and withdrawal some int die_roll = rand() % 1000; // mprintf(0,"Doing withdrawal check... %d\n",die_roll); if (die_roll < 5) // 0.5% chance { // Possibly factor in a distance to home check here? Withdraw(me); break; } } } } break; case ALIEN_ATTACKING_RANGED: { // If we've lost target, or if we're out of charges, go back to melee mode if (last_perceive_time > 6.0f || memory->energy_charges <= 0.0f) { // Return to melee mode SetMode(me, ALIEN_ATTACKING_MELEE); break; } } break; } // Update the energy effect UpdateEnergyEffect(me); // Increment the current mode time memory->mode_time += Game_GetFrameTime(); /* char type, next_type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); mprintf(0,"Curr anim type: %d, Next anim type: %d\n",type,next_type); if(next_type==AS_CUSTOM) mprintf(0,"NEXT TYPE IS CUSTOM!\n\n\n\n\n\n\n"); */ } // Process all AI Notify events bool AlienOrganism::DoNotify(int me, tOSIRISEventInfo *data) { if (IsGoalFinishedNotify(data->evt_ai_notify.notify_type)) { // Handle goal complete events switch (data->evt_ai_notify.goal_uid) { case ALIEN_GUID_GOT_HOME: memory->done_moving = true; break; case ALIEN_GUID_LANDED: memory->done_moving = true; break; case ALIEN_GUID_GOT_TO_DEST_OBJ: memory->dest_object_handle = OBJECT_HANDLE_NONE; SetMode(me, memory->mode); mprintf(0, "Alien got to destination object.\n"); break; } } else if (data->evt_ai_notify.notify_type == AIN_SCRIPTED_ORIENT) { // Handle custom orientation events if (memory->mode == ALIEN_LANDING_AT_HOME) { memory->done_turning = (AI_TurnTowardsVectors(me, &memory->home_fvec, &memory->home_uvec) != 0); } } else if (data->evt_ai_notify.notify_type == AIN_FIRED_WEAPON) { mprintf(0, "Energy weapon fired...\n"); // Subtract energy it took to fire energy bolt memory->energy_charges -= ALIEN_ENERGY_BOLT_COST; if (memory->energy_charges < 0.0f) memory->energy_charges = 0.0f; } else if (data->evt_ai_notify.notify_type == AIN_SCRIPTED_GOAL) { // mprintf(0,"Custom goal called.\n"); vector my_pos, dir; Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); SendCommand(me, memory->leader_handle, ALIEN_COM_GET_GOAL_POS, &memory->squad_goal_pos); dir = memory->squad_goal_pos - my_pos; vm_VectorNormalize(&dir); AI_Value(me, VF_SET, AIV_V_MOVEMENT_DIR, &dir); } else if (data->evt_ai_notify.notify_type == AIN_SCRIPTED_ENABLER) { // mprintf(0,"Custom enabler called.\n"); bool is_visible; // Ask leader if he can see me SendCommand(me, memory->leader_handle, ALIEN_COM_CAN_YOU_SEE_ME, &is_visible); // If I'm visible, do straight line to position, otherwise deactivate this goal return is_visible; } else if (data->evt_ai_notify.notify_type == AIN_MELEE_HIT) { // Make sure we are doing the energy suck attack if (data->evt_ai_notify.attack_num == 0) { int target = data->evt_ai_notify.it_handle; // Check if target has energy (if not a player, assume it does have energy) bool target_has_energy = true; int type; Obj_Value(target, VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_PLAYER) { if (qObjEnergy(target) <= 0.0f) { target_has_energy = false; } } // If target still has energy, give alien an energy charge if (target_has_energy && memory->energy_charges < ALIEN_MAX_ENERGY_CHARGES) { memory->energy_charges += ALIEN_ENERGY_SUCK_CHARGE; if (memory->energy_charges > ALIEN_MAX_ENERGY_CHARGES) { memory->energy_charges = ALIEN_MAX_ENERGY_CHARGES; } memory->ok_to_deposit = true; } } } else if (data->evt_ai_notify.notify_type == AIN_USER_DEFINED) { bot_com *com = (bot_com *)data->extra_info; return ReceiveCommand(me, data->evt_ai_notify.it_handle, com->action, com->ptr); } return true; } // Handles application of damage to an alien void AlienOrganism::DoDamage(int me, tOSIRISEVTDAMAGED *damage_data) { /* int wpn_handle=damage_data->weapon_handle; int type; uint16_t id; wpn_handle=damage_data->weapon_handle; Obj_Value(wpn_handle, VF_GET, OBJV_US_ID, &id); Obj_Value(wpn_handle, VF_GET, OBJV_I_TYPE, &type); if(type == OBJ_WEAPON && (id == Wpn_FindID("Napalm") || id == Wpn_FindID("NapalmBlob"))) */ // Handle fire damage if (damage_data->damage_type == GD_FIRE) { damage_data->damage *= 0.7f; // mprintf(0, "Damaged by fire!\n"); return; } // Handle damage by matter weapons if (damage_data->damage_type == GD_MATTER) { damage_data->damage *= 0.9f; if (Game_GetTime() >= memory->next_special_damage_time) { memory->next_special_damage_time = Game_GetTime() + 0.8f + ((float)rand() / (float)RAND_MAX) * 0.5f; // Create a frag burst effect vector pos; int room; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, frag_burst_effect_id, room, &pos, NULL, me); } return; } // Handle damage by energy weapons if ((damage_data->damage_type == GD_ENERGY || damage_data->damage_type == GD_ELECTRIC) && Game_GetTime() >= memory->next_special_damage_time) { // Check if we're currently doing energy transfer or healing if (IsEnergyRelatedLandedMode(memory->mode)) { memory->next_special_damage_time = Game_GetTime() + 0.1f + ((float)rand() / (float)RAND_MAX) * 0.2f; // Do double damage damage_data->damage *= 3.0f; // Create sparks int room; vector pos; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Game_CreateRandomSparks(30 + int(((float)rand() / (float)RAND_MAX) * 10.0f), &pos, room); return; } } // Just make it do little damage damage_data->damage *= 0.15f; // mprintf(0, "Not damaged!\n"); } // Handles attacks on aliens void AlienOrganism::DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data) { // See if it's a player or player weapon that hit us if (qObjIsPlayerOrPlayerWeapon(collide_data->it_handle)) { // mprintf(0,"Hit by player!\n"); memory->hit_by_player = true; } } // Receives all basic events and calls processesing functions int16_t AlienOrganism::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: DoDamage(data->me_handle, &data->evt_damaged); break; case EVT_DESTROY: msafe_struct mo; mo.objhandle = memory->pos1_handle; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); mo.objhandle = memory->pos2_handle; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); mo.objhandle = memory->pos3_handle; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); break; case EVT_COLLIDE: DoCollide(data->me_handle, &data->evt_collide); break; case EVT_MEMRESTORE: memory = (alienorganism_data *)data->evt_memrestore.memory_ptr; break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //--------------------- // Heavy Trooper class //--------------------- // Sets the trooper's max speed void HeavyTrooper::SetMaxSpeed(int me, float speed, bool accel_faster /*=false*/) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &speed); float delta_speed = memory->base_delta_speed; float turn_rate = memory->base_turn_rate; float delta_turn_rate = memory->base_delta_turn_rate; if (accel_faster) { delta_speed *= HT_RAMMING_DELTA_SPEED_MOD; turn_rate *= HT_RAMMING_TURN_RATE_MOD; delta_turn_rate *= HT_RAMMING_DELTA_TURN_RATE_MOD; } AI_Value(me, VF_SET, AIV_F_MAX_DELTA_SPEED, &delta_speed); AI_Value(me, VF_SET, AIV_F_MAX_TURN_RATE, &turn_rate); AI_Value(me, VF_SET, AIV_F_MAX_DELTA_TURN_RATE, &delta_turn_rate); } // Enable the trooper's gun attack void HeavyTrooper::EnableGunAttack(int me, bool enable /*=true*/) { int flags = AIF_DISABLE_FIRING; if (enable) { AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); } else { AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); } } // Launches a grenade void HeavyTrooper::LaunchGrenade(int me) { msafe_struct mstruct; mstruct.objhandle = me; mstruct.index = ht_grenade_id; if (mstruct.index == -1) return; mstruct.gunpoint = HT_GRENADE_GUNPOINT; MSafe_CallFunction(MSAFE_OBJECT_FIRE_WEAPON, &mstruct); // Play the impact mortar launch sound aSoundPlayObject(ht_grenade_sound_id, me, 1.0f); /* // Create blast effect vector pos; int room; Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_GetGunPos(me, HT_GRENADE_GUNPOINT, &pos); Obj_Create(OBJ_WEAPON, ht_grenade_effect_id, room, &pos, NULL, me); */ // Create smoke spew TurnOnSpew(me, 2, 7, 0.0f, 0.0f, 65536, false, 0.5f, 0.1f, 0.7f, 1.0f, 15.0f, true); TurnOnSpew(me, 2, 3, 0.0f, 0.0f, 65536, false, 0.2f, 0.05f, 0.3f, 2.0f, 40.0f, true); } // Checks whether the trooper has a clear shot bool HeavyTrooper::HasAClearGrenadeShot(int me, bool use_grenade_gunpoint /*=false*/) { vector pos, dir; matrix orient; int room; // Get the current object's room number Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); // See where we should aim from if (use_grenade_gunpoint) { // aim directly from the grenade launcher's gunpoint Obj_GetGunPos(me, HT_GRENADE_GUNPOINT, &pos, &dir); } else { // since we don't want to aim with the gunpoint, just use the forward vector Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); dir = orient.fvec; } // See if should check for a clear shot tShotData shot_data; shot_data.object_handle = me; shot_data.danger_radius = 20.0f; shot_data.radius_inc = 1.2f; shot_data.max_dist = 120.0f; shot_data.risk_factor = 0.0f; shot_data.start_point = pos; shot_data.start_room = room; shot_data.dir = dir; shot_data.shot_radius = 5.0f; /* // If we're not checking during actual launch, make check a little bigger if(!use_grenade_gunpoint) { shot_data.danger_radius *= 1.3f; } */ int result = HasClearShot(&shot_data); if (result == CS_ALL_CLEAR) { return true; } else { mprintf(0, "Didn't shoot because: %d\n", result); return false; } } // Sets the Current Mode void HeavyTrooper::SetMode(int me, char mode) { int flags; // mprintf(0, "From mode %d, ",memory->mode); // Clear out any non-Dallas assigned goals AI_SafeSetType(me, AIT_AIS); // Turn on dodging (only off when curled up) flags = AIF_DODGE; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Do mode specific setups switch (mode) { case HT_ATTACK_NORMAL: { mprintf(0, "Normal attack mode set.\n"); // Set firing capabilities EnableGunAttack(me); // Set speed SetMaxSpeed(me, memory->base_speed); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_PREPARING_GRENADE: { mprintf(0, "Preparing grenade mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_GRENADE_AIMING_SPEED_MOD); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_FIRING_GRENADE: { mprintf(0, "Firing grenade mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_GRENADE_FIRING_SPEED_MOD); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); // Play the fire grenade anim Obj_SetCustomAnim(me, HT_GRENADE_START_FRAME, HT_GRENADE_FIRE_FRAME, HT_GRENADE_FIRE_ANIM_TIME, 0, -1, -1); } break; case HT_RECOVERING: { mprintf(0, "Recovering mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_GRENADE_AIMING_SPEED_MOD); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_PREPARING_FOR_CURL: { mprintf(0, "Preparing for curl mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_CURLING_SPEED_MOD); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_CURLING_UP: { mprintf(0, "Curling up mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_CURLING_SPEED_MOD); // Play the curling up animation Obj_SetCustomAnim(me, HT_CURLUP_START_FRAME, HT_CURLUP_END_FRAME, HT_CURLUP_ANIM_TIME, 0, -1, -1); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_UNCURLING: { mprintf(0, "Uncurling mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Set speed SetMaxSpeed(me, memory->base_speed * HT_CURLING_SPEED_MOD); // Play the uncurling animation and then switch back to alert Obj_SetCustomAnim(me, HT_UNCURL_START_FRAME, HT_UNCURL_END_FRAME, HT_UNCURL_ANIM_TIME, AIAF_NOTIFY, -1, AS_ALERT); // Set to default ranged attack goals AI_SafeSetType(me, AIT_EVADER1); } break; case HT_ARMORED_ATTACK: { mprintf(0, "Armored mode set.\n"); // Disable firing capabilities EnableGunAttack(me, false); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set speed SetMaxSpeed(me, memory->base_speed * HT_RAMMING_SPEED_MOD, true); // Set goal to ram into player AI_AddGoal(me, AIG_GET_TO_OBJ, 1, 1.0f, -1, GF_ORIENT_VELOCITY | GF_OBJ_IS_TARGET | GF_USE_BLINE_IF_SEES_GOAL | GF_KEEP_AT_COMPLETION | GF_NONFLUSHABLE, OBJECT_HANDLE_NONE); AI_SetGoalCircleDist(me, 1, 0.1f); } break; case HT_ARMORED_CIRCLE_BACK: { mprintf(0, "Armored circle-back mode set.\n"); vector pos; int room; // Disable firing capabilities EnableGunAttack(me, false); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set speed SetMaxSpeed(me, memory->base_speed * HT_CIRCLE_BACK_SPEED_MOD, true); // Set the goal to wander away for a few seconds Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); float dist = 10.0f; AI_AddGoal(me, AIG_WANDER_AROUND, 1, 1.0f, -1, GF_KEEP_AT_COMPLETION | GF_NONFLUSHABLE, &pos, room); AI_GoalValue(me, 1, VF_SET, AIGV_F_CIRCLE_DIST, &dist); int g_index = AI_AddGoal(me, AIG_GET_AROUND_OBJ, ACTIVATION_BLEND_LEVEL, 1.0f, -1, GF_OBJ_IS_TARGET | GF_KEEP_AT_COMPLETION | GF_NONFLUSHABLE, OBJECT_HANDLE_NONE); AI_GoalValue(me, g_index, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } break; default: mprintf(0, "Heavy Trooper is all messed up!\n"); break; } // Set the current mode and reset the mode time memory->mode = mode; memory->mode_time = 0.0f; } // Processes the AI Initialize event void HeavyTrooper::DoInit(int me) { // Setup memory data tOSIRISMEMCHUNK ch; ch.id = HT_MEMORY_ID; ch.size = sizeof(heavytrooper_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (heavytrooper_data *)Scrpt_MemAlloc(&ch); // Init base values AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); AI_Value(me, VF_GET, AIV_F_MAX_DELTA_SPEED, &memory->base_delta_speed); AI_Value(me, VF_GET, AIV_F_MAX_TURN_RATE, &memory->base_turn_rate); AI_Value(me, VF_GET, AIV_F_MAX_DELTA_TURN_RATE, &memory->base_delta_turn_rate); Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &memory->base_shields); // Init to no charge memory->charge = 0.0f; memory->max_charge = HT_MAX_CHARGE + ((float)rand() / (float)RAND_MAX) * HT_MAX_CHARGE_VARIANCE; memory->curlup_dist = HT_CURLUP_DIST + ((float)rand() / (float)RAND_MAX) * HT_CURLUP_DIST_VARIANCE; // Check for a grenade shot as soon as possible memory->next_grenade_check_time = Game_GetTime(); memory->last_grenade_time = Game_GetTime(); memory->next_charge_time = Game_GetTime(); memory->next_blast_time = Game_GetTime(); // Do next particle effect as soon as possible memory->next_particle_time = Game_GetTime(); // Start out in normal attack mode SetMode(me, HT_ATTACK_NORMAL); } // Processes the AI Frame Interval Event void HeavyTrooper::DoFrame(int me) { float last_see_time, last_see_game_time; float last_hear_time, last_hear_game_time; float curr_anim_frame; // Get the last see and hear time AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_game_time); AI_Value(me, VF_GET, AIV_F_LAST_HEAR_TARGET_TIME, &last_hear_game_time); // Calculate the time since target was seen or heard last_hear_time = Game_GetTime() - last_hear_game_time; last_see_time = Game_GetTime() - last_see_game_time; // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &curr_anim_frame); // Handle charge increase and decrease if (memory->mode == HT_ATTACK_NORMAL || memory->mode == HT_PREPARING_GRENADE || memory->mode == HT_FIRING_GRENADE || memory->mode == HT_RECOVERING) { if (Game_GetTime() >= memory->next_charge_time) { // Set the next recharge time memory->next_charge_time = Game_GetTime() + HT_RECHARGE_TIME; // Increment the charge memory->charge += HT_CHARGE_INC; if (memory->charge > memory->max_charge) { memory->charge = memory->max_charge; } } } else if (memory->mode == HT_ARMORED_ATTACK || memory->mode == HT_ARMORED_CIRCLE_BACK) { if (Game_GetTime() >= memory->next_charge_time) { // Set the next decharge time memory->next_charge_time = Game_GetTime() + HT_DECHARGE_TIME; // Decrement the charge memory->charge -= HT_CHARGE_INC; if (memory->charge < 0.0f) { memory->charge = 0.0f; } } } // mprintf(0,"Charge: %.1f\n",memory->charge); // Do mode specific checks switch (memory->mode) { case HT_ATTACK_NORMAL: { // Get the current target handle int target_handle; AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); ///* // If we are fully charged, decide if we should curl up if (memory->charge >= memory->max_charge) { // If low on hitpoints float shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &shields); if (shields < (memory->base_shields * HT_CURLUP_DAMAGE)) { SetMode(me, HT_PREPARING_FOR_CURL); break; } // If close to target if (target_handle != OBJECT_HANDLE_NONE) { float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); if (dist < memory->curlup_dist) { SetMode(me, HT_PREPARING_FOR_CURL); break; } } } //*/ // Decide if we should switch into grenade mode if (Game_GetTime() >= memory->next_grenade_check_time) { memory->next_grenade_check_time = Game_GetTime() + HT_GRENADE_CHECK_INTERVAL + ((float)rand() / (float)RAND_MAX) * HT_GRENADE_CHECK_VARIANCE; // Make sure enough time has gone by since we last fired if ((Game_GetTime() - memory->last_grenade_time) >= HT_GRENADE_RELOAD_TIME) { // Make sure we have a target if (target_handle != OBJECT_HANDLE_NONE) { float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); // Make sure the target is within range if (dist > HT_GRENADE_DIST && dist < HT_GRENADE_DIST_MAX) { // Make sure we currently have a clear shot if (HasAClearGrenadeShot(me, false)) { SetMode(me, HT_PREPARING_GRENADE); break; } } } } } // See if we're currently in the firing animation if (curr_anim_frame > HT_GUNFIRE_START_FRAME && curr_anim_frame < HT_GUNFIRE_END_FRAME) { // Check the time to see if we need to generate a new particle spew if (Game_GetTime() >= memory->next_particle_time) { memory->next_particle_time = Game_GetTime() + HT_MAX_PARTICLE_TIME; // Create the casing particle effect TurnOnSpew(me, 3, 15, 2.0f, 0.0f, 65536 | 128, false, 0.1f, 0.15f, HT_PARTICLE_SPEW_TIME, 0.2f, 50, true); /* msafe_struct mstruct; mstruct.objhandle = me; mstruct.index = Wpn_FindID("ChaffSpark") ; if (mstruct.index == -1) return; mstruct.gunpoint = 3; MSafe_CallFunction(MSAFE_OBJECT_FIRE_WEAPON,&mstruct); */ } } } break; case HT_PREPARING_GRENADE: { char type, next_type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); // Wait until the current or next anim type is alert, then try and fire grenade if (type == AS_ALERT || next_type == AS_ALERT) { SetMode(me, HT_FIRING_GRENADE); break; } } break; case HT_FIRING_GRENADE: { // Wait until the fire frame is up if (curr_anim_frame == HT_GRENADE_FIRE_FRAME) { float anim_time; // Start the last grenade time count now memory->last_grenade_time = Game_GetTime(); // Do another aim check, but this time from the actual grenade gunpoint if (HasAClearGrenadeShot(me, true)) { // Fire grenade from gunpoint LaunchGrenade(me); // Set anim time to recoil anim_time = HT_GRENADE_RECOIL_ANIM_TIME; } else { // Set time to misfire anim_time = HT_GRENADE_MISFIRE_ANIM_TIME; } // Play the recover animation Obj_SetCustomAnim(me, HT_GRENADE_FIRE_FRAME, HT_GRENADE_END_FRAME, anim_time, AIAF_NOTIFY, -1, AS_ALERT); // Go to recover mode SetMode(me, HT_RECOVERING); break; } } break; case HT_RECOVERING: { char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); // Wait until the current anim type is alert, then return to normal if (type == AS_ALERT) { SetMode(me, HT_ATTACK_NORMAL); break; } } break; case HT_PREPARING_FOR_CURL: { char type, next_type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); // Wait until the current or next anim type is alert, then curl up if (type == AS_ALERT || next_type == AS_ALERT) { SetMode(me, HT_CURLING_UP); break; } } break; case HT_CURLING_UP: { // If we're done curling up, switch into armored attack mode if (curr_anim_frame == HT_CURLUP_END_FRAME) { Obj_SetCustomAnim(me, HT_ROLL_START_FRAME, HT_ROLL_END_FRAME, HT_ROLL_ANIM_TIME, AIAF_LOOPING, -1, -1); SetMode(me, HT_ARMORED_ATTACK); break; } } break; case HT_UNCURLING: { char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); // Wait until the current anim type is alert, then return to normal if (type == AS_ALERT) { SetMode(me, HT_ATTACK_NORMAL); break; } } break; case HT_ARMORED_ATTACK: { // If we're out of charge time to uncurl if (memory->charge <= 0.0f) { SetMode(me, HT_UNCURLING); break; } } break; case HT_ARMORED_CIRCLE_BACK: { // If we're out of charge or have circled back long enough, get outta here if (memory->charge <= 0.0f || memory->mode_time >= HT_CIRCLE_BACK_TIME) { SetMode(me, HT_ARMORED_ATTACK); break; } } break; default: mprintf(0, "Heavy Trooper is all messed up!\n"); break; } // Increment the current mode time memory->mode_time += Game_GetFrameTime(); } // Process all AI Notify events bool HeavyTrooper::DoNotify(int me, tOSIRISEventInfo *data) { return true; } // Processes incoming damage reports void HeavyTrooper::DoDamage(int me, tOSIRISEVTDAMAGED *damage_data) { // If trooper is currently in armored mode, reduce damage significantly if (memory->mode == HT_ARMORED_ATTACK || memory->mode == HT_ARMORED_CIRCLE_BACK) { damage_data->damage *= HT_ARMOR_DAMAGED_MOD; } else if (memory->mode == HT_CURLING_UP || memory->mode == HT_UNCURLING) { damage_data->damage *= HT_CURLING_DAMAGED_MOD; } } // Handle smashing into target in armored mode void HeavyTrooper::DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data) { vector pos; int room; // Make sure we're in armored attack mode and still have charge left if (memory->mode == HT_ARMORED_ATTACK && memory->charge > 0.0f) { // Make sure it's ok to create the attack if necessary if (Game_GetTime() >= memory->next_blast_time) { // Make sure we have a target int target_handle; AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle != OBJECT_HANDLE_NONE) { // See if the object we collided with is the target if (collide_data->it_handle == target_handle) { // Create the shield blast Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, shield_blast_id, room, &pos, NULL, me); // Set the next valid shield blast time memory->next_blast_time = Game_GetTime() + HT_SHIELD_BLAST_DELAY; // Tell him to run away SetMode(me, HT_ARMORED_CIRCLE_BACK); } } } } } // Receives all basic events and calls processesing functions int16_t HeavyTrooper::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: DoDamage(data->me_handle, &data->evt_damaged); break; case EVT_DESTROY: break; case EVT_COLLIDE: DoCollide(data->me_handle, &data->evt_collide); break; case EVT_MEMRESTORE: memory = (heavytrooper_data *)data->evt_memrestore.memory_ptr; break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //-------------- // Lifter class //-------------- // Sets the trooper's max speed void Lifter::SetMaxSpeed(int me, float speed) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &speed); } // Checks to see if tractor beam pulling can commence/continue bool Lifter::OkToPull(int me, bool initial_check /*=true*/) { matrix orient; vector vec_to_target; int target_handle; float fov_angle; // If it's the initial check, make sure we're in alert mode if (initial_check) { char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); // Wait until the current anim type is alert, then return to normal if (type != AS_ALERT) return false; } // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle == OBJECT_HANDLE_NONE) return false; // Make sure it's a pullable target int type; Obj_Value(target_handle, VF_GET, OBJV_I_TYPE, &type); if (type != OBJ_PLAYER) return false; // If it's not the initial pull, make sure the target hasn't changed if (!initial_check && target_handle != memory->pull_target) return false; // Make sure the target isn't cloaked if (qObjectCloakTime(target_handle) != 0.0f) return false; // check distance to target float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); if (dist > LIFTER_MAX_PULLING_DIST) return false; // Only check min distance if it's the initial decision check (whether to start pulling) if (initial_check && dist < LIFTER_MIN_PULLING_DIST) return false; // check FOV position of target (greater than 45 degrees is too much) Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); // NOTE: do my own vec to target here! (chris' doesn't update quick enough) AI_Value(me, VF_GET, AIV_V_VEC_TO_TARGET, &vec_to_target); vm_VectorNormalize(&vec_to_target); fov_angle = orient.fvec * vec_to_target; if (fov_angle < 0.7f) return false; // see if anything is in the way ray_info ray; int flags, fate; vector start_pos, end_pos, landing_pos; int start_room, landing_room; float target_size; Obj_Value(me, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &start_room); Obj_Value(target_handle, VF_GET, OBJV_V_POS, &end_pos); Obj_Value(target_handle, VF_GET, OBJV_F_SIZE, &target_size); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &start_pos, &end_pos, start_room, target_size, flags, &ray); if (fate != HIT_NONE) return false; return true; } // Attempts to pull the target towards us void Lifter::PullTarget(int me) { // see if anything is in the way ray_info ray; int flags, fate; vector dir, start_pos, my_pos, end_pos, landing_pos; int start_room, landing_room; float target_size, dist; int target_handle; matrix target_orient; msafe_struct mstruct; // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle == OBJECT_HANDLE_NONE || target_handle != memory->pull_target) return; // get the target's info Obj_Value(target_handle, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(target_handle, VF_GET, OBJV_I_ROOMNUM, &start_room); Obj_Value(target_handle, VF_GET, OBJV_M_ORIENT, &target_orient); Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); Obj_Value(target_handle, VF_GET, OBJV_F_SIZE, &target_size); // Calc the direction to pull the target dir = my_pos - start_pos; vm_VectorNormalize(&dir); // Calc the distance to pull the target dist = (Game_GetTime() - memory->last_target_pull_time) * LIFTER_TARGET_PULL_SPEED; // Calc the point to move to end_pos = start_pos + (dir * dist); // See where it is safe to move to flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS; fate = FVI_RayCast(target_handle, &start_pos, &end_pos, start_room, target_size, flags, &ray); landing_pos = ray.hit_point; landing_room = ray.hit_room; // Now move the target to this position mstruct.objhandle = target_handle; mstruct.pos = landing_pos; mstruct.orient = target_orient; mstruct.roomnum = landing_room; MSafe_CallFunction(MSAFE_OBJECT_WORLD_POSITION, &mstruct); // Move the invisible target powerup to its appropriate position MoveTargetProp(target_handle); // Set the last pull time memory->last_target_pull_time = Game_GetTime(); } // Moves the target prop to the appropriate void Lifter::MoveTargetProp(int target_handle) { vector target_pos; int target_room; matrix target_orient; msafe_struct mstruct; Obj_Value(target_handle, VF_GET, OBJV_V_POS, &target_pos); Obj_Value(target_handle, VF_GET, OBJV_I_ROOMNUM, &target_room); Obj_Value(target_handle, VF_GET, OBJV_M_ORIENT, &target_orient); mstruct.objhandle = memory->pull_target_prop; mstruct.pos = target_pos - (target_orient.uvec * 1.4f); mstruct.orient = target_orient; mstruct.roomnum = target_room; MSafe_CallFunction(MSAFE_OBJECT_WORLD_POSITION, &mstruct); } // Creates the beam between me and the target void Lifter::UpdateTractorBeam(int me) { float next_duration = LIFTER_PULL_VALIDATE_INTERVAL; aLightningCreate(memory->pull_source_prop, memory->pull_target_prop, next_duration, 1.0f, 4, tractor_beam_effect_id, next_duration, 1, 255, 255, 255, false); } // All the controls that will be taken away while being force pulled #define PLAYER_CONTROL_MASK \ (PLYV_CMASK_FORWARD | PLYV_CMASK_REVERSE | PLYV_CMASK_LEFT | PLYV_CMASK_RIGHT | PLYV_CMASK_UP | PLYV_CMASK_DOWN | \ PLYV_CMASK_AFTERBURNER) // Takes away the target's movement void Lifter::ConfineTarget(int me) { int target_handle; // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle == OBJECT_HANDLE_NONE) return; // Save the current target handle AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &memory->pull_target); // Make sure it's a pullable target int type; Obj_Value(target_handle, VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_PLAYER) { aTogglePlayerObjControl(0, PLAYER_CONTROL_MASK, memory->pull_target); } // Store our start-of-pull shield level Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &memory->shields_at_pull_start); // Clear the next pull check time memory->next_pull_check_time = Game_GetTime(); mprintf(0, "Target confined...\n"); } // Restores the target's movement void Lifter::ReleaseTarget(int me) { // Make sure we still have a target if (memory->pull_target != OBJECT_HANDLE_NONE && qObjExists(memory->pull_target)) { // Check the type int type; Obj_Value(memory->pull_target, VF_GET, OBJV_I_TYPE, &type); if (type == OBJ_PLAYER) { aTogglePlayerObjControl(1, PLAYER_CONTROL_MASK, memory->pull_target); } } // Release the pull target memory->pull_target = OBJECT_HANDLE_NONE; // Set the next pull check time for a nice delay memory->next_pull_check_time = Game_GetTime() + LIFTER_NEXT_PULL_DELAY + (((float)rand() / (float)RAND_MAX) * LIFTER_NEXT_PULL_VARIANCE); mprintf(0, "Target released.\n"); } // Stops pulling and returns to hostile mode void Lifter::StopPulling(int me) { ReleaseTarget(me); SetMode(me, LIFTER_HOSTILE); } // Switches lifter to alert animation from idle void Lifter::SwitchToAlert(int me) { Obj_SetCustomAnim(me, 10, 12, 0.3f, AIAF_NOTIFY, -1, AS_ALERT); } // Sets the Current Mode void Lifter::SetMode(int me, char mode) { int flags; // mprintf(0, "From mode %d, ",memory->mode); // Clear out any non-Dallas assigned goals AI_SafeSetType(me, AIT_AIS); // Do mode specific setups switch (mode) { case LIFTER_PASSIVE: { mprintf(0, "Passive mode set.\n"); // Disable auto-targeting flags = AIF_DETERMINE_TARGET; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set to team neutral aAISetTeam(AIF_TEAM_NEUTRAL, me); // Set speed for this mode SetMaxSpeed(me, memory->base_speed); // Play the looping idle animation Obj_SetCustomAnim(me, 1, 10, 1.6f, AIAF_LOOPING, lifter_amb_sound_id, -1); } break; case LIFTER_AGITATED: { mprintf(0, "Agitated mode set.\n"); // Disable auto-targeting flags = AIF_DETERMINE_TARGET; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set to team neutral aAISetTeam(AIF_TEAM_NEUTRAL, me); // Set speed for this mode SetMaxSpeed(me, memory->base_speed * LIFTER_AGITATED_SPEED_MOD); } break; case LIFTER_HOSTILE: { mprintf(0, "Hostile mode set.\n"); // Set hostile melee attack flags = AIF_MELEE1; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); flags = AIF_MELEE2; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Enable auto-targeting flags = AIF_DETERMINE_TARGET; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set to team PTMC aAISetTeam(AIF_TEAM_PTMC, me); // Set melee mode AI_SafeSetType(me, AIT_MELEE1); // Set speed for this mode SetMaxSpeed(me, memory->base_speed * LIFTER_HOSTILE_SPEED_MOD); // Set the hostile flag memory->not_hostile_yet = false; } break; case LIFTER_PULLING: { mprintf(0, "Pulling mode set.\n"); // Set pull melee attack flags = AIF_MELEE2; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); flags = AIF_MELEE1; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Disable auto-targeting so it can't acquire a new target flags = AIF_DETERMINE_TARGET; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // NOTE: don't change teams here (needs to keep current target)! // Set melee mode AI_SafeSetType(me, AIT_MELEE1); // Set speed for this mode SetMaxSpeed(me, memory->base_speed * LIFTER_PULLING_SPEED_MOD); // Confine the target's movement ConfineTarget(me); // Move the target prop MoveTargetProp(memory->pull_target); // Clear the last pull time memory->last_target_pull_time = Game_GetTime(); } break; case LIFTER_PASSIFYING: { mprintf(0, "Passifying mode set.\n"); // Disable auto-targeting flags = AIF_DETERMINE_TARGET; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Set to team neutral aAISetTeam(AIF_TEAM_NEUTRAL, me); // Set speed for this mode SetMaxSpeed(me, memory->base_speed); // Play the to idle animation Obj_SetCustomAnim(me, 50, 52, 1.0f, 0, -1, -1); } break; default: mprintf(0, "Lifter is all messed up!\n"); break; } // Set the current mode and reset the mode time memory->mode = mode; memory->mode_time = 0.0f; } // Processes the AI Initialize event void Lifter::DoInit(int me) { // Setup memory data tOSIRISMEMCHUNK ch; ch.id = HT_MEMORY_ID; ch.size = sizeof(lifter_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (lifter_data *)Scrpt_MemAlloc(&ch); // Init base values AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &memory->base_shields); memory->shield_loss_margin = memory->base_shields * LIFTER_LOSE_PULL_SHIELD_PERCENT; memory->pull_target = OBJECT_HANDLE_NONE; // Create and setup the beam pulling target prop object msafe_struct mstruct; vector pos; int room; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); memory->pull_target_prop = Obj_Create(OBJ_POWERUP, powerup_id, room, &pos, NULL, me); mstruct.objhandle = memory->pull_target_prop; MSafe_CallFunction(MSAFE_OBJECT_NO_RENDER, &mstruct); // Create the source tractor beam prop memory->pull_source_prop = CreateAndAttach(me, "STEmitter", OBJ_ROBOT, 1, 0, true, true); // Init time values memory->last_damaged_time = Game_GetTime(); memory->next_pull_check_time = Game_GetTime(); memory->last_target_pull_time = Game_GetTime(); memory->next_lift_beam_time = Game_GetTime(); // Set the attacked flag memory->not_hostile_yet = true; // Create the lifter's stick effect aLightningCreateGunpoints(0, 1, me, 1000000000.0f, 2.0f, 2, lifter_stick_effect_id, 0.5f, 1, 255, 255, 255, false); // Start out in normal attack mode SetMode(me, LIFTER_PASSIVE); } // Process all AI Notify events bool Lifter::DoNotify(int me, tOSIRISEventInfo *data) { if (data->evt_ai_notify.notify_type == AIN_TARGET_DIED) { if (memory->mode == LIFTER_PULLING) { StopPulling(me); } } else if (data->evt_ai_notify.notify_type == AIN_MELEE_HIT) { // If we are pulling if (memory->mode == LIFTER_PULLING) { // Create hit sparks int room; vector pos; Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_GetGunPos(me, 1, &pos); Game_CreateRandomSparks(20, &pos, room); // Stop pulling StopPulling(me); // Create the concussive hit blast Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Value(me, VF_GET, OBJV_V_POS, &pos); // Obj_GetGunPos(me, 2, &pos); Obj_Create(OBJ_WEAPON, lifter_blast_effect_id, room, &pos, NULL, me); } } return true; } // Processes the AI Frame Interval Event void Lifter::DoFrame(int me) { float last_see_time, last_see_game_time; float last_hear_time, last_hear_game_time; float last_perceive_time; float curr_anim_frame; // Get the last see and hear time AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_game_time); AI_Value(me, VF_GET, AIV_F_LAST_HEAR_TARGET_TIME, &last_hear_game_time); // Calculate the time since target was seen or heard last_hear_time = Game_GetTime() - last_hear_game_time; last_see_time = Game_GetTime() - last_see_game_time; // Perceive time is last time target was seen or heard (whichever is smalled) if (last_hear_time <= last_see_time) { last_perceive_time = last_hear_time; } else { last_perceive_time = last_see_time; } // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &curr_anim_frame); // Do mode specific checks switch (memory->mode) { case LIFTER_PASSIVE: { } break; case LIFTER_AGITATED: { // If we've been agitated for a little while, and are damaged enough, switch to hostile if (memory->mode_time > LIFTER_MIN_AGITATED_MODE_TIME) { float curr_shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &curr_shields); if (curr_shields < (memory->base_shields * LIFTER_HOSTILE_SHIELD_PERCENT)) { SetMode(me, LIFTER_HOSTILE); break; } // If we haven't been damaged in a while, become passive again float time_since_damaged; time_since_damaged = Game_GetTime() - memory->last_damaged_time; if (time_since_damaged >= LIFTER_AGITATED_INTEREST_TIME) { SetMode(me, LIFTER_PASSIFYING); break; } } } break; case LIFTER_HOSTILE: { // If hostile for a while, and haven't been damaged in a while, become passive if (memory->mode_time > LIFTER_MIN_HOSTILE_MODE_TIME) { float time_since_damaged; time_since_damaged = Game_GetTime() - memory->last_damaged_time; if (time_since_damaged >= LIFTER_HOSTILE_INTEREST_TIME) { char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); // Wait until the current anim type is alert, then return to passive if (type == AS_ALERT) { SetMode(me, LIFTER_PASSIFYING); break; } } } // See if we should do a tractor beam pull if (Game_GetTime() >= memory->next_pull_check_time) { memory->next_pull_check_time = Game_GetTime() + LIFTER_PULL_CHECK_INTERVAL; // See if it's possible to start pulling target if (OkToPull(me)) { aSoundPlayObject(lifter_pull_sound_id, me, 1.0f); // Switch to pulling mode SetMode(me, LIFTER_PULLING); break; } } } break; case LIFTER_PULLING: { // See if we should update the tractor beam and validate the current pull situation if (Game_GetTime() >= memory->next_pull_check_time) { memory->next_pull_check_time = Game_GetTime() + LIFTER_PULL_VALIDATE_INTERVAL; // See if the pulling operation is still valid if (OkToPull(me, false)) { UpdateTractorBeam(me); } else { // Otherwise, quit pulling StopPulling(me); break; } } // Pull the target forward bool not_multiplayer = true; if (not_multiplayer || (Game_GetTime() - memory->last_target_pull_time) >= LIFTER_PULL_TARGET_INTERVAL) { PullTarget(me); } // Have we reached the maximum pulling time? if (memory->mode_time > LIFTER_MAX_PULL_TIME) { StopPulling(me); break; } } break; case LIFTER_PASSIFYING: { float curr_anim_frame; // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &curr_anim_frame); // See if we can return to passive yet if (curr_anim_frame == 52) { SetMode(me, LIFTER_PASSIVE); break; } } break; default: mprintf(0, "Lifter is all messed up!\n"); break; } // If we're carrying anything, display the carry beam UpdateLiftBeam(me); // Increment the current mode time memory->mode_time += Game_GetFrameTime(); } // Handles the healing effects void Lifter::UpdateLiftBeam(int me) { // See if it's time to create the next energy effect if (memory->next_lift_beam_time <= Game_GetTime()) { float next_duration = LIFTER_LIFT_BEAM_UPDATE_TIME; memory->next_lift_beam_time = Game_GetTime() + next_duration; // See if we're carrying an object int attached_handle = Obj_GetAttachChildHandle(me, 0); if (attached_handle != OBJECT_HANDLE_NONE) { aLightningCreate(me, attached_handle, next_duration, 2.0f, 2, boss_heal_effect_id, next_duration, 1, 255, 255, 255, false); } } } // Processes incoming damage reports void Lifter::DoDamage(int me, tOSIRISEVTDAMAGED *damage_data) { memory->last_damaged_time = Game_GetTime(); if (memory->mode == LIFTER_PASSIVE) { if (memory->not_hostile_yet) { // If not hostile yet, check to see if we should be agitated float curr_shields; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &curr_shields); if (curr_shields < (memory->base_shields * LIFTER_AGITATED_SHIELD_PERCENT)) { SwitchToAlert(me); SetMode(me, LIFTER_AGITATED); return; } } else { // If we've been hostile already, just switch right back into hostile mode SwitchToAlert(me); SetMode(me, LIFTER_HOSTILE); return; } } else if (memory->mode == LIFTER_PULLING) { float curr_shields, damage_done_during_pull; Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &curr_shields); damage_done_during_pull = memory->shields_at_pull_start - curr_shields; if (damage_done_during_pull >= memory->shield_loss_margin) { // Create tractor beam device sparks int room; vector pos; Obj_Value(memory->pull_source_prop, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Value(memory->pull_source_prop, VF_GET, OBJV_V_POS, &pos); Game_CreateRandomSparks(15, &pos, room); // Stop pulling StopPulling(me); } } } // Something collided with lifter void Lifter::DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data) {} // Does any final cleanup when object is destroyed void Lifter::DoCleanUp(int me) { // remove the powerup props msafe_struct mo; mo.objhandle = memory->pull_source_prop; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); mo.objhandle = memory->pull_target_prop; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); // if we're pulling a target, release it if (memory->pull_target != OBJECT_HANDLE_NONE) { ReleaseTarget(me); } } // Receives all basic events and calls processesing functions int16_t Lifter::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: DoDamage(data->me_handle, &data->evt_damaged); break; case EVT_DESTROY: DoCleanUp(data->me_handle); break; case EVT_COLLIDE: DoCollide(data->me_handle, &data->evt_collide); break; case EVT_PLAYER_DIES: if (memory->mode == LIFTER_PULLING && data->evt_player_dies.it_handle == memory->pull_target) { StopPulling(data->me_handle); } break; case EVT_MEMRESTORE: memory = (lifter_data *)data->evt_memrestore.memory_ptr; break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //----------------- // AlienBoss class //----------------- // Sends a command out to alien boss bool AlienBoss::SendCommand(int me, int it, char command, void *ptr) { bot_com com; com.action = command; com.ptr = ptr; tOSIRISEventInfo ei; ei.me_handle = it; ei.extra_info = (void *)&com; ei.evt_ai_notify.notify_type = AIN_USER_DEFINED; ei.evt_ai_notify.it_handle = me; return Obj_CallEvent(it, EVT_AI_NOTIFY, &ei); } // Processes a command from another alien bool AlienBoss::ReceiveCommand(int me, int it, char command, void *ptr) { switch (command) { case AB_COM_THRUSTER_IS_FIRING: { if (memory->mode == AB_HEALING) { // Set to the death mode for final dallas-controlled cinematic SetMode(me, AB_DYING); // Let dallas know that we were healing *(bool *)ptr = true; } else { // Run away from flame if necessary SetMode(me, AB_HIDING_FROM_THRUSTER); // Let dallas know that we weren't healing *(bool *)ptr = false; } } break; case AB_COM_THRUSTER_MISFIRED: { // Time to go protect the nest SetMode(me, AB_GOING_TO_PROTECT_NEST); } break; case AB_COM_ITS_SHOWTIME: { // Make it start out in reverse memory->wander_forward = false; // Set the intro end time memory->intro_end_time = *(float *)ptr; memory->intro_ended = false; // Time to go protect the nest SetMode(me, AB_WANDERING); } break; case AB_COM_START_WAITING_EFFECT: { // Make it start out in reverse memory->waiting_effect_enabled = true; } break; case AB_COM_THRUSTER_IGNITED: { // This makes us angry memory->state = AB_ANGRY; // Set the new damage level memory->damage_threshold = AB_ANGRY_DAMAGE_LEVEL * memory->base_shields; // Make it slightly faster memory->base_speed *= 1.05f; mprintf(0, "Alien Boss is now angry.\n"); } break; case AB_COM_SET_SCENARIO_IDS: { ab_scenario_ids *ids = (ab_scenario_ids *)ptr; memory->nest_handle = ids->nest_handle; memory->left_hide_room = ids->left_hide_room; memory->right_hide_room = ids->right_hide_room; mprintf(0, "Set scenario ids.\n"); } break; case AB_COM_IS_NESTING: { if (memory->mode == AB_HEALING) { *(bool *)ptr = true; } else { *(bool *)ptr = false; } } break; } // mprintf(0, "AlienOrganism action %d failed\n"); return false; } // Sets the alien boss's max speed void AlienBoss::SetMaxSpeed(int me, float speed) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &speed); } // Checks to see if alien is currently on fire bool AlienBoss::IsOnFire(int me) { return (Obj_IsEffect(me, EF_NAPALMED)); } // Sets wandering destination goal for boss void AlienBoss::SetWanderGoal(int me) { vector pos; int room = 0; float dist = 15.0f; // If the destination object is valid, set the goals to go to that object if (memory->dest_object_handle != OBJECT_HANDLE_NONE) { int type; Obj_Value(memory->dest_object_handle, VF_GET, OBJV_I_TYPE, &type); if (type != OBJ_NONE) { AI_AddGoal(me, AIG_GET_TO_OBJ, 2, 1.0f, ALIEN_GUID_GOT_TO_DEST_OBJ, GF_ORIENT_VELOCITY | GF_USE_BLINE_IF_SEES_GOAL | GF_KEEP_AT_COMPLETION | GF_NONFLUSHABLE, memory->dest_object_handle); AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); return; } memory->dest_object_handle = OBJECT_HANDLE_NONE; } // Set the goal to wander AI_AddGoal(me, AIG_WANDER_AROUND, 2, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, room, room); AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } // Sets goal to go to the current wander room void AlienBoss::SetWanderRoomGoal(int me) { vector pos; int roomnum; roomnum = memory->wander_rooms[memory->curr_wander_room_index]; Room_Value(roomnum, VF_GET, RMSV_V_PATH_PNT, &pos, 0); AI_AddGoal(me, AIG_GET_TO_POS, 2, DEFAULT_INFLUENCE, AB_GUID_GOT_TO_WANDER_ROOM, GF_ORIENT_VELOCITY, &pos, roomnum); // Save values for teleport memory->dest_pos = pos; memory->dest_room = roomnum; float dist = 35.0f; AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } // Sets goal to go to the current wander room void AlienBoss::SetProtectNestGoal(int me) { vector pos; int roomnum; // Go to the nest room (first room is list) roomnum = memory->wander_rooms[0]; Room_Value(roomnum, VF_GET, RMSV_V_PATH_PNT, &pos, 0); AI_AddGoal(me, AIG_GET_TO_POS, 2, DEFAULT_INFLUENCE, AB_GUID_GOT_TO_NEST_ROOM, GF_ORIENT_VELOCITY, &pos, roomnum); // Save values for teleport memory->dest_pos = pos; memory->dest_room = roomnum; float dist = 35.0f; AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } // Sets goal to go to the current wander room void AlienBoss::SetHideRoomGoal(int me) { vector pos, left_room_pos, right_room_pos, my_pos; int roomnum; float left_room_dist; float right_room_dist; // Get current position Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); // Get positions of hide rooms Room_Value(memory->left_hide_room, VF_GET, RMSV_V_PATH_PNT, &left_room_pos, 0); Room_Value(memory->right_hide_room, VF_GET, RMSV_V_PATH_PNT, &right_room_pos, 0); // Get the distance to each hide room left_room_dist = vm_VectorDistance(&my_pos, &left_room_pos); right_room_dist = vm_VectorDistance(&my_pos, &right_room_pos); // Determine the closest hide room, and go to it if (left_room_dist < right_room_dist) { roomnum = memory->left_hide_room; pos = left_room_pos; } else { roomnum = memory->right_hide_room; pos = right_room_pos; } Room_Value(roomnum, VF_GET, RMSV_V_PATH_PNT, &pos, 0); AI_AddGoal(me, AIG_GET_TO_POS, 2, DEFAULT_INFLUENCE, AB_GUID_GOT_TO_HIDE_ROOM, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, &pos, roomnum); // Save values for teleport memory->dest_pos = pos; memory->dest_room = roomnum; float dist = 30.0f; AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } // Returns the index of the wander room closest to current position int AlienBoss::DetermineClosestRoom(int me) { int j, closest_room; float closest_dist, dist; vector my_pos, room_pos; // Get current position Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); // Find the room closest to closest_room = 0; closest_dist = 10000; for (j = 0; j < AB_NUM_WANDER_ROOMS; j++) { // Get room's position Room_Value(memory->wander_rooms[j], VF_GET, RMSV_V_PATH_PNT, &room_pos, 0); // Get the distance to this room dist = vm_VectorDistanceQuick(&my_pos, &room_pos); // See if this room is closer than the currently closest room if (dist < closest_dist) { closest_room = j; closest_dist = dist; } } mprintf(0, "Alien Boss - Determined closest wander room: %d\n", closest_room); return closest_room; } // Returns any current animations to alert void AlienBoss::AbortCurrentMode(int me) { switch (memory->mode) { case AB_HEALING: { // Stop the healing fog protection StopHealing(me); } break; case AB_LANDING_AT_NEST: { // Play the takeoff anim and tell it to go to alert next Obj_SetCustomAnim(me, AB_TAKEOFF_START_FRAME, AB_TAKEOFF_END_FRAME, AB_TAKEOFF_ANIM_TIME, AIAF_NOTIFY, -1, AS_ALERT); } break; case AB_SPECIAL_ATTACK: { // Play rest of attack animation and return to alert Obj_SetCustomAnim(me, AB_RECOIL_START_FRAME, AB_RECOIL_END_FRAME, AB_RECOIL_ANIM_TIME, AIAF_NOTIFY, -1, AS_ALERT); } break; } } // Handles the healing effects void AlienBoss::UpdateHealingEnergyBeams(int me) { // See if it's time to create the next energy effect if (memory->next_update_beam_time <= Game_GetTime()) { float next_duration = AB_BEAM_UPDATE_TIME; memory->next_update_beam_time = Game_GetTime() + next_duration; aLightningCreate(memory->tail_pos_handle, memory->nest_handle, next_duration, 1.0f, 2, boss_heal_effect_id, next_duration, 1, 255, 255, 255, false); // Create healing sparks int room; vector pos; Obj_Value(memory->tail_pos_handle, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Value(memory->tail_pos_handle, VF_GET, OBJV_V_POS, &pos); Game_CreateRandomSparks(5, &pos, room); } } // Handles the healing effects void AlienBoss::UpdateDamageEffect(int me) { // See if it's time to create the next energy effect if (memory->next_damage_effect_time <= Game_GetTime()) { memory->next_damage_effect_time = Game_GetTime() + AB_DAMAGE_EFFECT_INTERVAL; float curr_damage; curr_damage = memory->damage; if (curr_damage > memory->damage_threshold) curr_damage = memory->damage_threshold; if (curr_damage > 0.0f && memory->damage_threshold != 0.0f) { float spark_rate; spark_rate = (curr_damage / memory->damage_threshold) * AB_MAX_DAMAGE_SPARK_RATE; aObjSpark(me, spark_rate, AB_DAMAGE_EFFECT_INTERVAL); } // do the following for debugging purposes // Obj_Create(OBJ_WEAPON, teleport_effect_id, memory->dest_room, &memory->dest_pos, NULL, me); } } // Start healing fog protection void AlienBoss::StartHealing(int me) { // Set the green healing fog aRoomChangeFog(memory->wander_rooms[0], 0.0f, 1.0f, 0.0f, 800.0f, 2.0f); // Set the room damage aRoomSetDamage(memory->wander_rooms[0], 5.0f, 1); } // End healing fog protection void AlienBoss::StopHealing(int me) { // Set the green healing fog aRoomChangeFog(memory->wander_rooms[0], 0.0f, 1.0f, 0.0f, 9000.0f, 4.0f); // Set the room damage aRoomSetDamage(memory->wander_rooms[0], 0.0f, 1); } // Checks if the given mode should do stuck prevention bool AlienBoss::ModeIsStuckSensitive(int mode) { if (mode == AB_WANDERING || mode == AB_GOING_TO_NEST || mode == AB_LANDING_AT_NEST || mode == AB_GOING_TO_PROTECT_NEST) { return true; } return false; } // Sets the Current Mode void AlienBoss::SetMode(int me, char mode) { mprintf(0, "From mode %d, ", memory->mode); int flags; char movement_type; vector vel; // Clear out any goals AI_SafeSetType(me, AIT_AIS); // Set normal FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Turn off ranged firing flags = AIF_DISABLE_FIRING; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Turn off melee attacks flags = AIF_MELEE1 | AIF_MELEE2; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Make it always aware flags = AIF_FORCE_AWARENESS; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Do mode specific setups switch (mode) { case AB_GOING_TO_NEST: { mprintf(0, "Going to Nest mode set.\n"); ray_info ray; int fate; vector end_pos, landing_pos; int end_room, landing_room; // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Calculate destination home point a int16_t distance from actual landing point end_pos = memory->home_pos + (memory->home_uvec * 20.0f); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &memory->home_pos, &end_pos, memory->home_room, 0.0f, flags, &ray); landing_pos = ray.hit_point; landing_room = ray.hit_room; // Set goal to head home AI_AddGoal(me, AIG_GET_TO_POS, 2, 1.0f, AB_GUID_GOT_TO_NEST, GF_ORIENT_VELOCITY, &landing_pos, landing_room); AI_SetGoalCircleDist(me, 2, 2.0f); // Save values for teleport memory->dest_pos = landing_pos; memory->dest_room = landing_room; // Set returning home speed SetMaxSpeed(me, memory->base_speed * AB_RETURN_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case AB_LANDING_AT_NEST: { mprintf(0, "Landing at Nest mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn on point-collide-walls and lock axes flags = PF_POINT_COLLIDE_WALLS | PF_LOCK_MASK; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn off auto-level flags = PF_LEVELING; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Make him land at his home point AI_AddGoal(me, AIG_GET_TO_POS, 2, 100.0f, AB_GUID_LANDED, GF_NONFLUSHABLE | GF_KEEP_AT_COMPLETION | GF_ORIENT_SCRIPTED, &memory->home_pos, memory->home_room); AI_SetGoalCircleDist(me, 2, 0.0f); // Save values for teleport memory->dest_pos = memory->home_pos; memory->dest_room = memory->home_room; // Play his landing animation Obj_SetCustomAnim(me, AB_LANDING_START_FRAME, AB_LANDING_END_FRAME, AB_LANDING_ANIM_TIME, 0, -1, -1); // Set landing speed SetMaxSpeed(me, memory->base_speed * AB_LAND_SPEED_MOD); // Clear the next activity time memory->next_activity_time = 0.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case AB_HEALING: { mprintf(0, "Healing mode set.\n"); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn off auto-level flags = PF_LEVELING; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn on point-collide-walls and lock axes flags = PF_POINT_COLLIDE_WALLS | PF_LOCK_MASK; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // No movement allowed movement_type = MT_NONE; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Play his landing animation Obj_SetCustomAnim(me, AB_HEALING_START_FRAME, AB_HEALING_END_FRAME, AB_HEALING_ANIM_TIME, AIAF_LOOPING, -1, -1); // Set landed speed SetMaxSpeed(me, 0.0f); // Set the next activity time memory->next_activity_time = Game_GetTime() + ALIEN_LANDED_ACTION_TIME; // Start healing protection StartHealing(me); } break; case AB_WAITING: { mprintf(0, "Waiting mode set.\n"); // Turn off dodging flags = AIF_DODGE; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn off auto-level flags = PF_LEVELING; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn on point-collide-walls and lock axes flags = PF_POINT_COLLIDE_WALLS | PF_LOCK_MASK; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // No movement allowed movement_type = MT_NONE; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Play his landing animation Obj_SetCustomAnim(me, AB_HEALING_START_FRAME, AB_HEALING_END_FRAME, AB_HEALING_ANIM_TIME, AIAF_LOOPING, -1, -1); // Set landed speed SetMaxSpeed(me, 0.0f); } break; case AB_WANDERING: { mprintf(0, "Wandering mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Get Closest wander room memory->curr_wander_room_index = DetermineClosestRoom(me); SetWanderRoomGoal(me); // Set wander speed if (memory->intro_ended) { SetMaxSpeed(me, memory->base_speed * AB_WANDER_SPEED_MOD); } else { // wander at fixed speed for the intro SetMaxSpeed(me, 30.0f); } // Set the next activity check to happen within .4 to .8 seconds memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 3.0f + 3.0f; } break; case AB_ATTACKING: { mprintf(0, "Attacking mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set attack FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_EVADER1); // Turn on ranged firing flags = AIF_DISABLE_FIRING; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &flags); // Turn on melee attacks flags = AIF_MELEE1 | AIF_MELEE2; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * AB_ATTACK_SPEED_MOD); // Set the next activity time memory->next_activity_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.0f; // Clear the next wander time memory->max_wander_time = 0.0f; } break; case AB_MELEE_ATTACK: { mprintf(0, "Melee Attack mode set.\n"); // Set attack FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Turn on melee attacks flags = AIF_MELEE1 | AIF_MELEE2; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_MELEE1); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * AB_MELEE_ATTACK_SPEED_MOD); } break; case AB_PREPARE_SPECIAL_ATTACK: { mprintf(0, "Prepare Special Attack mode set.\n"); // Set attack FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_EVADER1); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * AB_SPEC_ATTACK_SPEED_MOD); } break; case AB_SPECIAL_ATTACK: { mprintf(0, "Special Attack mode set.\n"); // Set attack FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_EVADER1); // Play special attack animation Obj_SetCustomAnim(me, AB_ATTACK_START_FRAME, AB_ATTACK_END_FRAME, AB_ATTACK_ANIM_TIME, 0, -1, -1); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * AB_SPEC_ATTACK_SPEED_MOD); } break; case AB_SPECIAL_ATTACK_RECOIL: { mprintf(0, "Special Attack Recoil mode set.\n"); // Set attack FOV to 360 float fov = -1.0f; AI_Value(me, VF_SET, AIV_F_FOV, &fov); // Only set goals if not currently in squad formation AI_SafeSetType(me, AIT_EVADER1); // Play special attack animation Obj_SetCustomAnim(me, AB_RECOIL_START_FRAME, AB_RECOIL_END_FRAME, AB_RECOIL_ANIM_TIME, AIAF_NOTIFY, -1, AS_ALERT); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed * AB_SPEC_ATTACK_SPEED_MOD); } break; case AB_FLEEING: { mprintf(0, "Fleeing mode set.\n"); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Set Goal to go somewhere else SetWanderGoal(me); // Set Fleeing Speed SetMaxSpeed(me, memory->base_speed * AB_FLEE_SPEED_MOD); // Set the max time to flee (base time somewhat off of damage) float time_percent = (float(memory->fire_flee_chance) / 100.0f); memory->max_wander_time = ((float)rand() / (float)RAND_MAX) * 2.0f + AB_MAX_FLEE_TIME * time_percent; // Decrease the flee chance for next time memory->fire_flee_chance -= AB_FLEE_CHANCE_DECREMENT; if (memory->fire_flee_chance < 0.0f) memory->fire_flee_chance = 0.0f; } break; case AB_HIDING_FROM_THRUSTER: { mprintf(0, "Hiding from Thruster mode set.\n"); // Abort whatever mode we are in AbortCurrentMode(me); // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Determine closest hide room and go to it SetHideRoomGoal(me); // Set returning home speed SetMaxSpeed(me, memory->base_speed * AB_RETURN_SPEED_MOD); } break; case AB_GOING_TO_PROTECT_NEST: { mprintf(0, "Going to Protect Nest mode set.\n"); // Abort whatever mode we are in AbortCurrentMode(me); // Set to protective state memory->state = AB_PROTECTIVE; // Set the new damage level memory->damage_threshold = AB_PROTECTIVE_DAMAGE_LEVEL * memory->base_shields; // If currently landed, set take off velocity DoTakeoff(me, 8.0f, 3.0f); // Set movement type back to physics for flying around movement_type = MT_PHYSICS; Obj_Value(me, VF_SET, OBJV_C_MOVEMENT_TYPE, &movement_type); // Return to nest room SetProtectNestGoal(me); // Set returning home speed SetMaxSpeed(me, memory->base_speed * AB_RETURN_SPEED_MOD); } break; case AB_DYING: { mprintf(0, "Dying mode set.\n"); // Set Engage and Attack Speeds SetMaxSpeed(me, memory->base_speed); // Stop the healing room effects aRoomChangeFog(memory->wander_rooms[0], 0.0f, 1.0f, 0.0f, 9000.0f, 6.0f); // Set the room damage aRoomSetDamage(memory->wander_rooms[0], 0.0f, 1); } break; case AB_TELEPORTING: { mprintf(0, "Teleporting mode set.\n"); // Start cloaking aCloakObject(me, 0.0f); // Save current mode so we can return to it once teleport is done memory->mode_prior_teleporting = memory->mode; // set transfer flag memory->did_transfer = false; } break; default: mprintf(0, "Alien Boss is all messed up!\n"); } // Reset the landing flags memory->done_moving = false; memory->done_turning = false; // Set the current mode and reset the mode time memory->mode = mode; memory->mode_time = 0.0f; // If we just switched to a stuck sensitive mode, clear out the values if (ModeIsStuckSensitive(memory->mode)) { memory->next_check_if_stuck_time = Game_GetTime() + AB_CHECK_IF_STUCK_INTERVAL; memory->squared_dist_moved = 0.0f; Obj_Value(me, VF_GET, OBJV_V_POS, &memory->last_pos); } } // Handles alien takeoffs void AlienBoss::DoTakeoff(int me, float takeoff_speed, float speed_variance) { // If currently landed, set take off velocity if (memory->mode == AB_HEALING || memory->mode == AB_WAITING) { vector vel; int flags; Obj_Value(me, VF_GET, OBJV_V_VELOCITY, &vel); vel += (memory->home_uvec * (takeoff_speed + ((float)rand() / (float)RAND_MAX) * speed_variance)); Obj_Value(me, VF_SET, OBJV_V_VELOCITY, &vel); // Play the takeoff anim and tell it to go to alert next Obj_SetCustomAnim(me, AB_TAKEOFF_START_FRAME, AB_TAKEOFF_END_FRAME, AB_TAKEOFF_ANIM_TIME, AIAF_NOTIFY, -1, AS_ALERT); // Clear wall point collision and axes lock flags = PF_LOCK_MASK | PF_POINT_COLLIDE_WALLS; Obj_Value(me, VF_CLEAR_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); // Turn on dodging flags = AIF_DODGE; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &flags); // enable auto-leveling flags = PF_LEVELING; Obj_Value(me, VF_SET_FLAGS, OBJV_I_PHYSICS_FLAGS, &flags); } } // Processes the AI Initialize event void AlienBoss::DoInit(int me) { int flags; msafe_struct m; m.objhandle = me; // Setup memory data tOSIRISMEMCHUNK ch; ch.id = ALIEN_MEMORY_ID; ch.size = sizeof(alienboss_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (alienboss_data *)Scrpt_MemAlloc(&ch); // Init base values AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); Obj_Value(me, VF_GET, OBJV_F_SHIELDS, &memory->base_shields); // Clear the destination object handle memory->dest_object_handle = OBJECT_HANDLE_NONE; // Alien hasn't been hit by player yet memory->hit_by_player = false; memory->damage = 0.0f; memory->damage_threshold = AB_NORMAL_DAMAGE_LEVEL * memory->base_shields; memory->fire_flee_chance = AB_STARTING_FLEE_CHANCE; memory->curr_wander_room_index = 0; memory->wander_forward = true; memory->protected_nest = false; memory->waiting_effect_enabled = false; memory->intro_ended = false; // Update the energy effect as soon as charge exists memory->next_update_beam_time = Game_GetTime(); // Do special damage as soon as relevant memory->next_special_damage_time = Game_GetTime(); memory->next_damage_effect_time = Game_GetTime(); // Do special attack as soon as relevant memory->next_special_attack_time = Game_GetTime(); memory->next_melee_attack_time = Game_GetTime(); // Set the next generic check time memory->next_generic_check_time = Game_GetTime() + ((float)rand() / (float)RAND_MAX) * 1.0f + 0.5f; // Init other times memory->next_activity_time = Game_GetTime(); memory->max_wander_time = 0.0f; // Store Home Information Obj_Value(me, VF_GET, OBJV_V_POS, &memory->home_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &memory->home_room); matrix orient; Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); memory->home_fvec = orient.fvec; vm_VectorNormalize(&memory->home_fvec); memory->home_uvec = orient.uvec; vm_VectorNormalize(&memory->home_uvec); // Save values for teleport memory->dest_pos = memory->home_pos; memory->dest_room = memory->home_room; // stuck check stuff memory->squared_dist_moved = 0.0f; memory->next_check_if_stuck_time = Game_GetTime(); memory->last_pos = memory->home_pos; // Init the props memory->pos1_handle = OBJECT_HANDLE_NONE; memory->pos2_handle = OBJECT_HANDLE_NONE; memory->pos3_handle = OBJECT_HANDLE_NONE; // Create and setup the energy effect props msafe_struct mstruct; memory->tail_pos_handle = CreateAndAttach(me, "STEmitter", OBJ_ROBOT, 0, 0, true, true); mstruct.objhandle = memory->tail_pos_handle; MSafe_CallFunction(MSAFE_OBJECT_NO_RENDER, &mstruct); // Init the scenario ID's memory->nest_handle = OBJECT_HANDLE_NONE; memory->left_hide_room = -1; memory->right_hide_room = -1; // Do Lookups DoCustomLookups(); // Set the healing fog in room aRoomSetFog(memory->wander_rooms[0], 0.0f, 1.0f, 0.0f, 9000.0f); // Set to normal state memory->state = AB_NORMAL; // Set starting mode // SetMode(me, AB_GOING_TO_NEST); // for debugging SetMode(me, AB_WAITING); // Play looping boss sound aSoundPlayObject(boss_flapping_id, me, 1.0f); mprintf(0, "Alien boss initialized.\n"); } // Does custom name lookups void AlienBoss::DoCustomLookups(void) { int j; // Lookup the wander room names for (j = 0; j < AB_NUM_WANDER_ROOMS; j++) { memory->wander_rooms[j] = Scrpt_FindRoomName(AB_WanderRoomNames[j]); if (memory->wander_rooms[j] < 0) mprintf(0, "Alien Boss Error: Room not found - %s", AB_WanderRoomNames[j]); } } // Checks to see if tractor beam pulling can commence/continue bool AlienBoss::OkToStartSpecialAttack(int me) { matrix orient; vector vec_to_target; int target_handle; float fov_angle; // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle == OBJECT_HANDLE_NONE) return false; // Make sure the target isn't cloaked if (qObjectCloakTime(target_handle) != 0.0f) return false; // check distance to target float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); if (dist > AB_MAX_SPECIAL_ATTACK_DIST) return false; // check FOV position of target (greater than 45 degrees is too much) Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); // NOTE: do my own vec to target here! (chris' doesn't update quick enough) AI_Value(me, VF_GET, AIV_V_VEC_TO_TARGET, &vec_to_target); vm_VectorNormalize(&vec_to_target); fov_angle = orient.fvec * vec_to_target; if (fov_angle < 0.7f) return false; return true; } // Checks to see if tractor beam pulling can commence/continue bool AlienBoss::DoStingAttack(int me) { matrix orient; vector vec_to_target; int target_handle; float fov_angle; // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle == OBJECT_HANDLE_NONE) return false; // check distance to target float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); if (dist > AB_MAX_STING_DIST) return false; // check FOV position of target (greater than 45 degrees is too much) Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); // NOTE: do my own vec to target here! (chris' doesn't update quick enough) AI_Value(me, VF_GET, AIV_V_VEC_TO_TARGET, &vec_to_target); vm_VectorNormalize(&vec_to_target); fov_angle = orient.fvec * vec_to_target; if (fov_angle < 0.7f) return false; // see if anything is in the way ray_info ray; int flags, fate; vector start_pos, end_pos, landing_pos; int start_room, landing_room; float target_size; Obj_Value(me, VF_GET, OBJV_V_POS, &start_pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &start_room); Obj_Value(target_handle, VF_GET, OBJV_V_POS, &end_pos); Obj_Value(target_handle, VF_GET, OBJV_F_SIZE, &target_size); flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS | FQ_IGNORE_WEAPONS | FQ_IGNORE_MOVING_OBJECTS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS; fate = FVI_RayCast(me, &start_pos, &end_pos, start_room, target_size, flags, &ray); if (fate != HIT_NONE) return false; // Create the lightning effect for the sting attack aLightningCreate(target_handle, memory->tail_pos_handle, 0.4f, 1.0f, 3, transfer_effect_id, 0.4f, 1, 255, 255, 255, false); // Damage the target aObjApplyDamage(target_handle, AB_STING_DAMAGE); // Play damaged sound for target mprintf(0, "Did sting attack.\n"); return true; } // Processes the AI Frame Interval Event void AlienBoss::DoFrame(int me) { int flags; float anim_frame; float last_see_time, last_see_game_time; float last_hear_time, last_hear_game_time; float last_perceive_time; // If we're waiting, bail outta here if (memory->mode == AB_WAITING) { // display healing effects for intro if (memory->waiting_effect_enabled) { // Handle the healing energy beam effects UpdateHealingEnergyBeams(me); } return; } // Get the last see and hear time AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_game_time); AI_Value(me, VF_GET, AIV_F_LAST_HEAR_TARGET_TIME, &last_hear_game_time); // Calculate the time since target was seen and heard last_hear_time = Game_GetTime() - last_hear_game_time; last_see_time = Game_GetTime() - last_see_game_time; // Perceive time is last time target was seen or heard (whichever is smalled) if (last_hear_time <= last_see_time) { last_perceive_time = last_hear_time; } else { last_perceive_time = last_see_time; } // If alien boss was hit by player, take action if necessary if (memory->hit_by_player) { // reset the hit by player flag memory->hit_by_player = false; } // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &anim_frame); // Handle generic checks (pertains to more than 1 mode) if (memory->next_generic_check_time <= Game_GetTime()) { } // Handle mode specific operations switch (memory->mode) { case AB_HEALING: { // See if we should do a round of healing if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + AB_HEALING_ACTION_TIME; // Heal Some Damage float heal_amount = memory->base_shields * AB_HEAL_AMOUNT_PERCENT; memory->damage -= heal_amount; mprintf(0, "Healing %.1f by %.1f...\n", memory->damage, heal_amount); bool done_healing = false; if (memory->damage <= 0.0f) { memory->damage = 0.0f; done_healing = true; } // If we're all healed up, return to attack mode if (done_healing) { StopHealing(me); SetMode(me, AB_ATTACKING); break; } } // Handle the healing energy beam effects UpdateHealingEnergyBeams(me); } break; case AB_DYING: { // Allow the healing effects to play for a few seconds if (memory->mode_time < 4.5f) { UpdateHealingEnergyBeams(me); } } break; case AB_HIDING_FROM_THRUSTER: { // Don't hide for more than a few seconds at most if (memory->mode_time > 6.0f) { SetMode(me, AB_ATTACKING); break; } } break; case AB_GOING_TO_PROTECT_NEST: { // If we've seen a target, switch into attack mode if (memory->protected_nest && last_perceive_time <= 2.0f) { SetMode(me, AB_ATTACKING); break; } } break; case AB_TELEPORTING: { if (memory->mode_time > 2.0f) { // Set to previous mode SetMode(me, memory->mode_prior_teleporting); break; } else if (!memory->did_transfer && memory->mode_time > 1.0f) { vector pos; int room; // Do effect before Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, teleport_effect_id, room, &pos, NULL, me); // Move self Obj_Value(me, VF_SET, OBJV_V_POS, &memory->dest_pos); Obj_Value(me, VF_SET, OBJV_I_ROOMNUM, &memory->dest_room); // Do effect after Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, teleport_effect_id, room, &pos, NULL, me); memory->did_transfer = true; } } break; case AB_LANDING_AT_NEST: { // Play his landed animation if done with landing anim if (anim_frame == AB_LANDING_END_FRAME) { Obj_SetCustomAnim(me, AB_HEALING_START_FRAME, AB_HEALING_END_FRAME, AB_HEALING_ANIM_TIME, AIAF_LOOPING, -1, -1); } if (memory->done_turning == true && memory->done_moving == true) { SetMode(me, AB_HEALING); break; } } break; case AB_GOING_TO_NEST: { char type, next_type; // If we're in alert, or the next anim mode is alert, and we're done moving, land at home if (memory->done_moving == true) { AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); if (type == AS_ALERT || type == AS_IDLE || next_type == AS_ALERT || next_type == AS_IDLE) { SetMode(me, AB_LANDING_AT_NEST); break; } } } break; case AB_WANDERING: { // If we've seen a target, switch into attack mode if (memory->intro_ended && last_perceive_time <= 2.0f) { // Play the saw player shriek aSoundPlayObject(boss_see_id, me, 1.0f); SetMode(me, AB_ATTACKING); break; } // See if intro has ended yet if (!memory->intro_ended) { if (memory->mode_time > memory->intro_end_time) { // Set the normal wander speed SetMaxSpeed(me, memory->base_speed * AB_WANDER_SPEED_MOD); // set the intro ended flag memory->intro_ended = true; mprintf(0, "Intro ended for alien boss.\n"); } } } break; case AB_FLEEING: { // See if it is time to stop running if (memory->mode_time >= memory->max_wander_time) { // Go back to attack mode for now SetMode(me, AB_ATTACKING); } } break; case AB_ATTACKING: { // Do we need to heal? if (memory->damage > memory->damage_threshold) { aSoundPlayObject(boss_hurt_id, me, 1.0f); SetMode(me, AB_GOING_TO_NEST); break; } // See if we should do a round of healing if (Game_GetTime() >= memory->next_activity_time) { // Set the next time to do something memory->next_activity_time = Game_GetTime() + AB_ATTACK_CHECK_INTERVAL; // Should we flee? if (memory->state == AB_NORMAL && IsOnFire(me) && (rand() % 100) < memory->fire_flee_chance) { aSoundPlayObject(boss_hurt_id, me, 1.0f); SetMode(me, AB_FLEEING); break; } // Should we do a special attack? if (Game_GetTime() >= memory->next_special_attack_time) { if (OkToStartSpecialAttack(me)) { aSoundPlayObject(boss_see_id, me, 1.0f); memory->next_special_attack_time = Game_GetTime() + AB_SPECIAL_ATTACK_DELAY + ((float)rand() / (float)RAND_MAX) * AB_SPECIAL_ATTACK_VARIANCE; SetMode(me, AB_PREPARE_SPECIAL_ATTACK); break; } } // Should we switch to melee mode? if (Game_GetTime() >= memory->next_melee_attack_time) { int target_handle; // Make sure we still have a target AI_Value(me, VF_GET, AIV_I_TARGET_HANDLE, &target_handle); if (target_handle != OBJECT_HANDLE_NONE) { // check distance to target float dist; AI_Value(me, VF_GET, AIV_F_DIST_TO_TARGET, &dist); if (dist < AB_MELEE_DIST) { SetMode(me, AB_MELEE_ATTACK); break; } } } } // If haven't perceived target for a while, return to wander mode if (last_perceive_time >= 10.0f) { // If we're not in a protective state, go ahead and wander if (memory->state == AB_PROTECTIVE) { // See if we're in the nest room int curr_room; Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &curr_room); if (curr_room != memory->wander_rooms[0]) { // Go protect nest SetMode(me, AB_GOING_TO_PROTECT_NEST); break; } } else { SetMode(me, AB_WANDERING); break; } } } break; case AB_MELEE_ATTACK: { // Wait until the melee attack time has expired if (memory->mode_time > AB_MAX_MELEE_TIME) { memory->next_melee_attack_time = Game_GetTime() + AB_MELEE_ATTACK_DELAY + ((float)rand() / (float)RAND_MAX) * AB_MELEE_ATTACK_VARIANCE; SetMode(me, AB_ATTACKING); break; } } break; case AB_PREPARE_SPECIAL_ATTACK: { char type, next_type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); AI_Value(me, VF_GET, AIV_C_NEXT_ANIMATION_TYPE, &next_type); // Wait until the current or next anim type is alert, then try special attack if (type == AS_ALERT || next_type == AS_ALERT) { SetMode(me, AB_SPECIAL_ATTACK); break; } } break; case AB_SPECIAL_ATTACK: { // Wait until the fire frame is up if (anim_frame == AB_ATTACK_END_FRAME) { // Create a frag burst effect vector pos; int room; matrix orient; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); Obj_Create(OBJ_WEAPON, boss_frag_burst_id, room, &pos, &orient, me); // Go to recover mode SetMode(me, AB_SPECIAL_ATTACK_RECOIL); break; } } break; case AB_SPECIAL_ATTACK_RECOIL: { char type; AI_Value(me, VF_GET, AIV_C_ANIMATION_TYPE, &type); // Wait until the current anim type is alert, then return to normal if (type == AS_ALERT) { SetMode(me, AB_ATTACKING); break; } } break; } // Do the damage effect if necessary UpdateDamageEffect(me); // If necessary, check if we're stuck if (ModeIsStuckSensitive(memory->mode)) { vector curr_pos, dist_vec; // Update the values Obj_Value(me, VF_GET, OBJV_V_POS, &curr_pos); dist_vec = curr_pos - memory->last_pos; memory->squared_dist_moved += (dist_vec.x * dist_vec.x + dist_vec.y * dist_vec.y + dist_vec.z * dist_vec.z); memory->last_pos = curr_pos; // See if we need to do a stuck check if (Game_GetTime() >= memory->next_check_if_stuck_time) { // Calc the next time to check for stuckitude memory->next_check_if_stuck_time = Game_GetTime() + AB_CHECK_IF_STUCK_INTERVAL; mprintf(0, "Checking to see if Alien Boss is stuck (dist*dist=%f)...\n", memory->squared_dist_moved); // Check if the distance moved indicates being stuck if (memory->squared_dist_moved < (AB_CHECK_IF_STUCK_DIST * AB_CHECK_IF_STUCK_DIST)) { mprintf(0, "Alien Boss is stuck... now teleporting to destination.\n"); // Teleport boss to new location SetMode(me, AB_TELEPORTING); } else { // Clear the distance moved memory->squared_dist_moved = 0.0f; } } } // Increment the current mode time memory->mode_time += Game_GetFrameTime(); } // Process all AI Notify events bool AlienBoss::DoNotify(int me, tOSIRISEventInfo *data) { if (IsGoalFinishedNotify(data->evt_ai_notify.notify_type)) { // Handle goal complete events switch (data->evt_ai_notify.goal_uid) { case AB_GUID_GOT_TO_NEST: memory->done_moving = true; break; case AB_GUID_LANDED: memory->done_moving = true; break; case AB_GUID_GOT_TO_NEST_ROOM: { // if we're going to protect nest, switch into attack mode if (memory->mode == AB_GOING_TO_PROTECT_NEST) { mprintf(0, "Alien Boss got to nest room.\n"); // If we haven't protected nest yet, do special stuff if (!memory->protected_nest) { int minion1_handle, minion2_handle, player_handle; minion1_handle = Scrpt_FindObjectName("BossMinion1"); minion2_handle = Scrpt_FindObjectName("BossMinion2"); // Unghost the minions aObjGhostSet(0, minion1_handle); aObjGhostSet(0, minion2_handle); // Turn on their AI aAISetState(1, minion1_handle); aAISetState(1, minion2_handle); // Send them to get the closest player player_handle = FindClosestPlayer(minion1_handle); SendCommand(minion1_handle, minion1_handle, ALIEN_COM_HUNT_TO_OBJ, &player_handle); SendCommand(minion2_handle, minion2_handle, ALIEN_COM_HUNT_TO_OBJ, &player_handle); // Play Shriek aSoundPlayObject(boss_see_id, me, 1.0f); // Set the protected nest flag memory->protected_nest = true; } SetMode(me, AB_ATTACKING); break; } } break; case AB_GUID_GOT_TO_HIDE_ROOM: { // if we're going to protect nest, switch into attack mode if (memory->mode == AB_HIDING_FROM_THRUSTER) { mprintf(0, "Alien boss got to hide room.\n"); // SetMode(me,AB_ATTACKING); // break; } } break; case AB_GUID_GOT_TO_WANDER_ROOM: { // Make sure we're in wander mode if (memory->mode == AB_WANDERING) { // Decide whether or not to reverse directions if ((rand() % 100) < 4) { memory->wander_forward = (memory->wander_forward) ? false : true; mprintf(0, "Alien Boss - Reversing wander direction\n"); } // Check whether to follow list in reverse or not if (memory->wander_forward) { memory->curr_wander_room_index++; if (memory->curr_wander_room_index >= AB_NUM_WANDER_ROOMS) { memory->curr_wander_room_index = 0; } } else { memory->curr_wander_room_index--; if (memory->curr_wander_room_index < 0) { memory->curr_wander_room_index = AB_NUM_WANDER_ROOMS - 1; } } // Set the goal to go to the next room SetWanderRoomGoal(me); // Play the wandering sound aSoundPlayObject(boss_turf_id, me, 1.0f); mprintf(0, "Alien Boss - Wandering to room: %d\n", memory->curr_wander_room_index); } } break; case AB_GUID_GOT_TO_DEST_OBJ: memory->dest_object_handle = OBJECT_HANDLE_NONE; SetMode(me, memory->mode); mprintf(0, "Alien Boss got to destination object.\n"); break; } } else if (data->evt_ai_notify.notify_type == AIN_SCRIPTED_ORIENT) { // Handle custom orientation events if (memory->mode == AB_LANDING_AT_NEST) { memory->done_turning = (AI_TurnTowardsVectors(me, &memory->home_fvec, &memory->home_uvec) != 0); } } else if (data->evt_ai_notify.notify_type == AIN_MELEE_HIT) { // Create hit sparks int room; vector pos; Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_GetGunPos(me, 0, &pos); Game_CreateRandomSparks(30, &pos, room); // Create the concussive hit blast Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Create(OBJ_WEAPON, lifter_blast_effect_id, room, &pos, NULL, me); } else if (data->evt_ai_notify.notify_type == AIN_USER_DEFINED) { bot_com *com = (bot_com *)data->extra_info; return ReceiveCommand(me, data->evt_ai_notify.it_handle, com->action, com->ptr); } return true; } // Handles application of damage to an alien void AlienBoss::DoDamage(int me, tOSIRISEVTDAMAGED *damage_data) { // Handle fire damage if (damage_data->damage_type == GD_FIRE) { memory->damage += (damage_data->damage * 0.6f); return; } // Handle damage by matter weapons if (damage_data->damage_type == GD_MATTER) { memory->damage += (damage_data->damage * 0.7f); if (Game_GetTime() >= memory->next_special_damage_time) { memory->next_special_damage_time = Game_GetTime() + 0.8f + ((float)rand() / (float)RAND_MAX) * 0.5f; // Create a frag burst effect vector pos; int room; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Obj_Create(OBJ_WEAPON, frag_burst_effect_id, room, &pos, NULL, me); } return; } // Handle damage by energy weapons if ((damage_data->damage_type == GD_ENERGY || damage_data->damage_type == GD_ELECTRIC) && Game_GetTime() >= memory->next_special_damage_time) { // Check if we're currently susceptible to energy damage if (false) { memory->next_special_damage_time = Game_GetTime() + 0.1f + ((float)rand() / (float)RAND_MAX) * 0.2f; // Do double damage memory->damage += (damage_data->damage * 1.5f); // Create sparks int room; vector pos; Obj_Value(me, VF_GET, OBJV_V_POS, &pos); Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, &room); Game_CreateRandomSparks(30 + int(((float)rand() / (float)RAND_MAX) * 10.0f), &pos, room); return; } else { // Do standard energy damage memory->damage += (damage_data->damage * 0.3f); return; } } // Apply standard damage mod memory->damage += (damage_data->damage * 0.1f); } // Handles attacks on aliens void AlienBoss::DoCollide(int me, tOSIRISEVTCOLLIDE *collide_data) { // See if it's a player or player weapon that hit us if (qObjIsPlayerOrPlayerWeapon(collide_data->it_handle)) { // mprintf(0,"Hit by player!\n"); memory->hit_by_player = true; } } // Does any final cleanup when object is destroyed void AlienBoss::DoCleanUp(int me) { // remove the powerup props msafe_struct mo; mo.objhandle = memory->tail_pos_handle; MSafe_CallFunction(MSAFE_OBJECT_REMOVE, &mo); } // Receives all basic events and calls processesing functions int16_t AlienBoss::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: { DoDamage(data->me_handle, &data->evt_damaged); // If boss is slightly damaged, restore its hitpoints (so it can never die!) float curr_shields; Obj_Value(data->me_handle, VF_GET, OBJV_F_SHIELDS, &curr_shields); if (curr_shields < memory->base_shields * 0.75f) { Obj_Value(data->me_handle, VF_SET, OBJV_F_SHIELDS, &memory->base_shields); } // Only allow it to take very little damage (must take some or it doesn't catch on fire) data->evt_damaged.damage = 0.000001f; } break; case EVT_DESTROY: DoCleanUp(data->me_handle); break; case EVT_COLLIDE: DoCollide(data->me_handle, &data->evt_collide); break; case EVT_MEMRESTORE: memory = (alienboss_data *)data->evt_memrestore.memory_ptr; DoCustomLookups(); break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //----------------------- // Security Camera class //----------------------- // Sends a command out to another alien bool SecurityCamera::SendCommand(int me, int it, char command, void *ptr) { bot_com com; com.action = command; com.ptr = ptr; tOSIRISEventInfo ei; ei.me_handle = it; ei.extra_info = (void *)&com; ei.evt_ai_notify.notify_type = AIN_USER_DEFINED; ei.evt_ai_notify.it_handle = me; return Obj_CallEvent(it, EVT_AI_NOTIFY, &ei); } // Processes a command from another alien bool SecurityCamera::ReceiveCommand(int me, int it, char command, void *ptr) { switch (command) { // Alien is asked to return current mode case SC_COM_GET_ALERT_STATUS: *(bool *)ptr = memory->alerted; return true; break; } return false; } // Sets the Current Mode void SecurityCamera::SetMode(int me, char mode) { int flags; float curr_anim_frame; // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &curr_anim_frame); // mprintf(0, "From mode %d, ",memory->mode); // Do mode specific setups switch (mode) { case SC_PANNING: { float time; // Get the camera ready for looping memory->panning_state = SC_WARMING_UP; if (curr_anim_frame > SC_MAX_FRAME) curr_anim_frame = SC_MAX_FRAME; time = (SC_MAX_FRAME - curr_anim_frame) / SC_PAN_SPEED; Obj_SetCustomAnim(me, curr_anim_frame, SC_MAX_FRAME, time, AIAF_IMMEDIATE, -1, -1); mprintf(0, "Warming up...\n"); } break; case SC_TRACKING: { memory->next_activity_time = Game_GetTime(); // memory->last_update_anim_time = Game_GetTime(); mprintf(0, "Camera spotted you!\n"); } break; default: mprintf(0, "Security Camera is all messed up!\n"); break; } // Set the current mode and reset the mode time memory->mode = mode; memory->mode_time = 0.0f; } // Processes the AI Initialize event void SecurityCamera::DoInit(int me) { // Setup memory data tOSIRISMEMCHUNK ch; ch.id = SC_MEMORY_ID; ch.size = sizeof(securitycamera_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (securitycamera_data *)Scrpt_MemAlloc(&ch); memory->alerted = false; SetMode(me, SC_PANNING); } // Processes the AI Frame Interval Event void SecurityCamera::DoFrame(int me) { float last_see_time, last_see_game_time; float curr_anim_frame; // Get the last see time AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_game_time); // Calculate the time since target was seen or heard last_see_time = Game_GetTime() - last_see_game_time; // Get the current animation frame Obj_Value(me, VF_GET, OBJV_F_ANIM_FRAME, &curr_anim_frame); // Do mode specific checks switch (memory->mode) { case SC_PANNING: { // If we have a target switch to tracking mode if (last_see_time < SC_TIME_TO_GAIN_TARGET) { SetMode(me, SC_TRACKING); break; } // If we're warming up, see if we should start looping yet if (memory->panning_state == SC_WARMING_UP) { // See if we're at the final frame if (curr_anim_frame >= SC_MAX_FRAME) { memory->panning_state = SC_LOOPING; float time = (SC_MAX_FRAME - SC_START_FRAME) / SC_PAN_SPEED; Obj_SetCustomAnim(me, SC_START_FRAME, SC_MAX_FRAME, time, AIAF_IMMEDIATE | AIAF_LOOPING, -1, -1); mprintf(0, "Looping pan started...\n"); } } } break; case SC_TRACKING: { // If we haven't seen target for a while, return to panning if (last_see_time > SC_TIME_TO_LOSE_TARGET) { SetMode(me, SC_PANNING); break; } // If we've been tracking target long enough, switch to alert mode if (Game_GetTime() >= memory->next_activity_time) { memory->next_activity_time = Game_GetTime() + SC_TRACK_INTERVAL; // Track the current target vector local_vec_to_target, world_vec_to_target, dir, local_fvec; matrix orient; double theta; float frame_offset, dot, curr_frame, dest_frame, anim_time, next_frame; // Get the vec to target in local security camera space Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); AI_Value(me, VF_GET, AIV_V_VEC_TO_TARGET, &world_vec_to_target); local_vec_to_target = world_vec_to_target * orient; // Make it track target on a 2D plane dir = local_vec_to_target; dir.y = 0.0f; if ((dir.x + dir.y + dir.z) == 0.0f) break; vm_VectorNormalize(&dir); local_fvec.x = 0.0f; local_fvec.y = 0.0f; local_fvec.z = 1.0f; dot = dir * local_fvec; if (dot < -1.0f) dot = -1.0f; if (dot > 1.0f) dot = 1.0f; theta = acos(dot); theta = theta / PI * 4.0; frame_offset = float(theta) * SC_MAX_FRAME_OFFSET; if (frame_offset > SC_MAX_FRAME_OFFSET) frame_offset = SC_MAX_FRAME_OFFSET; // mprintf(0,"x=%.3f, y=%.3f, z=%.3f - dot=%.3f, theta=%.1f\n",dir.x,dir.y,dir.z,dot,theta); // mprintf(0,"Frame offset: %.1f\n",frame_offset); // Get the dest frame between start and end frames for reference if (dir.x < 0.0f) frame_offset = -frame_offset; dest_frame = SC_MID_FRAME + frame_offset; // Get the current frame between start and end frames for reference curr_frame = curr_anim_frame; if (curr_frame > SC_END_FRAME) curr_frame = SC_MAX_FRAME - curr_frame; // Prevent wobbling between two frames if (abs(dest_frame - curr_frame) <= SC_FRAME_DELTA_ERROR) { dest_frame = curr_frame; } // Check if frames are reversed, and adjust accordingly if (dest_frame < curr_frame) { dest_frame = SC_MAX_FRAME - dest_frame; curr_frame = SC_MAX_FRAME - curr_frame; } // Calculate the animation time to go from curr_frame to dest_frame anim_time = (dest_frame - curr_frame) / SC_TRACK_SPEED; /* // Calculate the max distance we can move it this frame anim_time = Game_GetTime() - memory->last_update_anim_time; next_frame = curr_frame + (anim_time*SC_PAN_SPEED); if(next_frame > dest_frame) next_frame=dest_frame; Obj_SetCustomAnim(me, next_frame, next_frame, 0.0f, AIAF_IMMEDIATE, -1, -1); */ // Set the next custom animation Obj_SetCustomAnim(me, curr_frame, dest_frame, anim_time, AIAF_IMMEDIATE, -1, -1); // mprintf(0,"Curr frame: %.6f, Dest frame: %.6f, Anim time: %.1f\n",curr_frame,dest_frame,anim_time); // Record the last update anim time // memory->last_update_anim_time = Game_GetTime(); } // If we've successfully tracked target for a while, then we are alerted if (memory->mode_time >= SC_TRACK_TIME_TO_ALERT && !memory->alerted) { memory->alerted = true; mprintf(0, "Alerted!\n"); } } break; } // mprintf(0,"Anim frame: %.2f\n",curr_anim_frame); // if(last_see_time < 3.0f) // mprintf(0,"Camera saw target %.2f seconds ago!\n",last_see_time); // Increment the current mode time memory->mode_time += Game_GetFrameTime(); } // Process all AI Notify events bool SecurityCamera::DoNotify(int me, tOSIRISEventInfo *data) { if (data->evt_ai_notify.notify_type == AIN_USER_DEFINED) { bot_com *com = (bot_com *)data->extra_info; return ReceiveCommand(me, data->evt_ai_notify.it_handle, com->action, com->ptr); } return true; } // Receives all basic events and calls processesing functions int16_t SecurityCamera::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: // DoDamage(data->me_handle, &data->evt_damaged); break; case EVT_DESTROY: break; case EVT_COLLIDE: // DoCollide(data->me_handle, &data->evt_collide); break; case EVT_MEMRESTORE: memory = (securitycamera_data *)data->evt_memrestore.memory_ptr; break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; } //----------------------- // Crowd Control class //----------------------- // Sends a command out to another alien bool CrowdControl::SendCommand(int me, int it, char command, void *ptr) { bot_com com; com.action = command; com.ptr = ptr; tOSIRISEventInfo ei; ei.me_handle = it; ei.extra_info = (void *)&com; ei.evt_ai_notify.notify_type = AIN_USER_DEFINED; ei.evt_ai_notify.it_handle = me; return Obj_CallEvent(it, EVT_AI_NOTIFY, &ei); } // Processes a command from another alien bool CrowdControl::ReceiveCommand(int me, int it, char command, void *ptr) { switch (command) { // Alien is asked to return current mode case CC_COM_SET_DATA: { cc_packet *data = (cc_packet *)ptr; memory->follow_handle = data->follow_handle; memory->stop_dist = data->stop_dist; memory->slowdown_offset = data->slowdown_offset; mprintf(0, "Set Crowd Control Data.\n"); return true; } break; case CC_COM_DISABLE_CHECK: { memory->disable_check = *(bool *)ptr; mprintf(0, "Crowd control disable check set to: %d\n", memory->disable_check); } break; } return false; } // Processes the AI Initialize event void CrowdControl::DoInit(int me) { // Setup memory data tOSIRISMEMCHUNK ch; ch.id = CC_MEMORY_ID; ch.size = sizeof(crowdcontrol_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (crowdcontrol_data *)Scrpt_MemAlloc(&ch); AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); memory->follow_handle = OBJECT_HANDLE_NONE; memory->stop_dist = 0.0f; memory->slowdown_offset = 0.0f; memory->disable_check = false; memory->next_check_time = Game_GetTime(); mprintf(0, "CrowdControl Initialized.\n"); } // Processes the AI Frame Interval Event void CrowdControl::DoFrame(int me) { // If data hasn't been set yet, just bail if (memory->follow_handle == OBJECT_HANDLE_NONE) return; // See if we need to do a distance check yet if (Game_GetTime() >= memory->next_check_time) { memory->next_check_time = Game_GetTime() + CC_CHECK_INTERVAL; float my_max_speed; bool ok_to_move_normally = false; // Get my current max speed AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &my_max_speed); // Make sure the object we're following still exists if (!memory->disable_check && qObjExists(memory->follow_handle)) { vector my_vel, my_pos, leader_pos, leader_dir; matrix my_orient; float my_max_speed, dist, new_speed; Obj_Value(me, VF_GET, OBJV_V_VELOCITY, &my_vel); Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &my_orient); Obj_Value(memory->follow_handle, VF_GET, OBJV_V_POS, &leader_pos); // It's ok to move in reverse, or away from leader ok_to_move_normally = ((my_vel * my_orient.fvec) <= 0.0f) ? true : false; if (!ok_to_move_normally) { leader_dir = leader_pos - my_pos; ok_to_move_normally = ((my_vel * leader_dir) <= 0.0f) ? true : false; } // Check to see if we're too close to leader (so we can slow down and stop) dist = vm_VectorDistance(&my_pos, &leader_pos); if (!ok_to_move_normally && dist < (memory->stop_dist + memory->slowdown_offset)) { if (dist <= memory->stop_dist || memory->slowdown_offset == 0.0f) { new_speed = CC_MIN_STOPPING_SPEED; } else { float curr_diff; curr_diff = dist - memory->stop_dist; new_speed = CC_MIN_STOPPING_SPEED + ((curr_diff / memory->slowdown_offset) * memory->base_speed); } AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &new_speed); } } else { ok_to_move_normally = true; } // Restore the max speed to the original value if necessary if (ok_to_move_normally && my_max_speed != memory->base_speed) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &memory->base_speed); } } } // Process all AI Notify events bool CrowdControl::DoNotify(int me, tOSIRISEventInfo *data) { if (data->evt_ai_notify.notify_type == AIN_USER_DEFINED) { bot_com *com = (bot_com *)data->extra_info; return ReceiveCommand(me, data->evt_ai_notify.it_handle, com->action, com->ptr); } return true; } // Receives all basic events and calls processesing functions int16_t CrowdControl::CallEvent(int event, tOSIRISEventInfo *data) { switch (event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_AI_FRAME: DoFrame(data->me_handle); break; case EVT_AI_NOTIFY: return (DoNotify(data->me_handle, data) != false) ? CONTINUE_CHAIN | CONTINUE_DEFAULT : 0; break; case EVT_DAMAGED: // DoDamage(data->me_handle, &data->evt_damaged); break; case EVT_DESTROY: break; case EVT_COLLIDE: // DoCollide(data->me_handle, &data->evt_collide); break; case EVT_MEMRESTORE: memory = (crowdcontrol_data *)data->evt_memrestore.memory_ptr; break; } return CONTINUE_CHAIN | CONTINUE_DEFAULT; }