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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}


