AI Programming Overview Draft version .1 // ======================================================================= // Purpose of this document. // ======================================================================= In this document, I am going to concentrate exclusively on the portions of the AI system that are needed to create new AIs for Descent III. I will only glance on the workings of internal systems, such emotion generation, when there is a reason to do so within the context of this document. At a later and unannounced date, I will release a paper that fully describes the architecture of Descent III's AI. // ======================================================================= // A quick overview // ======================================================================= There are a few things that you need to know. Firstly, I have attached the source code for two of the less code intensive robots that appeared in Descent III. The source actually goes a bit beyond just showing the code for the two robots as it provides a framework for further robots. If you are so inclined, skip the text and get straight to the code. Next, the AI system for Descent III uses goals. Goals means to getting your robots to do something. In D3, there are 5 priority levels for goals. There can be one goal for each priority levels 0 - 3. Zero is the lowest priority goal. Priority level 3 goals will override emotion based goals (like running away). The fifth priority level is the ACTIVATION_BLEND_LEVEL. These goals will blend with the highest priority 0 - 3 goal to determine the action of the robot. Not all goals are blendable. Blendable goals include goals like dodging weapons or avoiding enemies within a given range. As a general rule, if the goal requires the robot to generate a path, like AIG_GET_TO_OBJ, then that goal isn't blendable. Each goal can be assigned a GUID (Goal unique identification). GUID's are useful as a way of identifying, classifing, and sequencing goals. Goal slot 3 might be used alternately by 5 different goals, but each goal type could have a different GUID which allows you to id the goal an individual goal after it is assigned. On place you might want to do this is when you receive an AI event notification of a goal complete. GUIDs can be any integer value and actually do not have to be unique. They are a tool for programmers that is built into the AI system. Finally, this document ISN'T EVEN CLOSE TO BEING DONE. :( I will continue to update it until it is. // ======================================================================= // AI Related Events // ======================================================================= There are three AI exclusive events: The first event you should be aware of is the "AI Initialization" event. This event is used when your AI is first given life. It is only called once and is a good place for you to, hmmm., initialize your robot. EVT_AI_INIT // called to initialize SCRIPT AI stuff typedef struct { }tOSIRISEVTAIINIT; // struct for EVT_AI_INIT data The second AI event is the "AI Frame" event. This event is called ever frame the game is run. I generally use this event as my basis for making AI state machines. No parameteres are given, but you can call functions to get things like the amount of time that the last frame took or even the robot's current animation frame. EVT_AI_FRAME // called every frame for AI info typedef struct { }tOSIRISIEVTAIFRAME; // struct for EVT_AI_FRAME data Finally, the "AI Notify" event will inform your robots of what is going on, when goals are completed, and of other significant events. EVT_AI_NOTIFY // called when an AI gets notified typedef struct { int notify_type; // Event callback for the AIN_ (see list of them) int it_handle; // Used if another object is involved int goal_num; // Goal index if it's a goal related notify int goal_uid; // Goal unique id - an important thing for // sequencing robot actions union{ int enabler_num; // If an enabler is involved - the index of it int attack_num; // If an attack is involed - the index of it }; }tOSIRISEVTAINOTIFY; // struct for EVT_AI_NOTIFY data IMPORTANT NOTE: Do not limit your AIs to just using these events. Other events, such as EVT_DAMAGED, are can provide useful information to your AIs. // ======================================================================= // AI Animation Info // ======================================================================= AIAF_LOOPING AIAF_NOTIFY AIAF_UPDATE_WBS AIAF_USE_SPEED AIAF_IMMEDIATE // Force to not tile and set immediately AIPM_ORIENT // Masks for Orientation AIPF_NEXT_NODE // Orientate to velocity AIPF_VELOCITY // No automatic updating of orientation AIPF_NODE_ORIENT // Node orientate AIPF_USER_DEFINED // Always face the next node // ======================================================================= // AI Goal Enders // ======================================================================= MAX_ENABLERS_PER_GOAL // Ender types AIE_NEAR AIE_FAR AIE_LTE_AWARENESS AIE_GT_AWARENESS AIE_LTE_LAST_SEE_TARGET_INTERVAL AIE_GT_LAST_SEE_TARGET_INTERVAL AIE_AI_STATUS_FLAG AIE_SCRIPTED AIE_FEAR AIE_ANGRY AIE_CURIOUS AIE_FRUSTRATED AIE_DELAY_TIME AIE_CLEAR_TIME // How are the ending conditions related (BOOLEAN INFO) ENABLER_AND_NEXT ENABLER_OR_NEXT ENABLER_XOR_NEXT ENABLER_NEW_BOOL_NEXT // ======================================================================= // AI Goals // ======================================================================= // Maximum goals a robot can have at any given time MAX_GOALS // AI Goal Types AIG_GET_AWAY_FROM_OBJ AIG_HIDE_FROM_OBJ AIG_GET_TO_OBJ AIG_ATTACH_TO_OBJ AIG_FIRE_AT_OBJ AIG_MELEE_TARGET AIG_WANDER_AROUND AIG_USE_MOVEMENT_TYPE AIG_BLANK2 AIG_GUARD_OBJ AIG_GUARD_AREA AIG_BLANK3 AIG_FACE_DIR AIG_DODGE_OBJ AIG_FOLLOW_PATH AIG_SET_ANIM AIG_SCRIPTED AIG_MOVE_AROUND_OBJ AIG_MOVE_RELATIVE_OBJ_VEC AIG_MOVE_RELATIVE_OBJ AIG_GET_TO_POS AIG_DO_MELEE_ANIM AIG_GET_AROUND_OBJ AIG_GET_AROUND_POS AIG_ALIGNMENT AIG_AVOID_OBJ AIG_COHESION_OBJ AIG_ALIGN_OBJ // ======================================================================= // Goal end types (Not used yet) // ======================================================================= AIGE_HANDLE_INVALID AIGE_SUCESSFUL AIGE_OVERLOAD // ======================================================================= // Goal Flags // ======================================================================= GF_NONFLUSHABLE // A perminent goal GF_HAS_PATH // This goal has a path (not used yet) GF_KEEP_AT_COMPLETION // Keeps the goal as long at it is valid GF_NOTIFIES // By default, goals do not notify (unless an error occurs) GF_OBJ_IS_TARGET GF_CIRCLE_OBJ GF_CIRCLE_POS GF_USE_BLINE_IF_SEES_GOAL GF_FORCE_AWARENESS GF_OBJS_ARE_FRIENDS GF_OBJS_ARE_SPECIES GF_OBJS_ARE_ENEMIES GF_ORIENT_VELOCITY // Defaults to target if there is one (otherwise, to velocity) This flag forces velocity. GF_RAMPED_INFLUENCE // By default, distance based goals are (less dist = more influence) GF_MIN_MAX_INFLUENCE GF_SCRIPTED_INFLUENCE GF_ORIENT_TARGET GF_ORIENT_SCRIPTED GF_ORIENT_GOAL_OBJ GF_ORIENT_FOR_ATTACH GF_ORIENT_PATH_NODE GF_PATH_FOLLOW_EXACTLY GF_PATH_REVERSE_AT_END GF_PATH_CIRCLE_AT_END GFM_END_OF_PATH (GF_PATH_REVERSE_AT_END|GF_PATH_CIRCLE_AT_END) GF_PATH_MOVE_REVERSE_DIR GF_IS_ATTACH_CHILD // The moving object (for the attach goal) is actually the child GF_CLEAR_IF_NOT_CURRENT_GOAL // Removes the goal if it isn't the current one GF_ORIENT_SET_FVEC // Face the direction of the fvec GF_ORIENT_SET_FVEC_UVEC // Face the fvec and uvec GF_SPEED_MASK (GF_SPEED_NORMAL | GF_SPEED_DODGE) GF_SPEED_NORMAL GF_SPEED_DODGE GF_SPEED_FLEE GF_SPEED_ATTACK // ======================================================================= // Goal Specific Flags // ======================================================================= GWF_ONLY_IN_CUR_MINE GWF_ONLY_MINES GWF_ONLY_TERRAIN // Goal Sub-Types GST_FVEC GST_NEG_FVEC GST_RVEC GST_NEG_RVEC GST_UVEC GST_NEG_UVEC // ======================================================================= // Influence constants // ======================================================================= MAX_INFLUENCE HIGH_INFLUENCE NORMAL_INFLUENCE LOW_INFLUENCE MAX_INFLUENCE_DELTA_CONSIDER // ======================================================================= // Goal levels // ======================================================================= NUM_ACTIVATION_LEVELS ACTIVATION_BLEND_LEVEL // ======================================================================= // AI notification of events // ======================================================================= AIN_NEW_MOVEMENT AIN_OBJ_KILLED AIN_WHIT_BY_OBJ AIN_SEE_TARGET AIN_PLAYER_SEES_YOU AIN_WHIT_OBJECT AIN_TARGET_DIED // In code, it only notifies if the target is gone AIN_OBJ_FIRED AIN_GOAL_COMPLETE AIN_GOAL_FAIL AIN_GOAL_ERROR AIN_HEAR_NOISE AIN_NEAR_TARGET AIN_HIT_BY_WEAPON AIN_NEAR_WALL AIN_USER_DEFINED // Processed in script AIN_TARGET_INVALID// Goal is killed AIN_GOAL_INVALID AIN_SCRIPTED_GOAL AIN_SCRIPTED_ENABLER AIN_ANIM_COMPLETE AIN_BUMPED_OBJ AIN_MELEE_HIT AIN_MELEE_ATTACK_FRAME AIN_SCRIPTED_INFLUENCE AIN_SCRIPTED_ORIENT AIN_MOVIE_START AIN_MOVIE_END // ======================================================================= // AI Awareness constants // ======================================================================= AWARE_FULLY AWARE_MOSTLY AWARE_PARTIALLY AWARE_BARELY AWARE_NONE MAX_AWARE_TIME MAX_RENDER_RECENTLY_TIME // ======================================================================= // AI flags // ======================================================================= //Flags for AI info AIF_WEAPON1 AIF_WEAPON2 AIF_MELEE1 AIF_MELEE2 AIF_STAYS_INOUT AIF_GB_MIMIC_PLAYER_FIRING_HACK AIF_ACT_AS_NEUTRAL_UNTIL_SHOT AIF_PERSISTANT AIF_DODGE AIF_FIRE AIF_FLINCH AIF_DETERMINE_TARGET AIF_AIM AIF_ONLY_TAUNT_AT_DEATH AIF_AVOID_WALLS AIF_DISABLED AIF_FLUCTUATE_SPEED_PROPERTIES AIF_TEAM_MASK AIF_TEAM_PTMC AIF_TEAM_REBEL AIF_TEAM_HOSTILE AIF_TEAM_NEUTRAL AIF_ORDERED_WB_FIRING AIF_ORIENT_TO_VEL AIF_XZ_DIST AIF_REPORT_NEW_ORIENT AIF_TARGET_BY_DIST AIF_DISABLE_FIRING AIF_DISABLE_MELEE AIF_AUTO_AVOID_FRIENDS AIF_TRACK_CLOSEST_2_FRIENDS AIF_TRACK_CLOSEST_2_ENEMIES AIF_BIASED_FLIGHT_HEIGHT AIF_FORCE_AWARENESS AIF_UVEC_FOV AIF_AIM_PNT_FOV // ======================================================================= // AI Status Registers // ======================================================================= AISR_FLEE AISR_ATTACKING AISR_CIRCLE_DIST AISR_PATH AISR_MELEE AISR_RANGED_ATTACK // Full body stuff AISR_SEES_GOAL // ======================================================================= // OSIRIS AI Functions // ======================================================================= // Returns the index of path with the specified name. It returns -1 the path // is not found // int AI_GetPathID (char *pathname) // A simple version of the follow path goal // objhandle - The object you want to follow the path // path_id - This is returned from AI_GetPathID() // guid - User definable value that is passed back when the goal is completed // flags - Look at the list of AIGF_ flags (AI Goal Flags) // slot - goal index (0 - 3, or ACTIVATION_BLEND_LEVEL) // int AI_GoalFollowPathSimple (int objhandle,int path_id,int guid,int flags,int slot = 3) // Turns on/off a robot // objhandle - The object // int AI_PowerSwitch(int objhandle,ubyte f_power_on) // objhandle - The object // void AI_Value(int objhandle, char op, char vtype, void *ptr) // Turns a robot toward a user defined orientation - use this within // objhandle - The object // fvec - The desired forward vector // uvec - The desired upward vector // ubyte AI_TurnTowardsVectors(int objhandle,vector *fvec,vector *uvec) // Sets the type of robot. Usually this just includes setting some goals and some AI // flags. // // objhandle - The object // AI type - Basic AI type // AIT_EVADER1 Tailbot // AIT_EVADER2 Thresher // AIT_STATIONARY_TURRET Turrets // AIT_AIS Used to clean out a robot's goals // AIT_MELEE1 Tubbs is a AIT_MELEE1 with a some modifications // void AI_SetType(int objhandle,int type) vector AI_FindHidePos(int hideobjhandle,int viewobjhandle,float time,int *hide_room) int AI_GoalAddEnabler(int objhandle,int goal_index,int enabler_type,float percent,float interval,void *ptr) // objhandle - The object // int AI_AddGoal(int objhandle,int goal_type,int level,float influence, int guid, int flags, ... ) // objhandle - The object // typedef float( *AI_AddGoal_fp)(int objhandle,int goal_type,int level,float influence, int guid, int flags, ... ) // objhandle - The object // void AI_ClearGoal(int objhandle,int goal_index) // objhandle - The object // int AI_FindObjOfType(int objhandle, int type, int id, bool f_ignore_init_room, int parent_handle = OBJECT_HANDLE_NONE) AI_GetRoomPathPoint(int roomnum) // Returns the room number of the closest reachable energy center // objhandle - The object // int AI_FindEnergyCenter(int objhandle) // objhandle - The object // float AI_GetDistToObj(int objhandle,int otherobjhandle) // objhandle - The object // int AI_SetGoalFlags(int objhandle,int goal_handle,int flags,ubyte f_enable) // objhandle - The object // void AI_SetGoalCircleDist(int objhandle,int goal_handle,float dist) // objhandle - The object // bool AI_IsObjFriend(int objhandle, int it_handle) // objhandle - The object // bool AI_IsObjEnemy(int objhandle, int it_handle) // objhandle - The object // AI_GoalValue(int objhandle, char g_index, char op, char vtype, void *ptr, char index =0); // Returns the number of nearby objects, fills in the object_handle_list with objhandles // pos - Start position // init_roomnum - Room number // rad - Radius of search // object_handle_list - An array to be filled with object handles // max_elements - Number of ints in the array // f_lightmap_only - Only consider light mapped objects (used for static lighting) // f_only_players_and_ais - Ignores non-living/non-AI objects // f_include_non_collide_objects - Includes objects that cannot be collided with // f_stop_at_closed_doors - Don't look in rooms behind closed doors // int AI_GetNearbyObjs(vector *pos, int init_roomnum, float rad, int *object_handle_list, int max_elements, bool f_lightmap_only, bool f_only_players_and_ais = true, bool f_include_non_collide_objects = false, bool f_stop_at_closed_doors = true) // objhandle - The object // char AI_GetCurGoalIndex(int objhandle) // objhandle - The object // bool AI_IsDestReachable(int objhandle, int room) // objhandle - The object // bool AI_IsObjReachable(int objhandle, int target) // ======================================================================= // Value Functions // ======================================================================= All value functions work the same way. First // ======================================================================= // AI (and object) Value Functions // ======================================================================= VF_SET VF_GET VF_SET_FLAGS VF_CLEAR_FLAGS // ======================================================================= // AI Values for AI_Value() // ======================================================================= AIV_F_MAX_SPEED AIV_F_MAX_DELTA_SPEED AIV_F_MAX_TURN_RATE AIV_F_MAX_DELTA_TURN_RATE AIV_F_ATTACK_VEL_PERCENT AIV_F_FLEE_VEL_PERCENT AIV_F_DODGE_VEL_PERCENT AIV_F_CIRCLE_DIST AIV_F_DODGE_PERCENT AIV_F_MELEE_DAMAGE1 AIV_F_MELEE_DAMAGE2 AIV_F_MELEE_LATENCY1 AIV_F_MELEE_LATENCY2 AIV_C_MOVEMENT_TYPE AIV_C_MOVEMENT_SUBTYPE AIV_C_ANIMATION_TYPE AIV_C_NEXT_ANIMATION_TYPE AIV_C_NEXT_MOVEMENT AIV_C_CURRENT_WB_FIRING AIV_I_TARGET_HANDLE AIV_F_NEXT_TARGET_UPDATE_TIME AIV_F_DIST_TO_TARGET AIV_V_VEC_TO_TARGET AIV_F_NEXT_CHECK_SEE_TARGET_TIME AIV_V_LAST_SEE_TARGET_POS AIV_F_LAST_SEE_TARGET_TIME AIV_F_WEAPON_SPEED AIV_F_NEXT_MELEE_TIME AIV_F_LAST_RENDER_TIME AIV_F_NEXT_FLINCH_TIME AIV_V_MOVEMENT_DIR AIV_V_ROT_THRUST_VECTOR AIV_F_FOV AIV_F_AVOID_FRIENDS_DIST AIV_F_FRUSTRATION AIV_F_CURIOUSITY AIV_F_FIRE_SPREAD AIV_F_AGRESSION AIV_F_NIGHT_VISION AIV_F_FOG_VISION AIV_F_LEAD_ACCURACY AIV_F_LEAD_VARIENCE AIV_F_FIGHT_TEAM AIV_F_FIGHT_SAME AIV_F_HEARING AIV_F_ROAMING AIV_F_LIFE_PRESERVATION AIV_F_BIASED_FLIGHT_IMPORTANCE AIV_F_BIASED_FLIGHT_MIN AIV_F_BIASED_FLIGHT_MAX AIV_F_AWARENESS AIV_I_FLAGS AIV_I_STATUS_REG AIV_F_LAST_HEAR_TARGET_TIME // ======================================================================= // AI Goal Values for AI_GoalValue() // ======================================================================= AIGV_I_TYPE AIGV_C_ACTIVATION_LEVEL AIGV_F_MIN_INFLUENCE AIGV_F_MAX_INFLUENCE AIGSV_F_INFLUENCE_DIST AIGV_I_HANDLE AIGV_I_ROOMNUM AIGV_I_F_ACTIONS AIGV_I_ID AIGV_C_SUBTYPE AIGV_F_TIME AIGV_V_VEC AIGV_V_POS AIGV_F_STEER_MIN_DIST AIGV_F_STEER_MAX_DIST AIGV_F_STEER_MAX_STRENGTH AIGV_B_ATTACH_F_ALIGNED AIGV_B_ATTACH_F_SPHERE AIGV_F_ATTACH_RAD AIGV_C_ATTACH_CHILD_AP AIGV_C_ATTACH_PARENT_AP AIGV_I_WANDER_AVOID_HANDLE AIGV_C_WANDER_MIN_ROOMS AIGV_C_WANDER_MAX_ROOMS AIGV_C_WANDER_FLAGS AIGV_C_WANDER_MINE_INDEX AIGV_F_CIRCLE_DIST AIGV_I_STATUS_REG AIGV_F_START_TIME AIGV_F_NEXT_PATH_TIME AIGV_F_DIST_TO_GOAL AIGV_V_VEC_TO_TARGET AIGV_F_NEXT_CHECK_SEE_TARGET_TIME AIGV_V_LAST_SEE_TARGET_POS AIGV_F_LAST_SEE_TARGET_TIME AIGV_F_NEXT_TARGET_UPDATE_TIME AIGV_I_FLAGS AIGV_V_ORIENT_FVEC AIGV_V_ORIENT_UVEC AIGV_C_NUM_ENABLERS AIGSV_C_ENABLER_TYPE AIGSV_F_ENABLER_TIME AIGSV_C_ENABLER_MTYPE AIGSV_F_ENABLER_FLOAT_VALUE AIGSV_I_ENABLER_FLAGS AIGSV_F_ENABLER_DIST AIGSV_F_ENABLER_PERCENT AIGSV_F_ENABLER_CHECK_INTERVAL AIGSV_F_ENABLER_LAST_CHECK_TIME AIGSV_C_ENABLER_NEXT_ENABLER_OP AIGV_B_USED AIGV_I_SCRIPTED_DATA_PTR // ======================================================================= // Example Code // ======================================================================= // AIGameExample.cpp // #include #include #include #include "osiris_import.h" #include "osiris_common.h" #include "osiris_vector.h" #include "dallasfuncs.cpp" #ifdef _MSC_VER //Visual C++ Build #define STDCALL __stdcall #define STDCALLPTR *STDCALL #else //Non-Visual C++ Build #define STDCALL __attribute__((stdcall)) #define STDCALLPTR STDCALL* #endif #ifdef __cplusplus extern "C"{ #endif char STDCALL InitializeDLL(tOSIRISModuleInit *func_list); void STDCALL ShutdownDLL(void); int STDCALL GetGOScriptID(char *name,ubyte isdoor); void STDCALLPTR CreateInstance(int id); void STDCALL DestroyInstance(int id,void *ptr); short STDCALL CallInstanceEvent(int id,void *ptr,int event,tOSIRISEventInfo *data); int STDCALL SaveRestoreState( void *file_ptr, ubyte saving_state ); #ifdef __cplusplus } #endif int String_table_size = 0; char **String_table = NULL; static char *_Error_string = "!!ERROR MISSING STRING!!"; static char *_Empty_string = ""; 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) // ========================== // Script class definitions// // ========================== #define NUM_IDS 2 // maximum number of IDs #define ID_FLAK 0 #define ID_MANTARAY 1 typedef struct { int id; char *name; }tScriptInfo; tScriptInfo ScriptInfo[NUM_IDS] = { {ID_FLAK, "Flak"}, {ID_MANTARAY, "MantaRay"} }; int aigame_mod_id; //use this macro to create unique timer IDs #define CREATE_TIMER_ID(id) ( (aigame_mod_id<<16)|(id) ) class BaseObjScript { public: BaseObjScript(); ~BaseObjScript(); virtual short CallEvent(int event,tOSIRISEventInfo *data); }; //------------------ // Flak class //------------------ typedef struct { int canopy_handle; bool f_dead; float death_time; } flak_data; class Flak : public BaseObjScript { private: flak_data *memory; void DoInit(int me_handle); public: Flak(){} short CallEvent(int event,tOSIRISEventInfo *data); }; //------------------ // MantaRay class //------------------ // Modes #define MRM_NORMAL 0 #define MRM_ATTACK 1 #define MRM_ATTACK_CIRCLE_BACK 2 // Flags #define MRF_LEADER 0x01 #define MRF_SQUADIE 0x02 #define MRF_CATCHUP 0x04 // Squad commands #define MRC_JOIN_ME 11 #define MRC_LEAVE_YOU 12 #define MRC_GET_GOAL_POS 13 #define MRC_GET_GOAL_ROOM 14 #define MRC_GET_GOAL_ORIENT 15 #define MR_MAX_TEAMMATES 4 // Squad offsets #define MRO_FVEC 10.0f #define MRO_RVEC 20.0f #define MRO_UVEC 5.0f // Squad catchup offset #define MRO_CATCHUP_DIST 5.0f typedef struct { char mode; float mode_time; float next_mode_time; float base_speed; // We alter this when we are in catchup mode float attack_speed; int flags; unsigned short mantaray_id; int leader_handle; int num_teammates; int teammate[MR_MAX_TEAMMATES]; float next_update_squad_time; vector goal_pos; int goal_room; } mantaray_data; class MantaRay : public BaseObjScript { private: mantaray_data *memory; void DoInit(int me); void DoSquadieFrame(int me); void DoFrame(int me); void SetMode(int me, char mode); 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); void UpdateSquadList(int me); void UpdateSquad(int me); public: MantaRay(){} short CallEvent(int event,tOSIRISEventInfo *data); }; //---------------- // Standard stuff //---------------- // 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, ubyte saving_state ) { return 0; } char STDCALL InitializeDLL(tOSIRISModuleInit *func_list) { osicommon_Initialize((tOSIRISModuleInit *)func_list); 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; } aigame_mod_id = func_list->module_identifier; String_table_size = func_list->string_count; String_table = func_list->string_table; return 1; } void STDCALL ShutdownDLL(void) { } int STDCALL GetGOScriptID(char *name,ubyte isdoor) { for( int i = 0; i < NUM_IDS ; i++ ) { if(!stricmp(name,ScriptInfo[i].name)) { return ScriptInfo[i].id; } } return -1; } void STDCALLPTR CreateInstance(int id) { switch(id) { case ID_FLAK: return new Flak; break; case ID_MANTARAY: return new MantaRay; break; default: mprintf(0,"SCRIPT: Illegal ID (%d)\n",id); break; }; return NULL; } void STDCALL DestroyInstance(int id,void *ptr) { switch(id){ case ID_FLAK: delete ((Flak *)ptr); break; case ID_MANTARAY: delete ((MantaRay *)ptr); break; default: mprintf(0,"SCRIPT: Illegal ID (%d)\n",id); break; } } short STDCALL CallInstanceEvent(int id,void *ptr,int event,tOSIRISEventInfo *data) { return ((BaseObjScript *)ptr)->CallEvent(event,data); } //============================================ // Functions //============================================ inline bool IsGoalFinishedNotify(int index) { return (index == AIN_GOAL_COMPLETE || index == AIN_GOAL_INVALID || index == AIN_GOAL_FAIL || index == AIN_GOAL_ERROR); } //============================================ // Script Implementation //============================================ BaseObjScript::BaseObjScript() { } BaseObjScript::~BaseObjScript() { } short BaseObjScript::CallEvent(int event,tOSIRISEventInfo *data) { return CONTINUE_CHAIN|CONTINUE_DEFAULT; } // -------------- // Flak // -------------- void Flak::DoInit(int me) { tOSIRISMEMCHUNK ch; ch.id = 4; ch.size = sizeof(flak_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (flak_data *)Scrpt_MemAlloc(&ch); memory->canopy_handle = CreateAndAttach(me, "Flakcanopy", OBJ_ROBOT, 0, 0); memory->f_dead = false; AI_SetType(me, AIT_EVADER1); } short Flak::CallEvent(int event, tOSIRISEventInfo *data) { switch(event) { case EVT_AI_INIT: DoInit(data->me_handle); break; case EVT_INTERVAL: { if(!memory->f_dead) { int type; Obj_Value(memory->canopy_handle, VF_GET, OBJV_I_TYPE, &type); if(type != OBJ_ROBOT) { char ctype = CT_NONE; Obj_Value(data->me_handle, VF_SET, OBJV_C_CONTROL_TYPE, &ctype); vector vel = {0.0f, 0.0f, 0.0f}; Obj_Value(data->me_handle, VF_SET, OBJV_V_VELOCITY, &vel); msafe_struct mstruct; mstruct.random = 21; mstruct.is_real = 0; mstruct.objhandle = data->me_handle; mstruct.gunpoint = -2; mstruct.effect_type = MED_SMOKE_INDEX; mstruct.phys_info = 0; mstruct.drag = .001f; mstruct.mass = .001f; mstruct.interval = .1f; mstruct.longevity = 5.0f; mstruct.lifetime = 1.0; mstruct.size = 6.0f; mstruct.speed = 25.0; MSafe_CallFunction(MSAFE_OBJECT_START_SPEW, &mstruct); memory->f_dead = true; memory->death_time = Game_GetTime(); } } else { if(Game_GetTime() > memory->death_time + 5.0f) { Obj_Kill(data->me_handle, OBJECT_HANDLE_NONE, 1000.0f, DF_BLAST_RING | DF_LOSES_ANTIGRAV | DF_EXPL_MEDIUM | DF_FIREBALL | DF_BREAKS_APART | DF_DEBRIS_SMOKES, 0.0f, 0.0f); } } } break; case EVT_DESTROY: Obj_Kill(memory->canopy_handle, OBJECT_HANDLE_NONE, 1000.0f, DF_BLAST_RING | DF_LOSES_ANTIGRAV | DF_EXPL_MEDIUM | DF_FIREBALL | DF_BREAKS_APART | DF_DEBRIS_SMOKES, 0.0f, 0.0f); break; case EVT_MEMRESTORE: { memory = (flak_data *)data->evt_memrestore.memory_ptr; } break; } return CONTINUE_CHAIN|CONTINUE_DEFAULT; } //------------------ // MantaRay class //------------------ bool MantaRay::SendCommand(int me, int it, char command, void *ptr) { gb_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); } bool MantaRay::ReceiveCommand(int me, int it, char command, void *ptr) { switch(command) { case MRC_JOIN_ME: { if(memory->flags == 0) { memory->leader_handle = it; memory->flags |= MRF_SQUADIE; SetMode(me, memory->mode); return true; } } break; case MRC_LEAVE_YOU: break; case MRC_GET_GOAL_POS: { int i; for(i = 0; i < memory->num_teammates; i++) { if(memory->teammate[i] == it) { vector goal_pos; matrix orient; Obj_Value(me, VF_GET, OBJV_V_POS, &goal_pos); Obj_Value(me, VF_GET, OBJV_M_ORIENT, &orient); switch(i) { case 0: goal_pos -= orient.fvec * MRO_FVEC; goal_pos -= orient.rvec * MRO_RVEC; break; case 1: goal_pos -= orient.fvec * MRO_FVEC; goal_pos += orient.rvec * MRO_RVEC; break; case 2: goal_pos -= orient.fvec * MRO_FVEC * 2.0f; goal_pos -= orient.rvec * MRO_RVEC * 2.0f; break; case 3: goal_pos -= orient.fvec * MRO_FVEC * 2.0f; goal_pos += orient.rvec * MRO_RVEC * 2.0f; break; } *(vector *)ptr = goal_pos; return true; } } } break; case MRC_GET_GOAL_ROOM: { Obj_Value(me, VF_GET, OBJV_I_ROOMNUM, (int *)ptr); return true; } break; case MRC_GET_GOAL_ORIENT: { return true; } break; } // mprintf(0, "MantaRay action %d failed\n"); return false; } void MantaRay::UpdateSquadList(int me) { int i; for(i = 0; i < memory->num_teammates; i++) { int type; int j; Obj_Value(memory->teammate[i], VF_GET, OBJV_I_TYPE, &type); if(type == OBJ_NONE) { for(j = i; j < memory->num_teammates - 1; j++) { memory->teammate[j] = memory->teammate[j + 1]; } memory->num_teammates--; i--; } } } void MantaRay::UpdateSquad(int me) { if(memory->flags & MRF_SQUADIE) return; if(memory->flags & MRF_LEADER) { UpdateSquadList(me); if(memory->num_teammates == 0) memory->flags &= ~MRF_LEADER; if(memory->num_teammates >= MR_MAX_TEAMMATES) return; } 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, 200.0f, scan_objs, 25, false, true, false, true); for(i = 0; i < n_scan; i++) { unsigned short 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 == memory->mantaray_id) { int type; Obj_Value(scan_objs[i], VF_GET, OBJV_I_TYPE, &type); if(type == OBJ_ROBOT && scan_objs[i] != me) { bool f_on_team = false; if(memory->flags & MRF_LEADER) { int j; for(j = 0; j < memory->num_teammates; j++) { if(scan_objs[i] == memory->teammate[j]) { f_on_team = true; break; } } } if(!f_on_team) { if(SendCommand(me, scan_objs[i], MRC_JOIN_ME, NULL)) { memory->flags |= MRF_LEADER; memory->teammate[memory->num_teammates++] = scan_objs[i]; } } } } if(memory->num_teammates >= MR_MAX_TEAMMATES) return; } } void MantaRay::DoInit(int me) { tOSIRISMEMCHUNK ch; ch.id = 4; ch.size = sizeof(mantaray_data); ch.my_id.type = OBJECT_SCRIPT; ch.my_id.objhandle = me; memory = (mantaray_data *)Scrpt_MemAlloc(&ch); AI_Value(me, VF_GET, AIV_F_MAX_SPEED, &memory->base_speed); memory->attack_speed = 1.8f * memory->base_speed; memory->flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; memory->num_teammates = 0; // .1 to .6 seconds into the level, do the first matching memory->next_update_squad_time = Game_GetTime() + ((float)rand()/(float)RAND_MAX) *.5f + 0.1f; memory->mantaray_id = Obj_FindID("Manta Ray"); SetMode(me, MRM_NORMAL); } void MantaRay::SetMode(int me, char mode) { int f_attack_flags = (AIF_FIRE | AIF_AUTO_AVOID_FRIENDS); AI_SetType(me, AIT_AIS); if(memory->flags & MRF_SQUADIE) { // The leader is alive; so, get my new goal pos SendCommand(me, memory->leader_handle, MRC_GET_GOAL_POS, &memory->goal_pos); AI_GoalValue(me, 2, VF_SET, AIGV_V_POS, &memory->goal_pos); SendCommand(me, memory->leader_handle, MRC_GET_GOAL_ROOM, &memory->goal_room); AI_GoalValue(me, 2, VF_SET, AIGV_I_ROOMNUM, &memory->goal_room); AI_AddGoal(me, AIG_GET_TO_POS, 2, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, &memory->goal_pos, memory->goal_room); float cd = .1f; AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &cd); } else { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &memory->base_speed); } switch(mode) { case MRM_NORMAL: { vector pos; int room = 0; float dist = 15.0f; AI_Value(me, VF_CLEAR_FLAGS, AIV_I_FLAGS, &f_attack_flags); AI_AddGoal(me, AIG_WANDER_AROUND, 1, 1.0f, -1, GF_ORIENT_VELOCITY | GF_NOTIFIES | GF_KEEP_AT_COMPLETION, &pos, room); AI_GoalValue(me, 1, VF_SET, AIGV_F_CIRCLE_DIST, &dist); } break; case MRM_ATTACK: { AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &f_attack_flags); // memory->next_attack_time = Game_GetTime() + 10.0 * ((float)rand()/(float)RAND_MAX); float dist = 60.0f + ((float)rand()/(float)RAND_MAX)*10.0f; 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_GoalValue(me, 1, VF_SET, AIGV_F_CIRCLE_DIST, &dist); dist = 10.0f; int g_index = AI_AddGoal(me, AIG_GET_AROUND_OBJ, ACTIVATION_BLEND_LEVEL, 1.0f, -1, GF_ORIENT_VELOCITY | 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; case MRM_ATTACK_CIRCLE_BACK: { vector pos; int room = 0; AI_Value(me, VF_SET_FLAGS, AIV_I_FLAGS, &f_attack_flags); if(!(memory->flags & MRF_SQUADIE)) { AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &memory->attack_speed); } 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_NOTIFIES | GF_ORIENT_VELOCITY | 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_ORIENT_VELOCITY | 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; } memory->mode = mode; memory->mode_time = 0.0f; } void MantaRay::DoSquadieFrame(int me) { int type; Obj_Value(memory->leader_handle, VF_GET, OBJV_I_TYPE, &type); // Check if the leader is still alive if(type == OBJ_NONE) { // Reset the speed AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &memory->base_speed); memory->flags = 0; memory->leader_handle = OBJECT_HANDLE_NONE; SetMode(me, MRM_NORMAL); return; } // The leader is alive; so, get my new goal pos SendCommand(me, memory->leader_handle, MRC_GET_GOAL_POS, &memory->goal_pos); AI_GoalValue(me, 2, VF_SET, AIGV_V_POS, &memory->goal_pos); SendCommand(me, memory->leader_handle, MRC_GET_GOAL_ROOM, &memory->goal_room); AI_GoalValue(me, 2, VF_SET, AIGV_I_ROOMNUM, &memory->goal_room); AI_AddGoal(me, AIG_GET_TO_POS, 2, 1.0f, -1, GF_ORIENT_VELOCITY | GF_KEEP_AT_COMPLETION, &memory->goal_pos, memory->goal_room); float cd = .1f; AI_GoalValue(me, 2, VF_SET, AIGV_F_CIRCLE_DIST, &cd); vector my_pos; Obj_Value(me, VF_GET, OBJV_V_POS, &my_pos); float dist = vm_VectorDistance(&my_pos, &memory->goal_pos); if(dist <= MRO_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = dist/MRO_CATCHUP_DIST * 0.23f * catchup_speed; catchup_speed += scaled_speed; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } else if(dist > MRO_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = 0.23f * catchup_speed; catchup_speed += scaled_speed; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } // Get the leaders mode // Set my mode to the leaders mode... (Modes account for flags) } void MantaRay::DoFrame(int me) { float last_see_target_time; float last_hear_target_time; AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_target_time); AI_Value(me, VF_Gpos, &memory->goal_pos); if(dist <= MRO_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = dist/MRO_CATCHUP_DIST * 0.23f * catchup_speed; catchup_speed += scaled_speed; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } else if(dist > MRO_CATCHUP_DIST) { float catchup_speed; AI_Value(memory->leader_handle, VF_GET, AIV_F_MAX_SPEED, &catchup_speed); float scaled_speed = 0.23f * catchup_speed; catchup_speed += scaled_speed; AI_Value(me, VF_SET, AIV_F_MAX_SPEED, &catchup_speed); } // Get the leaders mode // Set my mode to the leaders mode... (Modes account for flags) } void MantaRay::DoFrame(int me) { float last_see_target_time; float last_hear_target_time; AI_Value(me, VF_GET, AIV_F_LAST_SEE_TARGET_TIME, &last_see_target_time); AI_Value(me, VF_GET, AIV_F_LAST_HEAR_TARGET_TIME, &last_hear_target_time); // Periodically update the squad information if(memory->next_update_squad_time <= Game_GetTime()) { // 3 to 6 seconds memory->next_update_squad_time = Game_GetTime() + ((float)rand()/(float)RAND_MAX) * 3.0f + 3.0f; UpdateSquad(me); } // If your a squadie, check for a leader if(memory->flags & MRF_SQUADIE) { DoSquadieFrame(me); } // if(memory->flags & MRF_LEADER) // { // vector pos; // Obj_Value(me, VF_GET, OBJV_V_POS, &pos); // vector gpos; // int groom; // float cd; // AI_GoalValue(me, 1, VF_GET, AIGV_I_ROOMNUM, &groom); // AI_GoalValue(me, 1, VF_GET, AIGV_V_POS, &gpos); // AI_GoalValue(me, 1, VF_GET, AIGV_F_CIRCLE_DIST, &cd); // mprintf(0, "%f,%f,%f-%f,%f,%f-%f\n", XYZ(&gpos), XYZ(&pos), cd); // } switch(memory->mode) { case MRM_ATTACK: { // mprintf(0, "MODE ATTACK\n"); if(Game_GetTime() > last_see_target_time + 7.0f && Game_GetTime() > last_hear_target_time + 7.0f) SetMode(me, MRM_NORMAL); // // float dist; // AI_GoalValue(me, 1, VF_GET, AIGV_F_DIST_TO_GOAL, &dist); } break; case MRM_ATTACK_CIRCLE_BACK: { // mprintf(0, "MODE CBACK\n"); if(Game_GetTime() > last_see_target_time + 7.0f && Game_GetTime() > last_hear_target_time + 7.0f) SetMode(me, MRM_NORMAL); if(memory->mode_time > 5.5f) SetMode(me, MRM_ATTACK); } break; case MRM_NORMAL: { // mprintf(0, "MODE NORMAL\n"); if(Game_GetTime() < last_see_target_time + 7.0f && Game_GetTime() < last_hear_target_time + 7.0f) SetMode(me, MRM_ATTACK); } break; } memory->mode_time += Game_GetFrameTime(); } bool MantaRay::DoNotify(int me, tOSIRISEventInfo *data) { if(IsGoalFinishedNotify(data->evt_ai_notify.notify_type)) { switch(memory->mode) { case MRM_ATTACK: if(data->evt_ai_notify.goal_num == 1) { SetMode(me, MRM_ATTACK_CIRCLE_BACK); return false; } break; case MRM_ATTACK_CIRCLE_BACK: if(data->evt_ai_notify.goal_num == 1) { SetMode(me, MRM_ATTACK); return false; } break; } } else if(data->evt_ai_notify.notify_type == AIN_USER_DEFINED) { gb_com *com = (gb_com *)data->extra_info; return ReceiveCommand(me, data->evt_ai_notify.it_handle, com->action, com->ptr); } return true; } short MantaRay::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_MEMRESTORE: { memory = (mantaray_data *)data->evt_memrestore.memory_ptr; } break; } return CONTINUE_CHAIN|CONTINUE_DEFAULT; }