From 1ab65e9e70c80af70e5887d398f9fe3a66472bd7 Mon Sep 17 00:00:00 2001 From: Robert MacGregor Date: Sat, 10 Oct 2015 23:55:09 -0400 Subject: [PATCH] Added weapon pre-profiler code --- scripts/DXAI/aiconnection.cs | 46 +++++++++++- scripts/DXAI/helpers.cs | 5 +- scripts/DXAI/main.cs | 139 +++++++++++++++++++++++++++++++++++ scripts/DXAI/objectives.cs | 6 +- 4 files changed, 187 insertions(+), 9 deletions(-) diff --git a/scripts/DXAI/aiconnection.cs b/scripts/DXAI/aiconnection.cs index e8bf532..9c1f239 100644 --- a/scripts/DXAI/aiconnection.cs +++ b/scripts/DXAI/aiconnection.cs @@ -15,6 +15,7 @@ //------------------------------------------------------------------------------------------ function AIConnection::initialize(%this) { + %this.dangerObjects = new SimSet(); %this.fieldOfView = $DXAI::Bot::DefaultFieldOfView; %this.viewDistance = $DXAI::Bot::DefaultViewDistance; } @@ -197,6 +198,10 @@ function AIConnection::updateLegs(%this) %delta = %now - %this.lastUpdateLegs; %this.lastUpdateLegs = %now; + // Set any danger we may need. + for (%iteration = 0; %iteration < %this.dangerObjects.getCount(); %iteration++) + %this.setDangerLocation(%this.dangerObjects.getObject(%iteration).getPosition(), 3); + if (%this.isMovingToTarget) { if (%this.aimAtLocation) @@ -225,23 +230,52 @@ function AIConnection::updateLegs(%this) //------------------------------------------------------------------------------------------ function AIConnection::updateWeapons(%this) { + %lockedObject = %this.player; + %mount = %this.player.getObjectMount(); + + if (isObject(%mount)) + %lockedObject = %mount; + + // FIXME: Toss %this.player.lockedCount grenades, this will toss all of them basically instantly. + if (%lockedObject.isLocked() && %this.player.invFlareGrenade != 0) + { + %this.pressGrenade(); + } + if (isObject(%this.engageTarget)) { %player = %this.player; %targetDistance = vectorDist(%player.getPosition(), %this.engageTarget.getPosition()); // Firstly, just aim at them for now - %this.aimAt(%this.engageTarget.getWorldBoxCenter()); - + %this.aimAt(%this.engageTarget.getPosition()); + // What is our current best weapon? Right now we just check target distance and weapon spread. %bestWeapon = 0; for (%iteration = 0; %iteration < %player.weaponSlotCount; %iteration++) { + %currentWeapon = %player.weaponSlot[%iteration]; + %currentWeaponImage = %currentWeapon.image; + + // No ammo? + if (isObject(%currentWeaponImage.ammo) && %this.player.inv[%currentWeaponImage.ammo] <= 0) + continue; + + if (%currentWeaponImage.projectileSpread >= 3 && %targetDistance <= 20) + %bestWeapon = %iteration; + else if (%currentWeaponImage.projectileSpread < 3 && %targetDistance >= 20) + %bestWeapon = %iteration; + else if (%targetDistance >= 100 && %currentWeaponImage.projectileType $= "GrenadeProjectile") + %bestWeapon = %iteration; + // Weapons with a decent bit of spread should be used <= 20m + // Arced & precision Weapons should be used at >= 100m + } %player.selectWeaponSlot(%bestWeapon); + %this.pressFire(200); } } @@ -266,7 +300,7 @@ function AIConnection::updateVisualAcuity(%this) // If we can't even see or if we're downright dead, don't do anything. if (%this.visibleDistance = 0 || !isObject(%this.player) || %this.player.getState() !$= "Move") { - %this.visualAcuityTick = %this.schedule(getRandom($DXAI::Bot::MinimumVisualAcuityTime, $DXAI::Bot::MaximumVisualAcuityTime,), "updateVisualAcuity"); + %this.visualAcuityTick = %this.schedule(getRandom($DXAI::Bot::MinimumVisualAcuityTime, $DXAI::Bot::MaximumVisualAcuityTime), "updateVisualAcuity"); return; } @@ -322,7 +356,11 @@ function AIConnection::updateVisualAcuity(%this) %noticeTime = getRandom(700, 1200); if (%this.awarenessTime[%current] < %noticeTime) continue; - + + // Is it a object we want to avoid? + if (AIGrenadeSet.isMember(%current)) + %this.dangerObjects.add(%current); + if (%current.getType() & $TypeMasks::ProjectileObjectType) { %className = %current.getClassName(); diff --git a/scripts/DXAI/helpers.cs b/scripts/DXAI/helpers.cs index b78b990..87221ed 100644 --- a/scripts/DXAI/helpers.cs +++ b/scripts/DXAI/helpers.cs @@ -230,9 +230,10 @@ function GameConnection::getObjectsInViewcone(%this, %typeMask, %distance, %perf %hitObject = getWord(%raycast, 0); - // Since the engine doesn't do raycasts against projectiles correctly, we just check if the bot + // Since the engine doesn't do raycasts against projectiles & items correctly, we just check if the bot // hit -nothing- when doing the raycast rather than checking for a hit against the object - if (%hitObject == %currentObject || (%currentObject.getType() & $TypeMasks::ProjectileObjectType && !isObject(%hitObject))) + %workaroundTypes = $TypeMasks::ProjectileObjectType | $TypeMasks::ItemObjectType; + if (%hitObject == %currentObject || (%currentObject.getType() & %workaroundTypes && !isObject(%hitObject))) %result.add(%currentObject); } } diff --git a/scripts/DXAI/main.cs b/scripts/DXAI/main.cs index 9026eb9..9500bf7 100644 --- a/scripts/DXAI/main.cs +++ b/scripts/DXAI/main.cs @@ -47,6 +47,9 @@ function DXAI::setup(%numTeams) // Set our setup flag so that the execution hooks can behave correctly $DXAI::System::Setup = true; + // Create the AIGrenadeSet to hold known grenades. + new SimSet(AIGrenadeSet); + for (%iteration = 1; %iteration < %numTeams + 1; %iteration++) { %commander = new ScriptObject() { class = "AICommander"; team = %iteration; }; @@ -113,6 +116,131 @@ function DXAI::validateEnvironment() $DXAI::System::InvalidatedEnvironment = false; } +//------------------------------------------------------------------------------------------ +// Description: The weapon profiler loops over the active game datablocks, trying to +// run solely on weapons and precompute useful usage information for the artificial +// intelligence to use during weapon selection & firing. +// Param %printResults: A boolean representing whether or not the results should be +// printed to the console, useful for debugging. +//------------------------------------------------------------------------------------------ +function DXAI::runWeaponProfiler(%printResults) +{ + if (!isObject(DatablockGroup)) + { + error("DXAI: Cannot run weapons profiler, no DatablockGroup exists!"); + return; + } + + for (%iteration = 0; %iteration < DataBlockGroup.getCount(); %iteration++) + { + %currentItem = DataBlockGroup.getObject(%iteration); + + if (%currentItem.getClassName() $= "ItemData") + { + %currentImage = %currentItem.image; + + if (isObject(%currentImage)) + { + %currentProjectile = %currentImage.Projectile; + + %ammoDB = %currentImage.ammo; + %usesAmmo = isObject(%ammoDB); + %usesEnergy = %currentImage.usesEnergy; + %firingEnergy = %currentImage.minEnergy; + %spread = %currentImage.projectileSpread; + + if (%currentImage.isSeeker) + { + %dryEffectiveRange = %currentImage.seekRadius; + %wetEffectiveRange = %currentImage.seekRadius; + %dryAccurateRange = %currentImage.seekRadius; + %wetAccurateRange = %currentImage.seekRadius; + } + else if (isObject(%currentProjectile) && %currentProjectile.getClassName() $= "SniperProjectileData") + { + %dryEffectiveRange = %currentProjectile.maxRifleRange; + %wetEffectiveRange = %currentProjectile.maxRifleRange; + %dryAccurateRange = %currentProjectile.maxRifleRange; + %wetAccurateRange = %currentProjectile.maxRifleRange; + } + else + { + %dryEffectiveRange = (%currentProjectile.lifetimeMS / 1000) * %currentProjectile.dryVelocity; + %wetEffectiveRange = (%currentProjectile.lifetimeMS / 1000) * %currentProjectile.wetVelocity; + %dryAccurateRange = %dryEffectiveRange - (%currentImage.projectileSpread * 8); + %wetAccurateRange = %wetEffectiveRange - (%currentImage.projectileSpread * 8); + } + + // We want to know if this thing fires underwater: We start at the initial state and look for something + // that prohibits underwater usage. + %firesWet = true; + %firesDry = true; + + // First, we map out state names + %stateCount = -1; + %targetState = -1; + while (%currentImage.stateName[%stateCount++] !$= "") + { + %stateMapping[%currentImage.stateName[%stateCount]] = %stateCount; + + if (%currentImage.stateFire[%stateCount]) + %targetStateID = %stateCount; + } + + // Start at the Ready state and go + %currentState = %stateMapping["Ready"]; + + %stateIteration = -1; + while (%stateIteration++ <= 10) + { + if (%currentImage.stateTransitionOnTriggerDown[%currentState] !$= "") + %currentState = %stateMapping[%currentImage.stateTransitionOnTriggerDown[%currentState]]; + else if (%currentImage.stateTransitionOnWet[%currentState] $= "DryFire") + { + %firesWet = false; + + // Check if it fires dry here as well + %firesDry = %currentImage.stateTransitionOnNotWet[%currentState] !$= "DryFire" ? true : false; + break; + } + } + + if (%stateIteration == 10) + error("DXAI: State analysis timed out on " @ %currentItem.getName() @ "!"); + + // Perform the assignments and we're done ... probably + %currentItem.firingEnergy = %firingEnergy; + %currentItem.dryEffectiveRange = %dryEffectiveRange; + %currentItem.wetEffectiveRange = %wetEffectiveRange; + %currentItem.dryAccurateRange = %dryAccurateRange; + %currentItem.wetAccurateRange = %wetAccurateRange; + %currentItem.firesWet = %firesWet; + %currentItem.firesDry = %firesDry; + %currentItem.usesAmmo = %usesAmmo; + %currentItem.usesEnergy = %usesEnergy; + %currentItem.firingEnergy = %firingEnergy; + %currentItem.ammoDB = %ammoDB; + %currentItem.spread = %spread; + + if (%printResults) + { + error(%currentItem.getName()); + error("Dry Range: " @ %dryEffectiveRange); + error("Wet Range: " @ %wetEffectiveRange); + error("Dry Accurate Range: " @ %dryAccurateRange); + error("Wet Accurate Range: " @ %wetAccurateRange); + error("Fires Wet: " @ %firesWet); + error("Fires Dry: " @ %firesDry); + + if (!isObject(%currentProjectile)) + error("*** COULD NOT FIND PROJECTILE ***"); + error("--------------------------------------"); + } + } + } + } +} + //------------------------------------------------------------------------------------------ // 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 @@ -298,6 +426,17 @@ package DXAI_Hooks // Ensure that the DXAI is active. DXAI::validateEnvironment(); } + + //------------------------------------------------------------------------------------------ + // 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); + } // Make this do nothing so the bots don't ever get any objectives by default function DefaultGame::AIChooseGameObjective(%game, %client) { return 11595; } diff --git a/scripts/DXAI/objectives.cs b/scripts/DXAI/objectives.cs index 7580a3f..9db9719 100644 --- a/scripts/DXAI/objectives.cs +++ b/scripts/DXAI/objectives.cs @@ -217,9 +217,9 @@ function AIEnhancedEngageTarget::monitor(%task, %client) return; } - %client.engageTargetLastPosition = %client.engageTarget.getWorldBoxCenter(); - %client.setMoveTarget(getRandomPositionOnTerrain(%client.engageTargetLastPosition, 40)); - %client.pressFire(); + // %client.engageTargetLastPosition = %client.engageTarget.getWorldBoxCenter(); + // %client.setMoveTarget(getRandomPositionOnTerrain(%client.engageTargetLastPosition, 40)); + //%client.pressFire(); } else if (%client.engageTargetLastPosition !$= "") {