/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 . */ #include "scripts.h" #include #include "toolkit.h" #define GTH_DEBUG 0 #if (GTH_DEBUG) #define GTH_DEBUG_INT( x , format ) Commands->Display_Int( x, format ) #define GTH_DEBUG_FLOAT( x , format ) Commands->Display_Float( x, format ) #else #define GTH_DEBUG_INT( x , format ) #define GTH_DEBUG_FLOAT( x , format ) #endif /* ** GTH_Drop_Object_On_Death (verified) ** This script will create an object at the position of the object when it dies. ** ** Params: ** Drop_Object - name of the preset to create an instance of ** Drop_Height - float meters to add to the Z coord of the original object when creating the drop obj ** Probability - int between 1 and 100, chance that the object will be created */ DECLARE_SCRIPT(GTH_Drop_Object_On_Death, "Drop_Object=:string,Drop_Height=0.25:float,Probability=100:int") { void Killed( GameObject * obj, GameObject * killer ) { const char * obj_name = Get_Parameter("Drop_Object"); bool doit = false; float probability = Get_Int_Parameter("Probability"); if (probability >= 100) { doit = true; } else { int random = Commands->Get_Random_Int(0,100); doit = random < probability; } if ((obj_name != NULL) && (doit)) { Vector3 spawn_location = Commands->Get_Position ( obj ); spawn_location.Z = spawn_location.Z + Get_Float_Parameter("Drop_Height"); Commands->Create_Object ( obj_name, spawn_location ); } } }; /* ** GTH_Drop_Object_On_Death_Zone (verified) ** This script is just like the other drop object on death except that it must also ** be activated by a custom message from another script. Use the GTH_Zone_Send_Custom ** to enable and disable this script. ** ** Params: ** Custom_Message - message id that turns this script on or off, use message ID's greater than 10000! ** Drop_Object - name of the preset to create an instance of ** Drop_Height - float meters to add to the Z coord of the original object when creating the drop obj ** Probability - int between 1 and 100, chance that the object will be created */ DECLARE_SCRIPT(GTH_Drop_Object_On_Death_Zone, "Custom_Message=20000:int,Drop_Object=:string,Drop_Height=0.25:float,Probability=100:int") { bool enabled; REGISTER_VARIABLES() { SAVE_VARIABLE(enabled, 1); } void Created( GameObject * obj ) { enabled = false; } void Custom(GameObject * obj, int type, int param, GameObject * sender) { if (type == Get_Int_Parameter("Custom_Message")) { enabled = (param==1); GTH_DEBUG_INT(enabled,"GTH_Drop_Object_On_Death_Zone custom message: enable = %d\n"); } } void Killed( GameObject * obj, GameObject * killer ) { GTH_DEBUG_INT(0,"GTH_Drop_Object_On_Death_Zone Killed callback\n"); if (!enabled) { GTH_DEBUG_INT(0,"not in zone\n"); return; } const char * obj_name = Get_Parameter("Drop_Object"); bool doit = false; float probability = Get_Int_Parameter("Probability"); if (probability >= 100) { doit = true; } else { int random = Commands->Get_Random_Int(0,100); doit = random < probability; } if ((obj_name != NULL) && (doit)) { Vector3 spawn_location = Commands->Get_Position ( obj ); spawn_location.Z = spawn_location.Z + Get_Float_Parameter("Drop_Height"); GTH_DEBUG_INT(0,"Creating object "); GTH_DEBUG_INT(0,obj_name); GTH_DEBUG_FLOAT(spawn_location.X," x=%f, "); GTH_DEBUG_FLOAT(spawn_location.Y," y=%f, "); GTH_DEBUG_FLOAT(spawn_location.Z," z=%f, "); Commands->Create_Object ( obj_name, spawn_location ); } } }; /* ** GTH_Zone_Send_Custom (verified) ** This script lets you send a custom message to an object on enter and exit of a zone. To talk ** to the "drop in death zone" script, send the same custom message with 1 for Enter_Param and ** 0 for Exit_Param... ** ** Params: ** Enter_Message = message id to send when an object enters this zone ** Enter_Param = message parameter to send when an object enters ** Exit_Message = message id to send when an object exits ** Exit_Param = message id to send when and object exits */ DECLARE_SCRIPT(GTH_Zone_Send_Custom, "Enter_Message=20000:int,Enter_Param=1:int,Exit_Message=20000:int,Exit_Param=0:int") { void Entered( GameObject * obj, GameObject * enterer ) { int message = Get_Int_Parameter("Enter_Message"); GTH_DEBUG_INT(message,"GTH_Zone_Send_Custom sending enter message %d\n"); if (message != 0) { Commands->Send_Custom_Event(obj,enterer,message,Get_Int_Parameter("Enter_Param")); } } void Exited( GameObject * obj, GameObject * exiter ) { int message = Get_Int_Parameter("Exit_Message"); GTH_DEBUG_INT(message,"GTH_Zone_Send_Custom sending exit message %d\n"); if (message != 0) { Commands->Send_Custom_Event(obj,exiter,message,Get_Int_Parameter("Exit_Param")); } } }; /* ** GTH_Create_Object_On_Enter (verified) ** This script will create an object when a script zone is entered by a game object. Use it ** to fire off cinematics for example... ** ** Params: ** Create_Object - name of the preset to create an instance of ** Position - world space position to create the object at ** Min_Delay - amount of time to wait before re-enabling the script once it has fired ** Max_Creations - maximum number of times the script should create an object ** Probability - integer between 1 and 100, chance on any given "Enter" that the object will be created ** Player_Type - type of player that can trigger integer, 0 = Nod, 1 = GDI, 2 = any */ DECLARE_SCRIPT(GTH_Create_Object_On_Enter, "Create_Object=:string,Position:vector3,Min_Delay=10:int,Max_Creations=1:int,Probability=100:int,Player_Type=2:int") { bool script_enabled; int trigger_count; enum { TIMER_ID_REENABLE = 0 }; public: GTH_Create_Object_On_Enter(void) : script_enabled(true), trigger_count(0) { } protected: REGISTER_VARIABLES() { SAVE_VARIABLE(script_enabled, 1); SAVE_VARIABLE(trigger_count, 2); } void Entered( GameObject * obj, GameObject * enterer ) { if (script_enabled == false) { return; } // Check player type, if it doesn't match, just return int our_player_type = Get_Int_Parameter("Player_Type"); if (our_player_type != 2) { int enter_player_type = Commands->Get_Player_Type(enterer); if (enter_player_type != our_player_type) { return; } } // Check probability, if the check fails, just return float probability = Get_Int_Parameter("Probability"); if (probability < 100) { int random = Commands->Get_Random_Int(0,100); if (random > probability) { return; } } GTH_DEBUG_INT( 0, "Enter callback in GTH_Create_Object_On_Enter" ); // Inc our trigger count, if we're allowed to fire again, set a timer to re-enable the script trigger_count++; script_enabled = false; if (trigger_count < Get_Int_Parameter("Max_Creations")) { Commands->Start_Timer(obj, this, Get_Float_Parameter("Min_Delay"), TIMER_ID_REENABLE); } // Ok, create the object const char * obj_name = Get_Parameter("Create_Object"); Vector3 obj_pos = Get_Vector3_Parameter("Position"); if (obj_name != NULL) { GTH_DEBUG_INT( 0, obj_name ); GTH_DEBUG_INT( obj_pos.X, " x: %f, " ); GTH_DEBUG_INT( obj_pos.Y, "y: %f, " ); GTH_DEBUG_INT( obj_pos.Z, "z: %f\n" ); Commands->Create_Object ( obj_name, obj_pos ); } } void Timer_Expired( GameObject * obj, int timer_id ) { if (timer_id == TIMER_ID_REENABLE) { script_enabled = true; } } }; #if 0 /* ** GTH_Speed_Controlled_Anim ** NOTE: THIS PROBABLY WON'T WORK IN A NET GAME ANYWAY.... DELETE ME ** Stop_Speed - float, maximum speed where the object is considered "stopped" ** Stop_Anim - name of the anim to play when the unit is "stopped" ** Walk_Speed - float, max speed where the object is considered "walking" ** Walk_Anim - name of the anim to play when the unit is "walking" ** Run_Anim - name of anim to play when the unit is moving faster than "walking" ** Update_Delay - delay between re-evaluating the state of the object (0.1 = 10 times a second) */ DECLARE_SCRIPT(GTH_Speed_Controlled_Anim,"Stop_Speed=0.1:float,StopAnim=none:string,Walk_Speed=5.0:float,Walk_Anim=none:string,Run_Anim=none:string,Update_Rate=0.1:float") { enum { STOPPED = 0, WALKING, RUNNING }; enum { TIMER_ID_TICK = 0xbeef }; int cur_state; Vector3 last_pos; REGISTER_VARIABLES() { SAVE_VARIABLE(cur_state, 1); SAVE_VARIABLE(last_pos, 2); } void Created( GameObject * obj ) { // initialize the state cur_state = -1; last_pos = Commands->Get_Position( obj ); update_state(obj,eval_speed(obj)); // start up our update timer Commands->Start_Timer(obj, this, Get_Float_Parameter("Update_Rate"), TIMER_ID_TICK); } void ( * Set_Animation )( GameObject * obj, const char * anim_name, bool looping, const char * sub_obj_name = NULL, float start_frame = 0.0F, float end_frame = -1.0F, bool is_blended = false ); void Timer_Expired( GameObject * obj, int timer_id ) { if (timer_id == TIMER_ID_TICK) { update_state(obj,eval_speed(obj)); Commands->Start_Timer(obj, this, get_update_rate(), TIMER_ID_TICK); } } float get_update_rate( void ) { float update_rate = Get_Float_Parameter("Update_Rate"); if (update_rate <= 0.01f) { update_rate = 0.01f; } return update_rate; } int eval_speed( GameObject * obj ) { // This function evaluates the object's current speed and returns the state Vector3 cur_pos = Commands->Get_Position(obj); Vector3 vel = (cur_pos - last_pos) / get_update_rate(); last_pos = cur_pos; float speed = vel.Length(); if (speed <= Get_Float_Parameter("Stop_Speed")) { return STOPPED; } if (speed <= Get_Float_Parameter("Walk_Speed")) { return WALKING; } return RUNNING; } void update_state( GameObject * obj, int new_state ) { // This function plugs in the animation depending on the current state if (new_state != cur_state) { cur_state = new_state; const char * anim = NULL; switch( cur_state ) { case STOPPED: { anim = Get_Parameter("Stop_Anim"); } break; case WALKING: { anim = Get_Parameter("Walk_Anim"); } break; case RUNNING: { anim = Get_Parameter("Run_Anim"); } break; }; if (anim != NULL) { Commands->Set_Animation( obj, anim, true ); } } } }; #endif /* ** GTH_On_Enter_Mission_Complete (verified) ** When you enter a zone with this script on it, the mission is complete. NOTE, this only ** works in single player levels ** ** Parameters: ** Success - 0 = mission failed, 1 = missuion succeeded ** Player_Type - type of player allowed to trigger, 0=nod, 1=gdi, 2=any */ DECLARE_SCRIPT(GTH_On_Enter_Mission_Complete, "Success=1:int, Player_Type=2:int" ) { void Entered( GameObject * obj, GameObject * enterer ) { GTH_DEBUG_INT(0,"GTH_On_Enter_Mission_Complete::Entered\n"); // check the player type int our_player_type = Get_Int_Parameter("Player_Type"); GTH_DEBUG_INT(our_player_type,"Script desired player type: %d\n"); if (our_player_type != 2) { int enter_player_type = Commands->Get_Player_Type(enterer); GTH_DEBUG_INT(enter_player_type,"Enterer player type: %d\n"); if (enter_player_type != our_player_type) { return; } } // if we get here, complete the mission int success = Get_Int_Parameter("Success"); GTH_DEBUG_INT(success,"Calling Mission_Complete(%d)\n"); if (success == 0) { Commands->Mission_Complete(false); } else { Commands->Mission_Complete(true); } } }; /* ** GTH_On_Death_Mission_Complete (verified) ** When you kill something this script on it, the mission is complete. NOTE this ** only works in single-player levels. ** ** Parameters: ** Success - 0 = mission failed, 1 = missuion succeeded ** Player_Type - type of player allowed to trigger, 0=nod, 1=gdi, 2=any */ DECLARE_SCRIPT(GTH_On_Killed_Mission_Complete, "Success=1:int, Player_Type=2:int" ) { void Killed( GameObject * obj, GameObject * killer ) { GTH_DEBUG_INT(0,"GTH_On_Killed_Mission_Complete::Killed\n"); // check the player type int our_player_type = Get_Int_Parameter("Player_Type"); GTH_DEBUG_INT(our_player_type,"Script desired player type: %d\n"); if (our_player_type != 2) { int enter_player_type = Commands->Get_Player_Type(killer); GTH_DEBUG_INT(enter_player_type,"Enterer player type: %d\n"); if (enter_player_type != our_player_type) { return; } } // if we get here, complete the mission int success = Get_Int_Parameter("Success"); GTH_DEBUG_INT(success,"Calling Mission_Complete(%d)\n"); if (success == 0) { Commands->Mission_Complete(false); } else { Commands->Mission_Complete(true); } } }; /* ** GTH_Create_Objective ** Adds an objective to the mission when the specified action (create, enter, poke, or kill) ** happens to the object with this script on it. ** ** params: ** Creation_Type - 0=Create, 1=Entered, 2=Poked, 3=Killed ** Objective_ID - id of the objective, match this with the "GTH_Objective_Complete" script ** Objective_Type - 0=PRIMARY, 1=SECONDARY ** Short_Desc_ID - string id for short description ** Long_Desc_ID - string id for long description ** Priority - priority of this objective ** Position - 3d position of the objective ** Pog_Texture - tga file for the objective pog ** Pog_Text_ID - string id for the pog text (usually something like IDS_POG_DESTROY) ** */ DECLARE_SCRIPT(GTH_Create_Objective,"Creation_Type=0:int,Objective_ID=0:int,Objective_Type=0:int,Short_Desc_ID=0:int,Long_Desc_ID=0:int,Priority=90:float,Position:vector3,Pog_Texture:string") { bool already_triggered; REGISTER_VARIABLES() { SAVE_VARIABLE(already_triggered, 1); } void Created( GameObject * obj ) { if (Get_Int_Parameter("Creation_Type") == 0) create_objective(); } void Entered( GameObject * obj, GameObject * enterer ) { if (Get_Int_Parameter("Creation_Type") == 1) create_objective(); } void Poked( GameObject * obj, GameObject * poker ) { if (Get_Int_Parameter("Creation_Type") == 2) create_objective(); } void Killed( GameObject * obj, GameObject * killer ) { if (Get_Int_Parameter("Creation_Type") == 3) create_objective(); } void create_objective(void) { if (already_triggered) { return; } // create the objective int id = Get_Int_Parameter("Objective_ID"); int type; if (Get_Int_Parameter("Objective_Type") == 0) { type = OBJECTIVE_TYPE_PRIMARY; } else { type = OBJECTIVE_TYPE_SECONDARY; } int short_desc_id = Get_Int_Parameter("Short_Desc_ID"); int long_desc_id = Get_Int_Parameter("Long_Desc_ID"); Commands->Add_Objective(id,type,OBJECTIVE_STATUS_PENDING,short_desc_id, NULL, long_desc_id ); // set up the radar blip Vector3 objective_pos = Get_Vector3_Parameter("Position"); Commands->Set_Objective_Radar_Blip(id, objective_pos); // set up the hud stuff const char * pog = Get_Parameter("Pog_Texture"); float priority = Get_Float_Parameter("Priority"); int pog_text_id = Get_Int_Parameter("Pog_Text_ID"); Commands->Set_Objective_HUD_Info_Position(id, priority, pog, pog_text_id, objective_pos); } }; /* ** GTH_Objective_Complete ** Ends an objective with either success or failure. All of the following things ** cause the objective to complete: "Entered", "Killed", or "Poked" ** ** param ** Objective_ID - id of the objective ** Success - 0 or 1, success or failure ** Player_Type - player type allowed to trigger this. 0=nod, 1=gdi, 2=any */ DECLARE_SCRIPT(GTH_Objective_Complete_Enter_Kill_Poke, "Objective_ID=0:int, Success=1:int, Player_Type=2:int" ) { bool already_triggered; REGISTER_VARIABLES() { SAVE_VARIABLE(already_triggered, 1); } void Created( GameObject * obj ) { already_triggered = false; } void Poked( GameObject * obj, GameObject * poker ) { trigger(poker); } void Killed( GameObject * obj, GameObject * killer ) { trigger(killer); } void Entered( GameObject * obj, GameObject * enterer ) { trigger(enterer); } void trigger(GameObject * obj) { // check if we've already triggered if (already_triggered) { return; } // check the player type int our_player_type = Get_Int_Parameter("Player_Type"); if (our_player_type != 2) { int other_player_type = Commands->Get_Player_Type(obj); if (other_player_type != our_player_type) { return; } } // if we get here, complete the mission int success = Get_Int_Parameter("Success"); int id = Get_Int_Parameter("Objective_ID"); if (success == 0) { Commands->Set_Objective_Status(id, 0); } else { Commands->Set_Objective_Status(id, 1); } already_triggered = true; } }; /* ** GTH_User_Controllable_Base_Defense (verified) ** Just like M00_Base_Defense except that if a player enters, he can control the object ** ** params: ** MinAttackDistance - min range for auto attack ** MaxAttackDistance - max range for auto attack ** AttackTimer - amount of time to continue tracking after last "enemy seen" */ DECLARE_SCRIPT (GTH_User_Controllable_Base_Defense, "MinAttackDistance=0:int, MaxAttackDistance=300:int, AttackTimer=10:int") { int token_01_id; int token_02_id; int token_03_id; int player_type; bool occupied; REGISTER_VARIABLES() { SAVE_VARIABLE (token_01_id, 1); SAVE_VARIABLE (token_02_id, 2); SAVE_VARIABLE (token_03_id, 3); SAVE_VARIABLE (player_type, 4); SAVE_VARIABLE (occupied, 5); } void Created (GameObject * obj) { occupied = false; // find out what my team preset is. player_type = Commands->Get_Player_Type( obj ); Commands->Debug_Message( "***** Player Type Saved *****\n" ); Commands->Enable_Hibernation (obj, false); Commands->Innate_Enable (obj); Commands->Enable_Enemy_Seen (obj, true); Vector3 my_position = Commands->Get_Position (obj); Vector3 token_01_pos = my_position; Vector3 token_02_pos = my_position; Vector3 token_03_pos = my_position; token_01_pos.X -= 10.0f; token_01_pos.Y -= 10.0f; token_01_pos.Z += 2.0f; token_02_pos.X += 10.0f; token_02_pos.Z += 2.0f; token_03_pos.X += 10.0f; token_03_pos.Y -= 10.0f; token_03_pos.Z += 2.0f; GameObject * token_01 = Commands->Create_Object ("Invisible_Object", token_01_pos); if (token_01) { token_01_id = Commands->Get_ID (token_01); } token_01 = Commands->Create_Object ("Invisible_Object", token_02_pos); if (token_01) { token_02_id = Commands->Get_ID (token_01); } token_01 = Commands->Create_Object ("Invisible_Object", token_03_pos); if (token_01) { token_03_id = Commands->Get_ID (token_01); } Commands->Start_Timer (obj, this, 10.0f, 1); } void Timer_Expired (GameObject * obj, int timer_id) { if (timer_id == 1) { int rnd_num = Get_Int_Random(0,2); if (occupied == false) { GTH_DEBUG_INT(rnd_num,"GTH_User_Controllable_Base_Defense aiming at target %d\n"); switch (rnd_num) { case (0): { GameObject * token_01 = Commands->Find_Object (token_01_id); if (token_01) { ActionParamsStruct params; params.Set_Basic(this, 70, 1); params.Set_Attack(token_01, 0.0f, 0.0f, true); Commands->Action_Attack(obj, params); } break; } case (1): { GameObject * token_02 = Commands->Find_Object (token_02_id); if (token_02) { ActionParamsStruct params; params.Set_Basic(this, 70, 1); params.Set_Attack(token_02, 0.0f, 0.0f, true); Commands->Action_Attack(obj, params); } break; } default: { GameObject * token_03 = Commands->Find_Object (token_03_id); if (token_03) { ActionParamsStruct params; params.Set_Basic(this, 70, 1); params.Set_Attack(token_03, 0.0f, 0.0f, true); Commands->Action_Attack(obj, params); } } } } Commands->Start_Timer (obj, this, 10.0f, 1); } else if (timer_id == 2) { Commands->Action_Reset (obj, 100.0f); } } void Enemy_Seen (GameObject * obj, GameObject * enemy) { GTH_DEBUG_INT(occupied,"GTH_User_Controllable_Base_Defense::Enemy_Seen, occupied = %d\n"); if (occupied == false) { Vector3 my_loc = Commands->Get_Position (obj); Vector3 enemy_loc = Commands->Get_Position (enemy); float distance = Commands->Get_Distance (my_loc, enemy_loc); if (distance > Get_Int_Parameter("MinAttackDistance") ) { ActionParamsStruct params; params.Set_Basic(this, 100, 2); params.Set_Attack(enemy, Get_Int_Parameter("MaxAttackDistance"), 0.0f, true); params.AttackCheckBlocked = false; Commands->Action_Attack(obj, params); Commands->Start_Timer (obj, this, Get_Int_Parameter( "AttackTimer"), 2); } } } void Action_Complete (GameObject * obj, int action_id, ActionCompleteReason complete_reason) { if (action_id == 2) { Commands->Action_Reset (obj, 100.0f); } } void Custom(GameObject * obj, int type, int param, GameObject * sender) { ActionParamsStruct params; switch (type) { case CUSTOM_EVENT_VEHICLE_ENTERED: occupied = true; params.Set_Basic(this, 100, 3); Commands->Action_Follow_Input( obj, params ); break; case CUSTOM_EVENT_VEHICLE_EXITED: occupied = false; // set team back to my preset. Commands->Set_Player_Type( obj, player_type ); Commands->Action_Reset (obj, 100.0f); break; } GTH_DEBUG_INT(occupied,"GTH_User_Controllable_Base_Defense occupied = %d\n"); } }; /* ** GTH_Credit_Trickle ** This script will give an amount money to its team at a regular interval. You can use it to ** create silos that give money as long as they're alive. ** NOTE: this won't work on buildings, only things like turrets, characters, or vehicles so make your ** "silos" as a weaponless vehcile set up like the nod-turret for example. ** ** Params: ** Credits - number of credits to give ** Delay - time between credit grants */ DECLARE_SCRIPT(GTH_Credit_Trickle, "Credits=1:int,Delay=2.0:float") { enum { TRICKLE_TIMER = 667 }; void Created (GameObject * obj) { // start the trickle timer Commands->Start_Timer (obj, this, Get_Float_Parameter("Delay"), TRICKLE_TIMER); } void Timer_Expired (GameObject * obj, int timer_id) { if (timer_id == TRICKLE_TIMER) { Commands->Give_Money( obj, Get_Int_Parameter("Credits"), true ); Commands->Start_Timer (obj, this, Get_Int_Parameter("Delay"), TRICKLE_TIMER); } } }; /* ** GTH_Enable_Spawner_On_Enter ** This script will enable or disable a spawner when its zone is entered ** ** Params: ** SpawnerID - id of the spawner ** Player_Type - type of player that can trigger integer, 0 = Nod, 1 = GDI, 2 = any ** Enable - enable or disable the spawner (1=enable, 0=disable) */ DECLARE_SCRIPT(GTH_Enable_Spawner_On_Enter, "SpawnerID=0:int,Player_Type=2:int,Enable=1:int") { void Entered( GameObject * obj, GameObject * enterer ) { // Check player type, if it doesn't match, just return int our_player_type = Get_Int_Parameter("Player_Type"); if (our_player_type != 2) { int enter_player_type = Commands->Get_Player_Type(enterer); if (enter_player_type != our_player_type) { return; } } // Now enable the spawner bool enable = (Get_Int_Parameter("Enable") != 0); Commands->Enable_Spawner( Get_Int_Parameter("SpawnerID"),enable); } }; /* ** GTH_CTF_Object ** This script will make the object it is attached to behave kind of like a CTF "flag" by ** attaching to the opposing player who pokes it. If its position gets within a ** certain distance of the "enemy home" an internal counter is incremented. Once the counter ** reaches a desired number, an object in the level is destroyed. This object should be the ** only building owned by the flag's team so that they immediately lose. ** ** You should use this script on an object that has "projectile" collision only. Make a model ** of your "flag", give it projectile collision and make a preset for it similar to the "Marker_Flag" ** in LevelEdit (you can find "Marker_Flag" under Object->Simple). ** ** CTF Rules as implemented: ** - this script controls how many flag captures cause a win ** - enemy entering the pickup radius of the flag attaches it to him ** - enemy teammates can take the flag from each other ** - friend poking the flag sends it back to its initial position (only if its not carried by an enemy) ** - enemy getting the flag to his home position increments an internal counter ** - when win count is reached, all of the "Win_Object_To_Kill" objects are destroyed (make sure ** this destroys all of the enemy team's buildings ** ** Params: ** Update_Delay - how many times per second to update (this will *always* be laggy though...) ** Enemy_Player_Type - type of player that wants to grab this flag (0=Nod,1=GDI) ** Enemy_Home_Position - when flag gets here, capture count increments! ** Pickup_Radius - how close to flag we need to pickup flag ** Home_Radius - how close to enemy home position we need to get to count ** Capture_Respawn_Timer - When flag is dropped respawn back at home position after x seconds. ** x < 0 means flag MUST be poked for it to be returned. ** Captures_Needed_To_Win - after this many captures, we destroy the token "building" for the win ** Flag_Stolen_Event_ID - custom event id to send when the flag is stolen ** Flag_Stolen_Object_ID - Object that receives the event ** Flag_Lost_Event_ID - custom event id to send when enemy team gets flag back to Enemy_Home_Position :-( ** Flag_Lost_Object_ID - Object that receives the event ** Flag_Saved_Event_ID - custom event id to send when flag carrier is killed ** Flag_Saved_Object_ID - Object that receives the event ** Flag_Returned_Event_ID - custom event id to send when flag has been returned to home position. ** Flag_Returned_Object_ID - Object that receives the event ** Captures_Exceeded_Event_ID - custom event id to send when flag has been captured "Captures_Needed_To_Win" times. ** Captures_Exceeded_Object_ID - Object that receives the event ** Win_Object_To_Kill0 - object that we destroy when the capture count is reached ** Win_Object_To_Kill1 - object that we destroy when the capture count is reached ** Win_Object_To_Kill2 - object that we destroy when the capture count is reached ** Win_Object_To_Kill3 - object that we destroy when the capture count is reached ** Win_Object_To_Kill4 - object that we destroy when the capture count is reached ** */ DECLARE_SCRIPT(GTH_CTF_Object2, "Update_Delay=0.05:float,Enemy_Player_Type=0:int,Enemy_Home_Position:vector3," "Pickup_Radius=5.0:float,Home_Radius=5.0:float,Capture_Respawn_Timer=30.0:float," "Captures_Needed_To_Win=5:int," "Flag_Stolen_Event_ID=25000:int,Flag_Stolen_Object_ID=0:int,Flag_Lost_Event_ID=25001:int," "Flag_Lost_Object_ID=0:int,Flag_Saved_Event_ID=25002:int,Flag_Saved_Object_ID=0:int," "Flag_Returned_Event_ID=25003:int,Flag_Returned_Object_ID=0:int," "Captures_Exceeded_Event_ID=25004:int,Captures_Exceeded_Object_ID=0:int," "Win_Object_To_Kill0=0:int,Win_Object_To_Kill1=0:int,Win_Object_To_Kill2=0:int," "Win_Object_To_Kill3=0:int,Win_Object_To_Kill4=0:int") { enum { CTF_UPDATE_TIMER = 0x10211131 }; int capture_count; int captured_by_id; Vector3 home_position; float capture_timer; REGISTER_VARIABLES() { SAVE_VARIABLE (capture_count, 1); SAVE_VARIABLE (captured_by_id, 2); SAVE_VARIABLE (home_position, 3); SAVE_VARIABLE (capture_timer, 4); } void Created (GameObject * obj) { // Initialize everything capture_count = 0; captured_by_id = 0; capture_timer = 0.0; home_position = Commands->Get_Position(obj); GTH_DEBUG_FLOAT(home_position.X,"Flag x: %f"); GTH_DEBUG_FLOAT(home_position.Y,"Flag y: %f"); GTH_DEBUG_FLOAT(home_position.Z,"Flag z: %f\n"); // Start the update timer! Commands->Start_Timer (obj, this, Get_Float_Parameter("Update_Delay"), CTF_UPDATE_TIMER); } void Damaged( GameObject * obj, GameObject * damager, float amount ) { Grab_Flag(obj,damager); } // void Poked( GameObject * obj, GameObject * poker ) // { // Grab_Flag(obj,poker); // } void Grab_Flag( GameObject * obj, GameObject * grabber ) { // If we're already captured, bail if (captured_by_id != 0) { return; } // Check distance, if the damager is too far away, bail Vector3 my_pos = Commands->Get_Position(obj); Vector3 delta = my_pos - Commands->Get_Position(grabber); if (delta.Length() > Get_Float_Parameter("Pickup_Radius")) { GTH_DEBUG_FLOAT(delta.Length(),"Flag too far away, dist = %f\n"); return; } // Check player type, if it doesn't match, warp back to our home position int capture_player_type = Get_Int_Parameter("Enemy_Player_Type"); int poke_player_type = Commands->Get_Player_Type(grabber); // Someone poked the flag...see if it needs to respawn back at home. if (capture_player_type != 2) { if (poke_player_type != capture_player_type) { // if we're not being carried and the flag isn't on a timer, warp back if (captured_by_id == 0 && Get_Float_Parameter("Capture_Respawn_Timer") < 0) { Commands->Set_Position(obj,home_position); GTH_DEBUG_INT(poke_player_type,"Flag recovered back by team: %d\n"); // Play flag returned sound int cust_id = Get_Int_Parameter("Flag_Returned_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Flag_Returned_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); } return; } } // The player type matches, Attach to this dude! captured_by_id = Commands->Get_ID(grabber); // attach to bone 0 (root) Commands->Attach_To_Object_Bone(obj,grabber,"ROOTTRANSFORM"); GTH_DEBUG_INT(poke_player_type,"Flag captured by team: %d\n"); // Play picked up sound int cust_id = Get_Int_Parameter("Flag_Stolen_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Flag_Stolen_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); } void Timer_Expired (GameObject * obj, int timer_id) { if (timer_id == CTF_UPDATE_TIMER) { // Search for a player around the flag and let them auto-grab it. // BMH 12/7/02 - Get_A_Star only returns the closest to the position. So if someone just sat // on top of the flag that's the only star we'd ever check. So check from a random position // within the pickup radius Vector3 my_pos = Commands->Get_Position(obj); Vector3 find_pos = my_pos; float pick_rad = Get_Float_Parameter("Pickup_Radius"); float rval = Commands->Get_Random(-pick_rad, pick_rad); find_pos.X += rval; rval = Commands->Get_Random(-pick_rad, pick_rad); find_pos.Y += rval; rval = Commands->Get_Random(-pick_rad, pick_rad); find_pos.Z += rval; GameObject *star_obj = Commands->Get_A_Star(find_pos); Grab_Flag(obj,star_obj); // check if we've been captured enough to end the game if (capture_count >= Get_Int_Parameter("Captures_Needed_To_Win")) { // Play Win Sound! int cust_id = Get_Int_Parameter("Captures_Exceeded_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Captures_Exceeded_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); // win! GameObject * win_obj = Commands->Find_Object(Get_Int_Parameter("Win_Object_To_Kill0")); if (win_obj != NULL) { Commands->Destroy_Object( win_obj ); } win_obj = Commands->Find_Object(Get_Int_Parameter("Win_Object_To_Kill1")); if (win_obj != NULL) { Commands->Destroy_Object( win_obj ); } win_obj = Commands->Find_Object(Get_Int_Parameter("Win_Object_To_Kill2")); if (win_obj != NULL) { Commands->Destroy_Object( win_obj ); } win_obj = Commands->Find_Object(Get_Int_Parameter("Win_Object_To_Kill3")); if (win_obj != NULL) { Commands->Destroy_Object( win_obj ); } win_obj = Commands->Find_Object(Get_Int_Parameter("Win_Object_To_Kill4")); if (win_obj != NULL) { Commands->Destroy_Object( win_obj ); } GTH_DEBUG_INT(0,"Capture count exceeded\n"); return; } // check if we're in our enemies home position Vector3 delta = my_pos - Get_Vector3_Parameter("Enemy_Home_Position"); float dist = delta.Length(); if (dist < Get_Float_Parameter("Home_Radius")) { // Increment the capture count, detatch from our carrier and warp home capture_count++; captured_by_id = 0; Commands->Attach_To_Object_Bone(obj,NULL,NULL); Commands->Set_Position(obj,home_position); GTH_DEBUG_INT(capture_count,"Flag stolen, counter now %d, returning to home!\n"); // Play flag capture sound int cust_id = Get_Int_Parameter("Flag_Lost_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Flag_Lost_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); } // check if we have a capturer and need to update if (captured_by_id != 0) { GameObject * capturer = Commands->Find_Object(captured_by_id); if (capturer != NULL) { // Don't do this cause we're using the attach to bone method! //Vector3 cap_position = Commands->Get_Position(capturer); //Commands->Set_Position(obj,cap_position); } else { captured_by_id = 0; // start the flag respawn timer. capture_timer = (((float)Commands->Get_Sync_Time()) / 1000.0); Commands->Attach_To_Object_Bone(obj,NULL,NULL); GTH_DEBUG_INT(0,"Capturer killed!\n"); // Play flag saved sound int cust_id = Get_Int_Parameter("Flag_Saved_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Flag_Saved_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); } } // Check if the flag has been sitting idle for too long and return it if need be. float ctimer = Get_Float_Parameter("Capture_Respawn_Timer"); if (captured_by_id == 0 && ctimer > 0) { // Make sure the flag isn't already at the base delta = my_pos - home_position; dist = delta.Length(); if (dist > Get_Float_Parameter("Pickup_Radius")) { // has the Respawn_Timer expired if (((((float)Commands->Get_Sync_Time()) / 1000.0) - capture_timer) > ctimer) { // Return the flag Commands->Set_Position(obj,home_position); GTH_DEBUG_INT(0,"Flag timeout has been hit, respawning flag back at home.\n"); // Play flag returned sound int cust_id = Get_Int_Parameter("Flag_Returned_Event_ID"); GameObject * event_obj = Commands->Find_Object(Get_Int_Parameter("Flag_Returned_Object_ID")); if (event_obj == NULL) event_obj = obj; Commands->Send_Custom_Event(obj,event_obj,cust_id, 1); } } } // register for another update! Commands->Start_Timer (obj, this, Get_Float_Parameter("Update_Delay"), CTF_UPDATE_TIMER); } } };