diff --git a/scripts/DXAI/aiconnection.cs b/scripts/DXAI/aiconnection.cs index 9c1f239..d3ba74b 100644 --- a/scripts/DXAI/aiconnection.cs +++ b/scripts/DXAI/aiconnection.cs @@ -259,15 +259,15 @@ function AIConnection::updateWeapons(%this) %currentWeaponImage = %currentWeapon.image; // No ammo? - if (isObject(%currentWeaponImage.ammo) && %this.player.inv[%currentWeaponImage.ammo] <= 0) + if (%currentWeapon.usesAmmo && %this.player.inv[%currentWeapon.ammoDB] <= 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") + if (%targetDistance <= %currentWeapon.dryEffectiveRange) %bestWeapon = %iteration; + // else if (%currentWeapon.spread < 3 && %targetDistance >= 20) + // %bestWeapon = %iteration; + // else if (%targetDistance >= 100 && %currentWeapon.projectileType $= "GrenadeProjectile") + // %bestWeapon = %iteration; // Weapons with a decent bit of spread should be used <= 20m // Arced & precision Weapons should be used at >= 100m diff --git a/scripts/DXAI/main.cs b/scripts/DXAI/main.cs index 9500bf7..c09018b 100644 --- a/scripts/DXAI/main.cs +++ b/scripts/DXAI/main.cs @@ -15,6 +15,7 @@ exec("scripts/DXAI/aiconnection.cs"); exec("scripts/DXAI/priorityqueue.cs"); exec("scripts/DXAI/cyclicset.cs"); exec("scripts/DXAI/loadouts.cs"); +exec("scripts/DXAI/weaponProfiler.cs"); //------------------------------------------------------------------------------------------ // Description: This cleanup function is called when the mission ends to clean up all @@ -116,131 +117,6 @@ 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 @@ -425,6 +301,9 @@ package DXAI_Hooks // Ensure that the DXAI is active. DXAI::validateEnvironment(); + + // Run our profiler here as well. + WeaponProfiler::run(false); } //------------------------------------------------------------------------------------------ @@ -502,6 +381,7 @@ package DXAI_Hooks parent::stationTriggered(%data, %obj, %isTriggered); // TODO: If the bot isn't supposed to be on the station, at least restock ammunition? + // FIXME: Can bots trigger dead stations? if (%isTriggered && %obj.triggeredBy.client.isAIControlled() && %obj.triggeredBy.client.shouldRearm) { %bot = %obj.triggeredBy.client; diff --git a/scripts/DXAI/weaponProfiler.cs b/scripts/DXAI/weaponProfiler.cs new file mode 100644 index 0000000..ae565e5 --- /dev/null +++ b/scripts/DXAI/weaponProfiler.cs @@ -0,0 +1,204 @@ +//------------------------------------------------------------------------------------------ +// weaponProfiler.cs +// Source file dedicated to the weapon profiling system. +// https://github.com/Ragora/T2-DXAI.git +// +// Copyright (c) 2015 Robert MacGregor +// This software is licensed under the MIT license. +// Refer to LICENSE.txt for more information. +//------------------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------------------ +// 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 WeaponProfiler::run(%printResults) +{ + if (!isObject(DatablockGroup)) + { + error("DXAI: Cannot run weapons profiler, no DatablockGroup exists!"); + return; + } + + // Perform an initial scan for player, turret and vehicle datablocks first + %armorTypes = new SimSet(); + %vehicleTypes = new SimSet(); + %turretTypes = new SimSet(); + for (%iteration = 0; %iteration < DataBlockGroup.getCount(); %iteration++) + { + %currentDB = DataBlockGroup.getObject(%iteration); + + %classType = %currentDB.getClassName(); + if (%classType $= "PlayerData") + %armorTypes.add(%currentDB); + else if (%classType $= "TurretData") + %turretTypes.add(%currentDB); + else if (%classType $= "HoverVehicleData" || %classType $= "FlyingVehicleData" || %classType $= "WheeledVehicleData") + %vehicleTypes.add(%currentDB); + } + + // Now we run the actual calculations + 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); + } + + WeaponProfiler::_processImageStates(%currentItem); + + // Perform the assignments and we're done ... probably + %currentItem.firingEnergy = %firingEnergy; + %currentItem.dryEffectiveRange = %dryEffectiveRange; + %currentItem.wetEffectiveRange = %wetEffectiveRange; + %currentItem.dryAccurateRange = %dryAccurateRange; + %currentItem.wetAccurateRange = %wetAccurateRange; + %currentItem.usesAmmo = %usesAmmo; + %currentItem.usesEnergy = %usesEnergy; + %currentItem.firingEnergy = %firingEnergy; + %currentItem.ammoDB = %ammoDB; + %currentItem.spread = %spread; + + WeaponProfiler::_processArmorEffectiveness(%currentItem, %armorTypes); + WeaponProfiler::_processVehicleEffectiveness(%currentItem, %vehicleTypes); + WeaponProfiler::_processTurretEffectiveness(%currentItem, %turretTypes); + + 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: " @ %currentItem.firesWet); + error("Fires Dry: " @ %currentItem.firesDry); + + if (!isObject(%currentProjectile)) + { + error("*** COULD NOT FIND PROJECTILE ***"); + + %currentItem.dryEffectiveRange = 300; + %currentItem.wetEffectiveRange = 300; + %currentItem.dryAccurateRange = 300; + %currentItem.wetAccurateRange = 300; + } + error("--------------------------------------"); + } + } + } + } + + %armorTypes.delete(); + %turretTypes.delete(); + %vehicleTypes.delete(); +} + +function WeaponProfiler::_processArmorEffectiveness(%itemDB, %armorTypes) +{ + %projectileType = %itemDB.image.Projectile; + + if (!isObject(%projectileType)) + return; + + for (%iteration = 0; %iteration < %armorTypes.getCount(); %iteration++) + { + %currentArmor = %armorTypes.getObject(%iteration); + + + } +} + +function WeaponProfiler::_processTurretEffectiveness(%itemDB, %turretTypes) +{ + %projectileType = %itemDB.image.Projectile; + + if (!isObject(%projectileType)) + return; +} + +function WeaponProfiler::_processVehicleEffectiveness(%itemDB, %vehicleTypes) +{ + %projectileType = %itemDB.image.Projectile; + + if (!isObject(%projectileType)) + return; +} + +function WeaponProfiler::_processImageStates(%itemDB) +{ + // 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; + } + } + + %itemDB.firesWet = %firesWet; + %itemDB.firesDry = %firesDry; + + if (%stateIteration == 10) + error("DXAI: State analysis timed out on " @ %currentItem.getName() @ "!"); +} \ No newline at end of file