// Sample ET script. // Scripts for bots are executed as part of the bots initialization process // When this script is executed, there is a variable called 'this' that references the bot // Properties can be set on 'this' and functions can be called on 'this' to tweak the bots behavior print("Sample Script for Enemy Territory"); //////////////////////////////////////////// // Customize some general bot properties. // //////////////////////////////////////////// // FieldOfView is the angle(in degrees) that the bot can 'see' in front of them. this.FieldOfView = 90.0; // ReactionTime is the time delay(in seconds) from when a bot first see's a target, to when // the bot will begin to react and target them. this.ReactionTime = 0; // MemorySpan is how long it takes(in seconds) for a bot to consider his memory of someone or something // 'out of date' and not considered for targeting and such this.MemorySpan = 2.0; // AimPersistance is how long the bot will aim in the direction of a target after the target has gone out of view. // This is useful for keeping the bot aiming toward the target in the event of brief obstructions of their view. this.AimPersistance = 2.0; // MaxViewDistance is the maximum distance(in game units) the bot is capable of seeing something. // This could be tweaked lower for maps with fog or for a closer to human view distance // Typically this value is best set in the map script in the OnBotJoin callback. //this.MaxViewDistance = 10000.0; // These 3 values are aim properties. Care must be taken when tweaking aim properties, since // improper values can produce aim oscillations and hurt the bots combat abilities. this.MaxTurnSpeed = 720.0; // degree's / second this.AimStiffness = 75.0; this.AimDamping = 10.0; // AimTolerance is an angle tolerance that effects the bot aiming and targeting. When a bot acquires a target // and turns toward the aim position for the target, the AimTolerance is the angle that the aim vector must be within // in order to be considered close enough to begin firing. This parameter is used by the TurnTo functions of the bot this.AimTolerance = 3.0; ///////////////////////////////////// // Register bot weapon properties. // ///////////////////////////////////// // Every weapon is capable of reading from the bots script table and setting custom properties // By default, every weapon has 'Bias', 'AimError', and 'AimOffset' More custom parameters may // be available to tweak on certain weapons. // To set the properties of a weapon in the script, you must access the weapon table through the bot // like so: // this.Weapons["WEAPON_NAME"].Property = value; // The WEAPON_NAME depends on the weapon, and the Property is the specific property you wish to set // The value depends on the property you are setting. Values can be numbers, 3d vectors, strings, etc... // NOTE: Changed in 0.52 // Weapon tables are no longer created automatically for each bot like it was previously. This saves a bit // of memory for each bot. Because of this, the syntax for setting bot properties has changed. // Properties // Bias - a multiplier used when calculating the bots desire to use a weapon. // Setting to 0.0 will make the bot not use the weapon. // Setting to 0.5 will make the weapon 50% less desirable. // Setting to 2.0 will make the weapon twice as desirable as default. // AimError - a 2d vector value representing the range of random error applied to shots // Setting to Vector2(0.0, 0.0); adds no error to the aim. // Setting to Vector2(0.0, 5.0); adds +- 5 units of error on the z axis(up/down), none on the horizontal axis. // AimOffset - a 3d vector value representing the default offset the weapon should be aimed // Setting to Vector3(0.0, 0.0, 0.0); adds no offset to the aim. // Setting to Vector3(0.0, 0.0, 5.0); adds +- 5 units of offset on the z axis(up/down) // Offset is applied before error. For an example, suppose you wanted a bots sniper weapon // to be aimed lower to reduce headshots, you might set the AimOffset to Vector3(0.0, 0.0, -5.0); // to make them aim 5 units lower. You could also tweak the AimError to widen their random vertical aim //this.Weapons["WEAPON_NAME"] = //{ // Bias = 0.5, // AimError = Vector2(0.0, 6.0), // AimOffset = Vector3(0.0, 0.0,5), //}; // Note the new syntax creates a script table for each weapon. Add additional properties within the brackets of existing // weapons, or create additional tables for weapons not yet defined. /////////////////////////////////// // Register bot item properties. // /////////////////////////////////// // In addition to weapons, some games may support items. Items are usually not weapons. They are // more often items held in the bots inventory and used at various times. // Things such as usable health kits, gas masks, silencers, etc... // Item properties are set the same way as weapon properties, except they use the Items table // like so: // this.Items["ITEM_NAME"].Property = value; /////////////////////////////////// // Register bot goal properties. // /////////////////////////////////// // Goals represent an action the bot can do in the game. Capture the flag, build an MG42, snipe, repair a vehicle, // plant dynamite, disarm dynamite, etc... are all goals. Every bot has a list of goals that it goes through // occasionally to calculate the desirability of. The most desirable goal is the goal the bot will do over all the others. // In order to tweak a bots goal behavior, the user can set properties on the goals, just like weapons and items. // Every goal has at least a 'Bias' property, similar to weapons. It has a similar effect to goals as the // weapon Bias has to weapons. Tweak the number higher or lower to change the desirability of the bot // performing a certain goal. // Goal properties are set the same way as weapon and item properties, except they use the Goals table // like so: // Goal Properties. //this.Goals["DEFEND"] = //{ // Bias = 2.0, //}; //this.Goals["ATTACK"] = //{ // Bias = 2.0, //}; //this.Goals["PLANT_MINE"] = //{ // Bias = 2.0, //}; ////////////////////////////// // Additional Bot Scripting // ////////////////////////////// // In addition to setting properties for a bot for their weapons, items, and goals, // it is also possible to set up 'script callbacks' based on certain events that // happen to the bot. 'script callbacks' are script functions that are called as a // result of some event being recieved by the bot. // Here is a simple example of a function for a script callback. voice_func = function(_params) { // This function will print out the parameters key and value foreach ( i and p in _params ) { print("Parameter # " + i +" : " + p); } if(_params.macro && (_params.macro == VOICE.FOLLOW_ME)) { this.SayVoice(VOICE.NEGATIVE); } }; // And another function for a different callback. spawn_func = function(_params) { print("spawn function"); }; // Once the functions are defined, as seen above, they must then be paired with events. // By assigning the functions to different events on the bots events table, they will be called // when the bot recieves the events. // In this example, the spawn_func will be called when the bot recieves an EVENT.SPAWNED // Also, the voice_func will be called whenever the bot recieves a global, team, or private voice macro message // Care must be taken when doing things like this, if multiple bots have these callbacks defined it can cause // a burst of message spam to occur as the bots continuously respond to each other. this.Events[EVENT.SPAWNED] = spawn_func; //this.Events[EVENT.GLOBAL_VOICE] = voice_func; this.Events[EVENT.TEAM_VOICE] = voice_func; //this.Events[EVENT.PRIVATE_VOICE] = voice_func; //////////////////// // Goal Scripting // //////////////////// // Perhaps the most powerful scripting available for a bot involves completely overriding the bots behavior. // To do this, you must define a goal function for the bot. A goal function is a script that is executed // top to bottom, and acts much like a 'thread' The goal function is capable of blocking execution while the // bot performs the desired action, and resuming script execution later. // Consider the following function. The most important aspects of this function are the calls to // block, yield, and sleep // Every bot in the game is capable of having their own goal function, executing as their own 'thread' // Although they behave much like threads, they aren't real cooperative threads, so it is up to // the thread to give control back to the application and prevent application freezes due to // infinite loops or the script taking too much time to run. // Here is a simple example of a goal function, with extra comments to explain it. goalFunc = function() { // The first thing this function does it block on the spawn event. // when you block the script, you are pausing execution, until the bot recieves the event that is passed to block // In this case, we are telling the script to pause execution until he recieves the EVENT.SPAWNED event // It is probably safest to always block your goal function on the SPAWNED event, to ensure that the bot has // actually made it into the game and is ready to actually move around the world and do stuff. block(EVENT.SPAWNED); // Since we're telling the bot to do something with the goto command below, we need to set it to script controlled. // This will ensure that the bots internal logic doesn't override the script. this.SetScriptControlled(true); // After the bot spawns, the script will begin executing again. // This function call requests a game entity from the game, by an numeric value(array index for programmers) // The index starts at 0, and can go as high as the number of entities the game supports. // In Quake 3 engine games such as ET, the first entities are always players. // This particular example is designed to run in a listen server, where index 0 will always be the human player(you) // This will end up getting the entity for player 0(the host in a listen server) playerEnt = GetGameEntityFromId(0); // Some error checking is always a good idea, to make sure the value that was returned is valid. // If you were to request an entity that doesn't exist, the function would return NULL, so it is best to // always check for errors for functions that can fail. if(playerEnt != null) { // At this point we know that playerEnt is a valid entity, so lets send the bot to the players position. // The GoTo function adds a GoTo goal onto the bots goal list. This goal is responsible for navigating // the bot to the desired position. this.GoTo(GetEntPosition(playerEnt)); } else { // A simple text message if an entity wasn't found, and a return; to exit the function. this.Say("Could't find player 0!!"); return; } // Here is another block message, only this time we want to block while waiting for multiple events. // EVENT.GOAL_SUCCESS and EVENT.GOAL_FAILED are events that are fired as a result of goals // succeeding or failing. // Since we gave the bot the GoTo goal above, the bot will signal the script with // EVENT.GOAL_SUCCESS or EVENT.GOAL_FAILED based on the outcome of the goal. // The goal may fail of the location is unreachable, or the bot gets stuck, etc... // Another important thing to note, and is demonstrated here, is that whichever event is fired, // the value will be returned by the block function, allowing you to compare the result with the // list of events that were blocked on. // In this example, the script blocks and waits for EVENT.GOAL_SUCCESS or EVENT.GOAL_FAILED events. // Whichever one is signalled is then being compared to EVENT.GOAL_SUCCESS in the if-statement if( block(EVENT.GOAL_SUCCESS, EVENT.GOAL_FAILED) == EVENT.GOAL_SUCCESS ) { // If the GoTo goal succeeds, the script continues executing in here. // A simple text message to show the player that the bot made it. this.Say("Wohoo, I made it!."); // And then the script will start into an infinite loop. // NOTE: IT IS VERY IMPORTANT THAT ANY TIME YOU USE LOOPS, ESPECIALLY INFINITE LOOPS // THAT THEY USE yield(), block(), or sleep() OCCASIONALLY TO ALLOW THE GAME TO // CONTINUE PROCESSING. FAILING TO DO THIS CAN FREEZE THE GAME INTO AN INFINITE LOOP // This loop shows some simple behavior. Since we know now that the bot is at the players // former location, this code simply makes the bot look for ally players and turn to face them // when they see them. dowhile(true) { // This calls the GetNearestAlly function on the bot, which searches his SensoryMemory // for the nearest game entity that is in the PLAYER category (CAT stands for CATEGORY) closest = this.GetNearestAlly(CAT.PLAYER, 0); // If there was a valid entity returned... if(closest != null) { // We then get the stored information for this entity. info = this.GetTargetInfo(closest); // Go ahead and start switching weapons as soon as we see them. this.SelectWeapon(WEAPON.KNIFE); // Lets go ahead and charge them too. this.MoveTowards(info.Position); // Once we have this information we can tell the bot to turn towards the entities position if( this.TurnToPosition(info.Position) == true ) { // The TurnToPosition returns true once the bot is aiming within a small tolerance (~3 degrees) // of the target. The result of TurnToPosition is used for example by the weapon system // as an indication of when the bot is aiming enough at the target to begin shooting. // Since weapon switching doesn't happen instantaneously, it's a good idea to make sure // that the current weapon is the desired weapon. If you don't, and blindly start firing // the bot could begin firing the wrong weapon. if( this.GetCurrentWeapon() == WEAPON.KNIFE ) { // The bot is now aiming directly at the player with the knife equipped. // Time for battle cry and attack! //this.Say("Die Mofo!"); this.FireWeapon(); } } } else { // If this bot doesn't see an ally player, the value of closest will be null, // and bring us to this code, which makes the bot say some simple message. this.Say("No Target."); // And then sleep. // sleep pauses script execution much like block() and yield() do, except with sleep // it is based on time in seconds. sleep(1.0); // After the 1 second pause, this continue tells the script to jump back to the top of the loop. // This isn't really required, because it would happen anyways after the yield() below, but // we'll show it just for examples sake. continue; } // yield pauses script execution for 1 frame. The next time the bot 'thinks' the script will be executed. // This yield will only happen if the bot sees an ally player, and turns toward them. By using yield, // we ensure that the bot will turn toward the target a little more every frame. yield(); } } else { // If the GoTo goal fails, the script continues executing in here. this.Say("Sorry, couldn't make it."); // Since we aren't looping or anything after this, the script will just end at this point. // In reality you would probably want to either loop and keep trying, or // return full control back to the bot so he can continue playing the game normally. // Doing nothing like this will result in a bot that just stands around, since // technically he is still set to 'script controlled' } }; // In order to execute the goal function we defined above, we need to create a script thread. // A bot can have any number of script threads running at one time, but care must be taken to // prevent multiple scripts from fighting over bot control. this:thread(goalFunc);