2015-06-26 21:02:55 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// main.cs
|
|
|
|
|
// Main source file for the DXAI experimental AI enhancement project.
|
|
|
|
|
// https://github.com/Ragora/T2-DXAI.git
|
|
|
|
|
//
|
2015-10-10 20:08:43 +00:00
|
|
|
// Copyright (c) 2015 Robert MacGregor
|
2015-06-26 21:02:55 +00:00
|
|
|
// This software is licensed under the MIT license. Refer to LICENSE.txt for more information.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
|
2015-06-26 21:02:55 +00:00
|
|
|
exec("scripts/DXAI/objectives.cs");
|
|
|
|
|
exec("scripts/DXAI/helpers.cs");
|
|
|
|
|
exec("scripts/DXAI/config.cs");
|
|
|
|
|
exec("scripts/DXAI/aicommander.cs");
|
|
|
|
|
exec("scripts/DXAI/aiconnection.cs");
|
2015-06-28 00:43:20 +00:00
|
|
|
exec("scripts/DXAI/priorityqueue.cs");
|
|
|
|
|
exec("scripts/DXAI/cyclicset.cs");
|
2015-10-05 09:03:45 +00:00
|
|
|
exec("scripts/DXAI/loadouts.cs");
|
2015-10-11 07:02:19 +00:00
|
|
|
exec("scripts/DXAI/weaponProfiler.cs");
|
2014-11-20 05:12:25 +00:00
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: This cleanup function is called when the mission ends to clean up all
|
|
|
|
|
// active commanders and to delete them for the next mission which guarantees a blank
|
|
|
|
|
// slate.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DXAI::cleanup()
|
|
|
|
|
{
|
|
|
|
|
$DXAI::System::Setup = false;
|
|
|
|
|
|
|
|
|
|
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
|
|
|
|
$DXAI::ActiveCommander[%iteration].delete();
|
|
|
|
|
|
|
|
|
|
$DXAI::ActiveCommanderCount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: This cleanup function is called when the mission starts to to instantiate
|
|
|
|
|
// and setup all AI commanders for the game.
|
|
|
|
|
// Param %numTeams: The number of teams to initialize for.
|
|
|
|
|
//
|
|
|
|
|
// TODO: Perhaps calculate %numTeams from the game object?
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DXAI::setup(%numTeams)
|
|
|
|
|
{
|
|
|
|
|
// Mark the environment as invalidated for each new run so that our hooks
|
|
|
|
|
// can be verified
|
|
|
|
|
$DXAI::System::InvalidatedEnvironment = true;
|
|
|
|
|
|
|
|
|
|
// Set our setup flag so that the execution hooks can behave correctly
|
|
|
|
|
$DXAI::System::Setup = true;
|
|
|
|
|
|
2015-10-11 03:55:09 +00:00
|
|
|
// Create the AIGrenadeSet to hold known grenades.
|
|
|
|
|
new SimSet(AIGrenadeSet);
|
|
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
for (%iteration = 1; %iteration < %numTeams + 1; %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%commander = new ScriptObject() { class = "AICommander"; team = %iteration; };
|
|
|
|
|
%commander.setup();
|
|
|
|
|
|
|
|
|
|
$DXAI::ActiveCommander[%iteration] = %commander;
|
2015-06-28 02:34:06 +00:00
|
|
|
%commander.loadObjectives();
|
|
|
|
|
%commander.assignTasks();
|
2014-11-20 05:12:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And setup the default values
|
|
|
|
|
for (%iteration = 0; %iteration < ClientGroup.getCount(); %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%currentClient = ClientGroup.getObject(%iteration);
|
|
|
|
|
|
|
|
|
|
%currentClient.viewDistance = $DXAI::Bot::DefaultViewDistance;
|
|
|
|
|
%currentClient.fieldOfView = $DXAI::Bot::DefaultFieldOfView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$DXAI::ActiveCommanderCount = %numTeams;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Why: Due to the way the AI system must hook into some functions and the way game
|
|
|
|
|
// modes work, we must generate runtime overrides for some gamemode related functions. We
|
|
|
|
|
// can't simply hook DefaultGame functions base game modes will declare their own and so
|
|
|
|
|
// we'll need to hook those functions post-start as the game mode scripts are executed for
|
|
|
|
|
// each mission run.
|
|
|
|
|
// Description: This function is called once per update tick (roughly 32 milliseconds) to
|
|
|
|
|
// check that the hooks we need are actually active if the system detects that may be a
|
|
|
|
|
// necessity to do so. A runtime check is initiated at gamemode start and for each exec
|
|
|
|
|
// call made during runtime as any given exec can overwrite the hooks we required.
|
|
|
|
|
// If they were not overwritten, the function will return 11595 and do nothing else if the
|
|
|
|
|
// appropriate dummy parameters are passed in.
|
|
|
|
|
//
|
|
|
|
|
// TODO: Perhaps calculate %numTeams from the game object?
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DXAI::validateEnvironment()
|
|
|
|
|
{
|
|
|
|
|
%gameModeName = $CurrentMissionType @ "Game";
|
|
|
|
|
|
|
|
|
|
%payloadTemplate = %payload = "function " @ %gameModeName @ "::<METHODNAME>() { return DefaultGame::<METHODNAME>($DXAI::System::RuntimeDummy); } ";
|
|
|
|
|
if (game.AIChooseGameObjective($DXAI::System::RuntimeDummy) != 11595)
|
|
|
|
|
{
|
|
|
|
|
error("DXAI: Function 'DefaultGame::AIChooseGameObjective' detected to be overwritten by the current gamemode. Correcting ...");
|
|
|
|
|
|
2015-06-25 01:03:57 +00:00
|
|
|
eval(strReplace(%payloadTemplate, "<METHODNAME>", "AIChooseGameObjective"));
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
// Make sure the patch took
|
|
|
|
|
if (game.AIChooseGameObjective($DXAI::System::RuntimeDummy) != 11595)
|
|
|
|
|
error("DXAI: Failed to patch 'DefaultGame::AIChooseGameObjective'! DXAI may not function correctly.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (game.onAIRespawn($DXAI::System::RuntimeDummy) != 11595)
|
|
|
|
|
{
|
|
|
|
|
error("DXAI: Function 'DefaultGame::onAIRespawn' detected to be overwritten by the current gamemode. Correcting ... ");
|
|
|
|
|
|
2015-06-25 01:03:57 +00:00
|
|
|
eval(strReplace(%payloadTemplate, "<METHODNAME>", "onAIRespawn"));
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
if (game.onAIRespawn($DXAI::System::RuntimeDummy) != 11595)
|
|
|
|
|
error("DXAI: Failed to patch 'DefaultGame::onAIRespawn'! DXAI may not function correctly.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$DXAI::System::InvalidatedEnvironment = false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: This update function is scheduled to be called roughly once every 32
|
|
|
|
|
// milliseconds which updates each active commander in the game as well as performs
|
|
|
|
|
// an environment validation if necessary.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: This is called on its own scheduled tick, therefore it should not be called
|
|
|
|
|
// directly.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DXAI::update()
|
|
|
|
|
{
|
|
|
|
|
if (isEventPending($DXAI::updateHandle))
|
|
|
|
|
cancel($DXAI::updateHandle);
|
|
|
|
|
|
2015-06-28 00:43:20 +00:00
|
|
|
if (!isObject(Game))
|
|
|
|
|
return;
|
|
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// Check if the bound functions are overwritten by the current gamemode, or if something
|
|
|
|
|
// may have invalidated our hooks
|
|
|
|
|
if ($DXAI::System::InvalidatedEnvironment && $DXAI::System::Setup)
|
|
|
|
|
DXAI::validateEnvironment();
|
|
|
|
|
|
|
|
|
|
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
|
|
|
|
$DXAI::ActiveCommander[%iteration].update();
|
|
|
|
|
|
|
|
|
|
// Apparently we can't schedule a bound function otherwise
|
2015-06-28 02:34:06 +00:00
|
|
|
$DXAI::updateHandle = schedule(32, 0, "eval", "DXAI::update();");
|
2014-11-20 05:12:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function DXAI::notifyPlayerDeath(%killed, %killedBy)
|
|
|
|
|
{
|
|
|
|
|
for (%iteration = 1; %iteration < $DXAI::ActiveCommanderCount + 1; %iteration++)
|
|
|
|
|
$DXAI::ActiveCommander[%iteration].notifyPlayerDeath(%killed, %killedBy);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: There is a series of functions that the AI code can safely hook without
|
|
|
|
|
// worry of being overwritten implicitly such as the disconnect or exec functions. For
|
|
|
|
|
// those that can be, there is an environment validation that is performed to ensure that
|
|
|
|
|
// the necessary code will be called in response to the events we need to know about in
|
|
|
|
|
// this AI system.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
package DXAI_Hooks
|
|
|
|
|
{
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Called when the mission ends. We use this to perform any necessary cleanup
|
|
|
|
|
// operations between games.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DefaultGame::gameOver(%game)
|
|
|
|
|
{
|
|
|
|
|
parent::gameOver(%game);
|
|
|
|
|
|
|
|
|
|
DXAI::cleanup();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: Called when the mission starts. We use this to perform initialization and
|
|
|
|
|
// to start the update ticks.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DefaultGame::startMatch(%game)
|
|
|
|
|
{
|
|
|
|
|
parent::startMatch(%game);
|
|
|
|
|
|
|
|
|
|
DXAI::setup(%game.numTeams);
|
|
|
|
|
DXAI::update();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: We hook the disconnect function as a step to fix console spam from leaving
|
|
|
|
|
// a listen server due to the AI code continuing to run post-server shutdown in those
|
|
|
|
|
// cases.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2015-06-25 01:03:57 +00:00
|
|
|
function disconnect()
|
|
|
|
|
{
|
|
|
|
|
parent::disconnect();
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
DXAI::cleanup();
|
2015-06-25 01:03:57 +00:00
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: In the game, bots can be made to change teams which means we need to hook
|
|
|
|
|
// this event so that commander affiliations can be properly updated.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function DefaultGame::AIChangeTeam(%game, %client, %newTeam)
|
|
|
|
|
{
|
|
|
|
|
// Remove us from the old commander's control first
|
|
|
|
|
$DXAI::ActiveCommander[%client.team].removeBot(%client);
|
|
|
|
|
|
|
|
|
|
parent::AIChangeTeam(%game, %client, %newTeam);
|
|
|
|
|
|
|
|
|
|
$DXAI::ActiveCommander[%newTeam].addBot(%client);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: In the game, bots can be kicked like regular players so we hook this to
|
|
|
|
|
// ensure that commanders are properly notified of lesser bot counts.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function AIConnection::onAIDrop(%client)
|
|
|
|
|
{
|
|
|
|
|
if (isObject(%client.commander))
|
|
|
|
|
%client.commander.removeBot(%client);
|
|
|
|
|
|
|
|
|
|
parent::onAIDrop(%client);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hooks for AI System notification
|
|
|
|
|
function DefaultGame::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLocation)
|
|
|
|
|
{
|
|
|
|
|
parent::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLocation);
|
|
|
|
|
|
|
|
|
|
DXAI::notifyPlayerDeath(%clVictim, %clKiller);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function DefaultGame::onAIKilled(%game, %clVictim, %clKiller, %damageType, %implement)
|
|
|
|
|
{
|
|
|
|
|
parent::onAIKilled(%game, %clVictim, %clKiller, %damageType, %implement);
|
|
|
|
|
|
|
|
|
|
DXAI::notifyPlayerDeath(%clVictim, %clKiller);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: We hook this function to implement some basic sound simulation for bots.
|
|
|
|
|
// This means that if something explodes, a bot will hear it and if the sound is close
|
|
|
|
|
// enough, they will shimmy away from the source using setDangerLocation.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2014-11-20 05:12:25 +00:00
|
|
|
function ProjectileData::onExplode(%data, %proj, %pos, %mod)
|
|
|
|
|
{
|
|
|
|
|
parent::onExplode(%data, %proj, %pos, %mod);
|
|
|
|
|
|
|
|
|
|
// Look for any bots nearby
|
2015-06-25 01:03:57 +00:00
|
|
|
InitContainerRadiusSearch(%pos, 100, $TypeMasks::PlayerObjectType);
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
while ((%targetObject = containerSearchNext()) != 0)
|
|
|
|
|
{
|
|
|
|
|
%currentDistance = containerSearchCurrRadDamageDist();
|
|
|
|
|
|
2015-06-25 01:03:57 +00:00
|
|
|
if (%currentDistance > 100 || !%targetObject.client.isAIControlled())
|
2014-11-20 05:12:25 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Get the projectile team
|
|
|
|
|
%projectileTeam = -1;
|
|
|
|
|
if (isObject(%proj.sourceObject))
|
|
|
|
|
%projectileTeam = %proj.sourceObject.client.team;
|
|
|
|
|
|
|
|
|
|
// Determine if we should run based on team & Team damage
|
|
|
|
|
%shouldRun = false;
|
|
|
|
|
if (isObject(%proj.sourceObject) && %projectileTeam == %targetObject.client.team && $TeamDamage)
|
|
|
|
|
%shouldRun = true;
|
|
|
|
|
else if (isObject(%proj.sourceObject) && %projectileTeam != %targetObject.client.team)
|
|
|
|
|
%shouldRun = true;
|
|
|
|
|
|
|
|
|
|
// Determine if we 'heard' it. The sound distance seems to be roughly 55m or less and we check the maxDistance
|
|
|
|
|
// IIRC The 55m distance should scale with the min/max distances and volume but I'm not sure how those interact
|
|
|
|
|
%heardHit = false;
|
|
|
|
|
%hitDistance = vectorDist(%targetObject.getWorldBoxCenter(), %pos);
|
|
|
|
|
|
2015-06-25 01:03:57 +00:00
|
|
|
if (%hitDistance <= 20 && %hitDistance <= %data.explosion.soundProfile.description.maxDistance)
|
2014-11-20 05:12:25 +00:00
|
|
|
%heardHit = true;
|
|
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
// If the thing has any radius damage (and we heard it), run around a little bit if we need to, and look at it for a bit
|
2015-06-25 01:03:57 +00:00
|
|
|
if (%data.indirectDamage != 0 && %heardHit)
|
2015-10-05 09:03:45 +00:00
|
|
|
{
|
2015-06-25 01:03:57 +00:00
|
|
|
%targetObject.client.schedule(getRandom(250, 400), "setDangerLocation", %pos, 20);
|
2015-10-05 09:03:45 +00:00
|
|
|
// TODO: Perhaps attempt to discern the direction of fire?
|
|
|
|
|
%targetObject.client.aimAt(%pos);
|
|
|
|
|
}
|
2014-11-20 05:12:25 +00:00
|
|
|
|
|
|
|
|
// If we should care and it wasn't a teammate projectile, notify
|
|
|
|
|
if (%shouldRun && %projectileTeam != %targetObject.client.team)
|
|
|
|
|
%targetObject.client.notifyProjectileImpact(%data, %proj, %pos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-10 20:08:43 +00:00
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: This function is hooked so that we can try and guarantee that the DXAI
|
|
|
|
|
// gamemode hooks still exist in the runtime as game mode scripts are executed for each
|
|
|
|
|
// mission load.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
2015-06-26 17:31:55 +00:00
|
|
|
function CreateServer(%mission, %missionType)
|
|
|
|
|
{
|
|
|
|
|
// Perform the default exec's
|
|
|
|
|
parent::CreateServer(%mission, %missionType);
|
|
|
|
|
|
|
|
|
|
// Ensure that the DXAI is active.
|
2015-06-26 21:02:55 +00:00
|
|
|
DXAI::validateEnvironment();
|
2015-10-11 07:02:19 +00:00
|
|
|
|
|
|
|
|
// Run our profiler here as well.
|
|
|
|
|
WeaponProfiler::run(false);
|
2015-06-26 17:31:55 +00:00
|
|
|
}
|
2015-10-11 03:55:09 +00:00
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
// Description: The AIGrenadeThrown function is called to notify the AI code that a
|
|
|
|
|
// grenade has been added to the game sim which is how bots will evade any grenade that
|
|
|
|
|
// is merely within range of them. However, this is not the behavior we want. We want the
|
|
|
|
|
// bots to actually see the grenade before responding to it.
|
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
|
function AIGrenadeThrown(%projectile)
|
|
|
|
|
{
|
|
|
|
|
AIGrenadeSet.add(%projectile);
|
|
|
|
|
}
|
2015-06-26 17:31:55 +00:00
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
// Make this do nothing so the bots don't ever get any objectives by default
|
|
|
|
|
function DefaultGame::AIChooseGameObjective(%game, %client) { return 11595; }
|
|
|
|
|
|
|
|
|
|
function DefaultGame::onAIRespawn(%game, %client)
|
|
|
|
|
{
|
|
|
|
|
// Make sure the bot has no objectives
|
2015-06-26 17:18:13 +00:00
|
|
|
// %client.reset();
|
|
|
|
|
// %client.defaultTasksAdded = true;
|
2015-10-05 09:03:45 +00:00
|
|
|
%client.shouldRearm = true;
|
|
|
|
|
%client.targetLoadout = 1;
|
2014-11-20 05:12:25 +00:00
|
|
|
|
2015-10-07 07:16:32 +00:00
|
|
|
%client.engageTargetLastPosition = "";
|
|
|
|
|
%client.engageTarget = -1;
|
|
|
|
|
|
2014-11-20 05:12:25 +00:00
|
|
|
return 11595;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We package hook the exec() and compile() functions to perform execution environment
|
|
|
|
|
// checking because these can easily load code that overwrites methods that are otherwise
|
|
|
|
|
// hooked by DXAI. This can happen with gamemode specific events because DXAI only hooks into
|
|
|
|
|
// DefaultGame. This is mostly helpful for developers.
|
|
|
|
|
function exec(%file)
|
|
|
|
|
{
|
|
|
|
|
$DXAI::System::InvalidatedEnvironment = true;
|
|
|
|
|
parent::exec(%file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compile(%file)
|
|
|
|
|
{
|
|
|
|
|
$DXAI::System::InvalidatedEnvironment = true;
|
|
|
|
|
parent::compile(%file);
|
|
|
|
|
}
|
2015-10-05 09:03:45 +00:00
|
|
|
|
|
|
|
|
function AIRespondToEvent(%client, %eventTag, %targetClient)
|
|
|
|
|
{
|
|
|
|
|
%clientPos = %client.player.getWorldBoxCenter();
|
|
|
|
|
//switch$ (%eventTag)
|
|
|
|
|
//{
|
|
|
|
|
schedule(250, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, "cmd.decline", $AIAnimSalute, $AIAnimSalute, 0);
|
2015-10-07 07:16:32 +00:00
|
|
|
schedule(2000, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, ObjectiveNameToVoice(%targetClient), $AIAnimSalute, $AIAnimSalute, 0);
|
2015-10-05 09:03:45 +00:00
|
|
|
schedule(3700, %targetClient, "AIPlayAnimSound", %targetClient, %clientPos, "vqk.sorry", $AIAnimSalute, $AIAnimSalute, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-07 07:16:32 +00:00
|
|
|
function AISystemEnabled(%enabled)
|
|
|
|
|
{
|
|
|
|
|
parent::AISystemEnabled(%enabled);
|
|
|
|
|
|
|
|
|
|
echo(%enabled);
|
|
|
|
|
$DXAI::AISystemEnabled = %enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function AIConnection::onAIDrop(%client)
|
|
|
|
|
{
|
|
|
|
|
parent::onAIDrop(%client);
|
|
|
|
|
|
|
|
|
|
if (isObject(%client.visibleHostiles))
|
|
|
|
|
%client.visibleHostiles.delete();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 09:03:45 +00:00
|
|
|
function Station::stationTriggered(%data, %obj, %isTriggered)
|
|
|
|
|
{
|
|
|
|
|
parent::stationTriggered(%data, %obj, %isTriggered);
|
|
|
|
|
|
|
|
|
|
// TODO: If the bot isn't supposed to be on the station, at least restock ammunition?
|
2015-10-11 07:02:19 +00:00
|
|
|
// FIXME: Can bots trigger dead stations?
|
2015-10-05 09:03:45 +00:00
|
|
|
if (%isTriggered && %obj.triggeredBy.client.isAIControlled() && %obj.triggeredBy.client.shouldRearm)
|
|
|
|
|
{
|
|
|
|
|
%bot = %obj.triggeredBy.client;
|
|
|
|
|
|
|
|
|
|
%bot.shouldRearm = false;
|
|
|
|
|
%bot.player.clearInventory();
|
|
|
|
|
|
|
|
|
|
%bot.player.setArmor($DXAI::Loadouts[%bot.targetLoadout, "Armor"]);
|
|
|
|
|
%bot.player.setInventory($DXAI::Loadouts[%bot.targetLoadout, "Pack"], 1, true);
|
|
|
|
|
|
|
|
|
|
for (%iteration = 0; %iteration < $DXAI::Loadouts[%bot.targetLoadout, "WeaponCount"]; %iteration++)
|
|
|
|
|
{
|
|
|
|
|
%bot.player.setInventory($DXAI::Loadouts[%bot.targetLoadout, "Weapon", %iteration], 1, true);
|
|
|
|
|
%bot.player.setInventory($DXAI::Loadouts[%bot.targetLoadout, "Weapon", %iteration].Image.Ammo, 999, true); // TODO: Make it actually top out correctly!
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-20 05:12:25 +00:00
|
|
|
};
|
|
|
|
|
|
2015-06-26 21:02:55 +00:00
|
|
|
// Only activate the package if it isn't already active.
|
2014-11-20 05:12:25 +00:00
|
|
|
if (!isActivePackage(DXAI_Hooks))
|
|
|
|
|
activatePackage(DXAI_Hooks);
|