Starsiege Scripting Tutorial: AIs
by Orogogus


Creating an AI unit

The basic method for creating an AI is to include something like the following in a function:

NewObject("Sam", Herc, 21 );
This would create a Goad with an object name of "Sam." This object name never really has to come into play, and shows up primarily in the object list. Herc, right after "Sam", indicates the type of object being created. The two we should be concerned about in this lesson are Herc and Tank. The difference should be self-explanator, but be sure to get them right -- if you get them mixed up, then the server will crash the first time it tries to create a HERC that the script calls a tank, or vice versa.

The last number, 21, indicates the ID number of the unit being created. These can be found by extracting datVehicle.cs out from scripts.vol, but for convenience, the entire list is reproduced here (with some added comments):


# Terrain plain
buildHerc( 1, "H", "datHerc_tr_apoc.cs" ); // Terran Apocalypse
buildHerc( 2, "H", "datHerc_tr_mino.cs" ); // Terran Minotaur
buildHerc( 3, "H", "datHerc_tr_gorg.cs" ); // Terran Gorgon
buildHerc( 4, "H", "datHerc_tr_talo.cs" ); // Terran Talon
buildHerc( 5, "H", "datHerc_tr_basl.cs" ); // Terran Basilisk
buildTank( 6, "H", "datTank_tr_pala.cs" ); // Terran Paladin
buildTank( 7, "H", "datTank_tr_myrm.cs" ); // Terran Myrmidon
buildTank( 8, "H", "datTank_tr_disr.cs" ); // Terran Disrupter
buildFlyer(9, "datFlyer_tr_bans.cs" );     // Terran Banshee

# Knights
buildHerc( 10, "H", "datHerc_kn_apoc.cs" ); // Knight's Apocalypse
buildHerc( 11, "H", "datHerc_kn_mino.cs" ); // Knight's Minotaur
buildHerc( 12, "H", "datHerc_kn_gorg.cs" ); // Knight's Gorgon
buildHerc( 13, "H", "datHerc_kn_talo.cs" ); // Knight's Talon
buildHerc( 14, "H", "datHerc_kn_basl.cs" ); // Knight's Basilisk
buildTank( 15, "H", "datTank_kn_pala.cs" ); // Knight's Paladin
buildTank( 16, "H", "datTank_kn_myrm.cs" ); // Knight's Myrmidon
buildTank( 17, "H", "datTank_kn_disr.cs" ); // Knight's Disrupter

# Support vehicles
buildFlyer(18, "datFlyer_tr_carg.cs" );    // Terran Cargo Ship
buildFlyer(19, "datFlyer_tr_escape.cs" );  // Terran Escape Pod

# Cybrids
buildHerc( 20, "C", "datHerc_cy_seek.cs" ); // Seeker
buildHerc( 21, "C", "datHerc_cy_goad.cs" ); // Goad
buildHerc( 22, "C", "datHerc_cy_shep.cs" ); // Shepherd
buildHerc( 23, "C", "datHerc_cy_adju.cs" ); // Adjudicator
buildHerc( 24, "C", "datHerc_cy_exec.cs" ); // Executioner
buildTank( 25, "C", "datTank_cy_bolo.cs" ); // Bolo
buildHerc( 27, "C", "datHerc_pl_adju.cs" ); // Platinum Guard Adjudicator
buildHerc( 28, "C", "datHerc_pl_exec.cs" ); // Platinum Guard Executioner
buildTank( 26, "C", "datTank_cy_recl.cs" ); // Recluse
buildHerc( 29, "C", "datHerc_cy_prom.cs" ); // Prometheus

# Rebels
buildHerc( 30, "H", "datHerc_rb_eman.cs" ); // Emancipator
buildTank( 31, "H", "datTank_rb_aven.cs" ); // Avenger
buildTank( 32, "H", "datTank_rb_drea.cs" ); // Dreadlock
buildHerc( 33, "H", "datHerc_rb_oly.cs" );  // Olympian


#Metagen
buildHerc( 35, "C", "datHerc_mg_seek.cs" ); // Metagen Seeker
buildHerc( 36, "C", "datHerc_mg_goad.cs" ); // Metagen Goad
buildHerc( 37, "C", "datHerc_mg_shep.cs" ); // Metagen Shepherd
buildHerc( 38, "C", "datHerc_mg_adju.cs" ); // Metagen Adjudicator
buildHerc( 39, "C", "datHerc_mg_exec.cs" ); // Metagen Executioner

# Misc
buildHerc( 40, "H", "datHerc_ha_apoc.cs" );   // Harabec's Apocalypse
buildTank( 41, "H", "datTank_ha_pred.cs" );   // Harabec's Predator
buildHerc( 42, "H", "datHerc_ca_basl.cs" );   // Caanon's Basilisk
buildHerc( 43, "H", "datHerc_cin_apoc.cs" );  // Cinematic Apocalypse
buildHerc( 44, "H", "datHerc_cin_basl.cs" );  // Cinematic Basilisk
buildTank( 45, "H", "datTank_SuperPred.cs" ); // Harabec's Super Predator

# Pirates
buildHerc( 50, "H", "datHerc_pi_apoc.cs" );   // Pirate Apocalypse
buildTank( 51, "H", "datTank_pi_drea.cs" );   // Pirate Dreadlock
buildHerc( 52, "H", "datHerc_pi_eman.cs" );   // Pirate Emancipator

# More Cybrid (player pilotable platiunum guard versions
buildHerc( 55, "C", "datHerc_pl_adju2.cs" );  // Player's Platinum Guard Adjudicator
buildHerc( 56, "C", "datHerc_pl_exec2.cs" );  // Player's Platinum Guard Executioner


# Drones
exec("datDroneGeneric.cs");
buildDrone( 60, "datDrone_tr_fltb.cs");
buildDrone( 61, "datDrone_tr_ammo.cs");
buildDrone( 62, "datDrone_tr_big_ammo.cs");
buildDrone( 63, "datDrone_tr_big_personnel.cs");     
buildDrone( 64, "datDrone_tr_fuel.cs");      
buildDrone( 65, "datDrone_tr_mino.cs");          
buildDrone( 66, "datDrone_rb_fltb.cs");               
buildDrone( 67, "datDrone_rb_ammo.cs");           
buildDrone( 68, "datDrone_rb_big.cs");
buildDrone( 69, "datDrone_rb_big_box.cs");
buildDrone( 70, "datDrone_rb_box.cs");
buildDrone( 71, "datDrone_tr_util.cs");
buildDrone( 72, "datDrone_rb_thum.cs");
buildDrone( 73, "datDrone_tr_star.cs");
buildDrone( 135, "datDrone_tr_sove.cs" );
buildDrone( 136, "datDrone_tr_surv.cs" );

# More cybrid (can't change id's anymore)
buildTank( 90, "C", "datTank_cy_artl.cs" );
buildFlyer( 91, "datFlyer_cy_advo.cs" );
buildFlyer( 92, "datFlyer_cy_drop.cs" );
buildFlyer( 93, "datFlyer_cy_cons.cs" );
buildDrone( 94, "datDrone_cy_omni.cs" );
buildDrone( 95, "datDrone_cy_prot.cs" );
buildDrone( 96, "datDrone_cy_jamm.cs" );

# More knights
buildFlyer( 110, "datFlyer_kn_bans.cs" );
buildFlyer( 111, "datFlyer_kn_drop.cs" );

# More Terrain vehicles
buildFlyer( 130, "datFlyer_tr_drop.cs" );
buildFlyer( 131, "datFlyer_tr_drac.cs" );
buildFlyer( 132, "datFlyer_tr_conv.cs" );
buildTank(  133, "H", "datTank_tr_nike.cs" );
buildTank(  134, "H", "datTank_tr_supr.cs" );

# More Rebels
buildTank(  137, "H", "datTank_rb_artl.cs" );
buildTank(  138, "H", "datTank_rb_bike.cs" );


#Starsiege Magic Bus
buildTank(  150, "H", "datTank_ss_bus.cs" );


In any case, it's usual to modify that original function slightly, to something like
%herc = NewObject("Sam", Herc, 21 );
This allows us to manipulate the new HERC more easily. Since the new HERC spawns in at map coordinates (0, 0, 0), we often call a function like setPosition() or warp() to put a new unit where we want it right after it spawns. For instance:

%herc = NewObject("Sam", Herc, 21 );
setPosition(%herc, -43, 324, 43); 
Another, easier, way to create a new vehicle is to clone an existing one with cloneVehicle(%vehicleID). To change the previous example:
%herc = CloneVehicle(%playerVehicle);
setPosition(%herc, -43, 324, 43); 
Other functions often seen in conjunction with spawning a new unit include setTeam(), order() and setPilotId(). For example:

setTeam( %herc, *IDSTR_TEAM_PURPLE );
order( %herc, attack, %playerVeh );
setPilotId(%herc,28);
SetTeam() is easy enough to understand, as is order(), more or less. For reference, the allowed orders are
Clear
Attack
Guard
Formation
Speed
Retreat
Rotation
MakeLeader
HoldFire
HoldPosition
Cloak
UseActiveRadar
Shutdown
Height
Flythrough
ZigZag
Acknowledge
Not all of these will work in multiplayer, however, as some orders are particular to the single-player campaigns.

SetPilot() deserves more discussion. You don't need to use setPilot(), since any new vehicle will have a default profile assigned to it, but it's useful for customizing your AI units. Before you spawn the AI, outside of any functions, you normally include a pilot profile, which looks like

Pilot Deathgod
{
   id = 28;
   
   skill = 0.9;
   accuracy = 0.9;
   aggressiveness = 0.5;
   activateDist = 2000.0;
   deactivateBuff = 3000.0;
   targetFreq = 4.0;
   trackFreq = 0.1;
   fireFreq = 0.1;
   LOSFreq = 0.4;
   name = "Yama";
};
You need to include the word Pilot at the beginning, followed by a name for the profile (which won't show up in the game, only in the script). Also notice the semicolon after the closing curly bracket.

Each AI profile you create must have a unique id, or else the last one will over overwrite any earlier ones with the same number. Skill, accuracy and aggressiveness are what they sound like. activateDist determines how far away an AI will detect a player (I don't think there's any way to bind this to the in-game radar profiles, unfortunately), and deactivateBuff is the distance at which an AI will break off from chasing a target. TargetFreq determines how often an AI looks for a new target (set it too high, and it will barrel singlemindedly at its first target and ignore any others that start firing on it). I'm not certain what trackfreq does. fireFreq determines the rate of weapons fire for the pilot, and LOSFreq affects how often the AI will try to shoot through solid objects. The name is the one that shows up on the overhead map or in the HUD if the AI has been scanned or is on the same team. (If an enemy AI has not been scanned, it will be listed by the name of its vehicle, instead).

An interesting phenomenon is that these pilot properties can be accessed and altered by the profile name (e.g., in the above example, Deathgod) with dot operators. Echo(Deathgod.name) will return "Yama" -- note the absence of a percent or dollar sign in front of Deathgod. I'm not entirely certain how or why this works, but it does. Once setPilot() is run on an AI unit, however, the properties stick to it, and stay put even if the pilot profile is changed. You can alter a vehicle's pilot properties after setPilot() only be running another setPilot() on it. This also means, however, that you can create different pilots using just one profile. The following script is such an example. Whenever a player spawns into the game, an AI pilot is cloned from the player's vehicle, ordered to guard him, and comes equipped with the name Mecha-(player name). For instance, if I jump in this game, my AI pilot will be called Mecha-Orogogus.
$missionName = "Wingman"; 
exec("multiplayerStdLib.cs"); 

function setDefaultMissionOptions()
{
   $server::TeamPlay = true;
   $server::AllowDeathmatch = false;
   $server::AllowTeamPlay = true;
 
   // what can the client choose for a team
   $server::AllowTeamRed = true;
   $server::AllowTeamBlue = true;
   $server::AllowTeamYellow = false;
   $server::AllowTeamPurple = false;
 
   // what can the server admin choose for available teams
   $server::disableTeamRed = false;
   $server::disableTeamBlue = false;
   $server::disableTeamYellow = true;
   $server::disableTeamPurple = true;

   $server::AllowMixedTech = "False";
   $server::MaxPlayers = 10;
   $server::MassLimit = 0;
} 


Pilot JSmith
{
   id = 28;
   
   skill = 0.9;
   accuracy = 0.9;
   aggressiveness = 0.4;
   activateDist = 500.0;
   deactivateBuff = 1000.0;
   targetFreq = 2.0;
   trackFreq = 0.1;
   fireFreq = 0.1;
   LOSFreq = 0.4;
   name = "Jane Smith";
};


function vehicle::OnAdd(%vehicleId)
{
  %player = playerManager::vehicleIdToPlayerNum(%vehicleId);
  if( %player == 0) return;
  schedule("addWingman(" @ %vehicleId @ ");", 5);
} 


function addWingman(%vehicleId)
{
  %player = playerManager::vehicleIdToPlayerNum(%vehicleId);

  %herc = cloneVehicle(%vehicleId); 
  %vehicleId.wingman = %herc;
  %herc.wingleader = %vehicleId;
  Jsmith.name = "Mecha-" @ getHudName(%vehicleId);
  setPilotId(%herc, 28);

  %x = getPosition(%vehicleId, x);
  %y = getPosition(%vehicleId, y);
  %z = getTerrainHeight(%x, %y);

  setPosition(%herc, (%x - 20), %y, %z );
  setHercOwner(%herc, %vehicleId);
  order(%herc, guard, %vehicleId);
}


function vehicle::OnDestroyed(%destroyed, %destroyer) 
{
  %message = getFancyDeathMessage(getHUDName(%destroyed), getHUDName(%destroyer));
  if(%message != "")
  {
    say( 0, 0, %message);
  }

  if( playerManager::vehicleIdToPlayerNum(%destroyed) != 0)
  {
     deleteObject(%destroyed.wingman);
  }

  else schedule( "addWingman(" @ %destroyed.wingleader @ ");", 5);
}













1