From f7d2b77e34d4358b315a2b192ec944d6331eaa3b Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Sat, 14 May 2022 21:12:11 -0700 Subject: [PATCH 1/9] projectile dodge --- LuaRules/Configs/customcmds.lua | 2 + LuaRules/Configs/projectile_dodge_defs.lua | 63 ++++ LuaRules/Configs/state_commands.lua | 2 + LuaRules/Gadgets/cmd_raw_move.lua | 66 ++++ LuaRules/Gadgets/map_obsticles.lua | 115 +++++++ LuaRules/Gadgets/unit_dodge.lua | 369 +++++++++++++++++++++ LuaRules/Gadgets/unit_tactical_ai.lua | 267 +++++++++------ LuaRules/Utilities/projectile_targets.lua | 75 +++++ LuaRules/Utilities/quad_tree.lua | 119 +++++++ LuaRules/Utilities/rect.lua | 29 ++ LuaRules/Utilities/vector.lua | 45 ++- LuaUI/Configs/customCmdTypes.lua | 4 + LuaUI/Configs/integral_menu_commands.lua | 2 + LuaUI/Configs/integral_menu_config.lua | 20 +- LuaUI/Configs/integral_menu_culling.lua | 4 + LuaUI/Configs/stateTypes.lua | 2 + LuaUI/Widgets/unit_transport_ai.lua | 2 + 17 files changed, 1085 insertions(+), 101 deletions(-) create mode 100644 LuaRules/Configs/projectile_dodge_defs.lua create mode 100644 LuaRules/Gadgets/map_obsticles.lua create mode 100644 LuaRules/Gadgets/unit_dodge.lua create mode 100644 LuaRules/Utilities/projectile_targets.lua create mode 100644 LuaRules/Utilities/quad_tree.lua create mode 100644 LuaRules/Utilities/rect.lua diff --git a/LuaRules/Configs/customcmds.lua b/LuaRules/Configs/customcmds.lua index adb7f07029..20646fa3f1 100644 --- a/LuaRules/Configs/customcmds.lua +++ b/LuaRules/Configs/customcmds.lua @@ -93,6 +93,8 @@ return { TURN = 38530, WANTED_SPEED = 38825, AIR_STRAFE = 39381, + IDLE_DODGE = 39382, + MOVE_DODGE = 39383, -- terraform RAMP = 39734, diff --git a/LuaRules/Configs/projectile_dodge_defs.lua b/LuaRules/Configs/projectile_dodge_defs.lua new file mode 100644 index 0000000000..55190b1c53 --- /dev/null +++ b/LuaRules/Configs/projectile_dodge_defs.lua @@ -0,0 +1,63 @@ +local TRACKED_WEAPONS = { + amphfloater_cannon = {}, + amphsupport_cannon = {}, + bomberheavy_arm_pidr = {}, + bomberprec_bombsabot = {}, + bomberriot_napalm = {}, + cloakarty_hammer_weapon = {}, + cloakskirm_bot_rocket = { + dynamic = true, + }, + empmissile_emp_weapon = {}, + gunshipassault_vtol_salvo = {}, + gunshipheavyskirm_emg = {}, + hoverarty_ata = {}, + jumparty_napalm_sprayer = {}, + jumpblackhole_black_hole = {}, + napalmmissile_weapon = {}, + seismic_seismic_weapon = {}, + shieldassault_thud_weapon = {}, + shieldskirm_storm_rocket = { + dynamic = true, + }, + shiparty_plasma = {}, + shipskirm_rocket = {}, + spiderassault_thud_weapon = {}, + spidercrabe_arm_crabe_gauss = {}, + spiderskirm_adv_rocket = {}, + staticarty_plasma = {}, + staticheavyarty_plasma = {}, + striderarty_rocket = {}, + tacnuke_weapon = { + dynamic = true, + }, + tankarty_core_artillery = {}, + tankassault_cor_reap = {}, + tankheavyarty_plasma = {}, + tankheavyassault_cor_gol = {}, + turretantiheavy_ata = {}, + turretgauss_gauss = {}, + turretheavy_plasma = {}, + turretheavylaser_laser = {}, + vehassault_plasma = {}, + vehheavyarty_cortruck_rocket = { + dynamic = true, + }, +} + +local config = {} +for projName, customData in pairs(TRACKED_WEAPONS) do + local wDef = WeaponDefNames[projName] + local wData = { + wType = wDef.type, + tracks = wDef.tracks, + maxVelocity = wDef.maxVelocity, + aoe = math.max(20, (wDef.impactOnly and 0) or wDef.damageAreaOfEffect), + dynamic = wDef.wobble ~= 0 or wDef.dance ~= 0 or wDef.tracks, + } + for key, data in pairs(customData) do + wData[key] = data + end + config[wDef.id] = wData +end +return config \ No newline at end of file diff --git a/LuaRules/Configs/state_commands.lua b/LuaRules/Configs/state_commands.lua index 0bf0648fd9..e45b70883d 100644 --- a/LuaRules/Configs/state_commands.lua +++ b/LuaRules/Configs/state_commands.lua @@ -39,6 +39,8 @@ local stateCommands = { [CMD_TOGGLE_DRONES] = true, [CMD_AUTO_CALL_TRANSPORT] = true, [CMD_SELECTION_RANK] = true, + [CMD_IDLE_DODGE] = true, + [CMD_MOVE_DODGE] = true, } return stateCommands diff --git a/LuaRules/Gadgets/cmd_raw_move.lua b/LuaRules/Gadgets/cmd_raw_move.lua index b13ef4210b..c7aca95836 100644 --- a/LuaRules/Gadgets/cmd_raw_move.lua +++ b/LuaRules/Gadgets/cmd_raw_move.lua @@ -160,6 +160,8 @@ local COMMON_STOP_RADIUS_ACTIVE_DIST_SQ = 120^2 -- Commands shorter than this do local CONSTRUCTOR_UPDATE_RATE = 30 local CONSTRUCTOR_TIMEOUT_RATE = 2 +local DODGE_UPDATE_RATE = 30 + local STOPPING_HAX = not Spring.Utilities.IsCurrentVersionNewerThan(104, 271) ---------------------------------------------------------------------------------------------- @@ -183,6 +185,10 @@ local constructorsPerFrame = 0 local constructorIndex = 1 local alreadyResetConstructors = false +local dodgeMoveUnit = {} +local dodgeMoveUnitByID = {} +local dodgeMoveUnitCount = 0 + local checkEngineMove local moveCommandReplacementUnits local fastConstructorUpdate @@ -258,11 +264,62 @@ local function ResetUnitData(unitData) unitData.possiblyTurning = nil end +---------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------- +-- Dodge Move Handling + +local function RemoveProjectileDodge(unitID) + if unitID and dodgeMoveUnitByID[unitID] then + local index = dodgeMoveUnitByID[unitID] + dodgeMoveUnit[index] = dodgeMoveUnit[dodgeMoveUnitCount] + dodgeMoveUnitByID[dodgeMoveUnit[dodgeMoveUnitCount]] = index + dodgeMoveUnitByID[unitID] = nil + dodgeMoveUnit[dodgeMoveUnitCount] = nil + dodgeMoveUnitCount = dodgeMoveUnitCount - 1 + end +end + +local function UnitProjectileDodge(unitID) + if rawMoveUnit[unitID] then + local unitData = rawMoveUnit[unitID] + local dodgeX, dodgeZ, dodgeSize = GG.UnitDodge.Move(unitID, unitData.mx, unitData.mz) + if dodgeSize > 5 then + local cmdID, _, cmdTag, _, _, _, isDodge = spGetUnitCurrentCommand(unitID) + if (cmdID == CMD_MOVE or cmdID == CMD_RAW_MOVE) and isDodge == -1 then + spGiveOrderToUnit(unitID, CMD_REMOVE, cmdTag, 0) + end + local dodgeY = Spring.GetGroundHeight(dodgeX, dodgeZ) + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, 0, dodgeX, dodgeY, dodgeZ, -1}, CMD_OPT_ALT) + end + else + RemoveProjectileDodge(unitID) + end +end + +local function AddProjectileDodge(unitID) + if unitID and dodgeMoveUnitByID[unitID] == nil then + dodgeMoveUnitCount = dodgeMoveUnitCount + 1 + dodgeMoveUnit[dodgeMoveUnitCount] = unitID + dodgeMoveUnitByID[unitID] = dodgeMoveUnitCount + UnitProjectileDodge(unitID) + end +end + +local function UpdateProjectileDodge(frame) + if frame % DODGE_UPDATE_RATE == 0 then + for i = dodgeMoveUnitCount, 1, -1 do + UnitProjectileDodge(dodgeMoveUnit[i]) + end + end +end + ---------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------- -- Raw Move Handling local function StopRawMoveUnit(unitID, stopNonRaw) + RemoveProjectileDodge(unitID) + if not rawMoveUnit[unitID] then return end @@ -296,6 +353,10 @@ local function HandleRawMove(unitID, unitDefID, cmdParams) end local goalDistOverride = cmdParams[4] + if cmdParams[4] == -1 then + goalDistOverride = nil + end + local timerIncrement = cmdParams[5] or 1 if not rawMoveUnit[unitID] then rawMoveUnit[unitID] = {} @@ -459,6 +520,9 @@ function gadget:CommandFallback(unitID, unitDefID, teamID, cmdID, cmdParams, cmd return false end local cmdUsed, cmdRemove = HandleRawMove(unitID, unitDefID, cmdParams) + if cmdUsed and not cmdRemove and cmdParams[4] ~= -1 then + AddProjectileDodge(unitID) + end return cmdUsed, cmdRemove end @@ -800,6 +864,7 @@ end function gadget:UnitDestroyed(unitID, unitDefID, teamID) if unitID then rawMoveUnit[unitID] = nil + RemoveProjectileDodge(unitID) if unitDefID and constructorBuildDistDefs[unitDefID] and constructorByID[unitID] then RemoveConstructor(unitID) end @@ -817,6 +882,7 @@ function gadget:GameFrame(n) UpdateConstructors(n) UpdateMoveReplacement() UpdateEngineMoveCheck(n) + UpdateProjectileDodge(n) if n%247 == 4 then oldCommandStoppingRadius = commonStopRadius commonStopRadius = {} diff --git a/LuaRules/Gadgets/map_obsticles.lua b/LuaRules/Gadgets/map_obsticles.lua new file mode 100644 index 0000000000..8de8c24de1 --- /dev/null +++ b/LuaRules/Gadgets/map_obsticles.lua @@ -0,0 +1,115 @@ +function gadget:GetInfo() + return { + name = "Map Obsticles", + desc = "tracks map obsticles", + author = "petturtle", + date = "2021", + layer = 0, + enabled = true + } +end + +local DEBUG = false +local MAP_WIDTH = Game.mapSizeX +local MAP_HEIGHT = Game.mapSizeZ + +if gadgetHandler:IsSyncedCode() then + +local TTYPE_U = string.byte("u") -- unit +local TTYPE_G = string.byte("g") -- ground +local TTYPE_F = string.byte("f") -- feature +local TTYPE_P = string.byte('p') -- projectile + +local QuadTree = VFS.Include("LuaRules/Utilities/quad_tree.lua") +local Config = VFS.Include("LuaRules/Configs/projectile_dodge_defs.lua") + +local spSetWatchWeapon = Script.SetWatchWeapon +local spGetUnitPosition = Spring.GetUnitPosition +local spGetFeaturePosition = Spring.GetFeaturePosition +local spGetProjectileDefID = Spring.GetProjectileDefID +local spGetProjectileTarget = Spring.GetProjectileTarget +local spGetProjectilePosition = Spring.GetProjectilePosition + +local function GetProjectileGroundTarget(tArgs) + return tArgs[1], tArgs[2], tArgs[3] +end + +local ProjTTypeToPos = { + [TTYPE_U] = spGetUnitPosition, + [TTYPE_G] = GetProjectileGroundTarget, + [TTYPE_F] = spGetFeaturePosition, + [TTYPE_P] = spGetProjectilePosition, +} + +local layers = { + projectiles = { + color = {1, 0, 0, 1}, + obsticles = {}, + quad_tree = QuadTree.New(0, 0, MAP_WIDTH, MAP_HEIGHT, 4, 4), + }, +} + +function gadget:ProjectileCreated(projID) + local projDefID = spGetProjectileDefID(projID) + if projDefID and Config[projDefID] then + local tType, tArgs = spGetProjectileTarget(projID) + local x, y, z = ProjTTypeToPos[tType](tArgs) + layers.projectiles.obsticles[projID] = {{x, z}, y, projID, projDefID} + layers.projectiles.quad_tree:Insert(x, z, projID) + end +end + +function gadget:ProjectileDestroyed(projID) + if layers.projectiles.obsticles[projID] then + local target = layers.projectiles.obsticles[projID][1] + layers.projectiles.quad_tree:Remove(target[1], target[2], projID) + layers.projectiles.obsticles[projID] = nil + end +end + +-- obsticle +-- [1] = target +-- [2] = target y +-- [3] = projID +-- [4] = projDefID + +local function Query(x, z, radius, layer_names) + local results = {} + local layer, layer_results + for _, layer_name in pairs(layer_names) do + layer = layers[layer_name] + layer_results = layer.quad_tree:Query(x, z, radius) + for _, obsticle_id in pairs(layer_results) do + results[#results+1] = layer.obsticles[obsticle_id] + end + end + return results +end + +function gadget:Initialize() + for projDefID, _ in pairs(Config) do + spSetWatchWeapon(projDefID, true) + end + + _G.layers = layers + GG.MapObsticles = Query +end + +elseif DEBUG then -- ----- Unsynced Debug ----- + local SYNCED = SYNCED + local glColor = gl.Color + local glDepthTest = gl.DepthTest + local glDrawGroundCircle = gl.DrawGroundCircle + + function gadget:DrawWorld() + glDepthTest(true) + for _, layer in pairs(SYNCED.layers) do + glColor(layer.color) + for _, obsticle in pairs(layer.obsticles) do + glDrawGroundCircle(obsticle[1][1], obsticle[2], obsticle[1][2], 32, 12) + end + end + glDepthTest(false) + glColor({1,1,1,1}) + end +end \ No newline at end of file diff --git a/LuaRules/Gadgets/unit_dodge.lua b/LuaRules/Gadgets/unit_dodge.lua new file mode 100644 index 0000000000..a39caccc70 --- /dev/null +++ b/LuaRules/Gadgets/unit_dodge.lua @@ -0,0 +1,369 @@ +function gadget:GetInfo() + return { + name = "Unit Dodge", + desc = "calculates obsticle dodge vector for units", + author = "petturtle", + date = "2021", + layer = 0, + enabled = true + } +end + +local DEBUG = false + +if gadgetHandler:IsSyncedCode() then + +VFS.Include("LuaRules/Configs/customcmds.h.lua") +local Config = VFS.Include("LuaRules/Configs/projectile_dodge_defs.lua") +local ProjectileTargets = VFS.Include("LuaRules/Utilities/projectile_targets.lua") + +local CACHE_TIME = 15 +local DODGE_HEIGHT = 50 +local QUERY_RADIUS = 300 +local SAFETY_RADIUS = 30 +local RAY_DISTANCE = 140 +local MAX_DISTANCE = 140 + +local abs = math.abs +local max = math.max +local min = math.min +local cos = math.cos +local sin = math.sin +local acos = math.acos +local sqrt = math.sqrt + +local vector = Spring.Utilities.Vector + +local spIsPosInLos = Spring.IsPosInLos +local spValidUnitID = Spring.ValidUnitID +local spGetGameFrame = Spring.GetGameFrame +local spGetUnitDefID = Spring.GetUnitDefID +local spGetUnitStates = Spring.GetUnitStates +local spGetUnitRadius = Spring.GetUnitRadius +local spGetUnitHeight = Spring.GetUnitHeight +local spGetUnitIsDead = Spring.GetUnitIsDead +local spGetUnitDirection = Spring.GetUnitDirection +local spGetUnitPosition = Spring.GetUnitPosition +local spGetUnitAllyTeam = Spring.GetUnitAllyTeam +local spFindUnitCmdDesc = Spring.FindUnitCmdDesc +local spEditUnitCmdDesc = Spring.EditUnitCmdDesc +local spInsertUnitCmdDesc = Spring.InsertUnitCmdDesc +local spGetProjectileDefID = Spring.GetProjectileDefID +local spGetProjectileGravity = Spring.GetProjectileGravity +local spGetProjectilePosition = Spring.GetProjectilePosition +local spGetProjectileVelocity = Spring.GetProjectileVelocity + +local markerX = 0 +local markerZ = 0 + +local function PlaceMarker(x, z, msg) + Spring.MarkerErasePosition(markerX, 0, markerZ) + markerX = x + markerZ = z + Spring.MarkerAddPoint(markerX, 0, markerZ, msg) +end + +local idleDodgeCmdDesc = { + id = CMD_IDLE_DODGE, + type = CMDTYPE.ICON_MODE, + name = "Idle Dodge.", + action = 'idledodge', + tooltip = '.', + params = {0, "Always", "Not on Hold Pos", "Never"} +} + +local moveDodgeCmdDesc = { + id = CMD_MOVE_DODGE, + type = CMDTYPE.ICON_MODE, + name = "Move Dodge.", + action = 'movedodge', + tooltip = '.', + params = {0, "Always", "Not on Hold Pos", "Never"} +} + +-- obsticle +-- [1] = target +-- [2] = target y +-- [3] = projID +-- [4] = projDefID + +local hitCache = {} +local idleStates = {} +local moveStates = {} + +local function GetHitData(obsticle) + local currFrame = spGetGameFrame() + local hitData = hitCache[obsticle[3]] + if hitData and (hitData.expiredFrame == nil or hitData.expiredFrame > currFrame) then + return hitData + end + + local config = Config[obsticle[4]] + local line, eta = ProjectileTargets(config, obsticle[3], obsticle[1], obsticle[2], obsticle[2] + DODGE_HEIGHT) + hitData = { + eta = currFrame + eta, + line = line, + radius = config.aoe, + } + + if config.dynamic then + hitData.expiredFrame = currFrame + CACHE_TIME + end + + hitCache[obsticle[3]] = hitData + return hitData +end + +local function QueryHitData(unitID) + local query = {} + local currFrame = spGetGameFrame() + local allyTeam = spGetUnitAllyTeam(unitID) + local uPosX, _, uPosZ = spGetUnitPosition(unitID) + local obsticles = GG.MapObsticles(uPosX, uPosZ, QUERY_RADIUS, {"projectiles"}) + for i = 1, #obsticles do + local obsticle = obsticles[i] + local pPos, pPosY = vector.New3(spGetProjectilePosition(obsticle[3])) + if spIsPosInLos(pPos[1], pPosY, pPos[2], allyTeam) then + local hitData = GetHitData(obsticle) + if hitData.eta > currFrame then + query[#query+1] = GetHitData(obsticle) + end + end + end + return query +end + +local function GetNearestPointToLine(point, a, b) + local line = vector.Subtract(b, a) + if line[1] ~= 0 or line[2] ~= 0 then + local t = ((point[1]-a[1])*line[1] + (point[2] - a[2])*line[2]) / (line[1]*line[1] + line[2]*line[2]) + if t < 0 then + return vector.Clone(a) + elseif t > 1 then + return vector.Clone(b) + else + return vector.Add(a, vector.Mult(t, line)) + end + end + return vector.Clone(a) +end + + +local function BoidDodge(query, uPos, uRadius) + local dodge, dodgeMag = {0, 0}, 0 + for i = 1, #query do + local hitData = query[i] + local line = hitData.line + local hitPoint = GetNearestPointToLine(uPos, line[1], line[3]) + if uPos[1] == hitPoint[1] and uPos[2] == hitPoint[2] then + hitPoint[1] = hitPoint[1] + 1 + hitPoint[2] = hitPoint[2] + 1 + end + local dodgeRadius = vector.Distance(uPos, hitPoint) + if dodgeRadius < hitData.radius + uRadius + SAFETY_RADIUS then + local hitNormal = vector.Norm(1, vector.Subtract(uPos, hitPoint)) + dodge = vector.Add(dodge, hitNormal) + dodgeMag = max(dodgeMag, hitData.radius + uRadius + SAFETY_RADIUS + SAFETY_RADIUS - dodgeRadius) + end + end + dodgeMag = min(dodgeMag, MAX_DISTANCE) + dodge = vector.Norm(dodgeMag, dodge) + return dodge, dodgeMag +end + +local function RayDodge(query, uPos, uDir, rDir, uRadius) + local closestPoint, closestRadius, closestDir, closestDistance = nil, 0, 0, 999999 + for i = 1, #query do + local hitData = query[i] + local line = hitData.line + local lineDir = vector.Subtract(line[3], line[1]) + local intersection = vector.Intersection(uPos, rDir, line[1], lineDir) + if intersection then + local unitToInter = vector.Subtract(intersection, uPos) + if vector.Dot(rDir, unitToInter) >= 0 then + local cNear = GetNearestPointToLine(intersection, line[1], line[3]) + local cDistance = vector.Distance(cNear, intersection) + if cDistance < hitData.radius + uRadius + SAFETY_RADIUS then + local cNearToUnit = vector.Subtract(uPos, cNear) + local angle = vector.AngleTo(cNearToUnit, lineDir) + local dodgePointDistance = hitData.radius / sin(abs(angle)) + SAFETY_RADIUS + local dodgePoint = vector.Add(cNear, vector.Norm(dodgePointDistance, cNearToUnit)) + local distance = vector.Distance(dodgePoint, uPos) + if distance < closestDistance then + closestDir = lineDir + closestPoint = dodgePoint + closestRadius = hitData.radius + closestDistance = distance + end + end + end + end + end + + if closestPoint then + if vector.Dot(uDir, closestDir) < 0 then + closestDir = {-closestDir[1], -closestDir[2]} + end + local dodgeRadius = uRadius + closestRadius + SAFETY_RADIUS + SAFETY_RADIUS + local target = vector.Add(closestPoint, vector.Norm(dodgeRadius, closestDir)) + local toTarget = vector.Norm(RAY_DISTANCE, vector.Subtract(target, uPos)) + return uPos[1] + toTarget[1], uPos[2] + toTarget[2], RAY_DISTANCE + end + + return uPos[1] + rDir[1], uPos[2] + rDir[2], 0 +end + +local function IdleDodge(unitID) + if not spValidUnitID(unitID) or spGetUnitIsDead(unitID) then + return 0, 0, 0 + end + + local uPos = vector.New3(spGetUnitPosition(unitID)) + if not idleStates[unitID] then + return uPos[1], uPos[2], 0 + end + + local idleDodge = idleStates[unitID][1] + local moveState = spGetUnitStates(unitID).movestate + if (idleDodge == 1 and moveState == 0) or idleDodge == 2 then + return uPos[1], uPos[2], 0 + end + + local query = QueryHitData(unitID) + local uRadius = spGetUnitRadius(unitID) + local dodge, dodgeMag = BoidDodge(query, uPos, uRadius) + return uPos[1] + dodge[1], uPos[2] + dodge[2], dodgeMag +end + +local function MoveDodge(unitID, move_x, move_z) + if not spValidUnitID(unitID) or spGetUnitIsDead(unitID) then + return move_x, move_z, 0 + end + + if not moveStates[unitID] then + return move_x, move_z, 0 + end + + local moveDodge = moveStates[unitID][1] + local moveState = spGetUnitStates(unitID).movestate + if (moveDodge == 1 and moveState == 0) or moveDodge == 2 then + return move_x, move_z, 0 + end + + local query = QueryHitData(unitID) + local uRadius = spGetUnitRadius(unitID) + local uPos = vector.New3(spGetUnitPosition(unitID)) + local dodge, dodgeMag = BoidDodge(query, uPos, uRadius) + + if dodgeMag <= 0 and move_x then + local uDir = vector.New3(spGetUnitDirection(unitID)) + local rDir = vector.Subtract({move_x, move_z}, uPos) + return RayDodge(query, uPos, uDir, rDir, uRadius) + end + + return uPos[1] + dodge[1], uPos[2] + dodge[2], dodgeMag +end + +local external = { + Idle = IdleDodge, + Move = MoveDodge, +} + +local function CmdToggle(unitID, unitDefID, cmdID, cmdParams) + local cmdDescID, cmdDesc + if cmdID == CMD_IDLE_DODGE then + cmdDesc = idleDodgeCmdDesc + cmdDescID = spFindUnitCmdDesc(unitID, CMD_IDLE_DODGE) + idleStates[unitID] = cmdDesc.params + else + cmdDesc = moveDodgeCmdDesc + cmdDescID = spFindUnitCmdDesc(unitID, CMD_MOVE_DODGE) + moveStates[unitID] = cmdDesc.params + end + + if (cmdDescID) then + cmdDesc.params[1] = cmdParams[1] + spEditUnitCmdDesc(unitID, cmdDescID, {params = cmdDesc.params}) + end + return false +end + +function gadget:UnitCreated(unitID, unitDefID, teamID) + spInsertUnitCmdDesc(unitID, idleDodgeCmdDesc) + spInsertUnitCmdDesc(unitID, moveDodgeCmdDesc) + CmdToggle(unitID, unitDefID, CMD_IDLE_DODGE, {1}) + CmdToggle(unitID, unitDefID, CMD_MOVE_DODGE, {1}) +end + +function gadget:UnitDestroyed(unitID) + idleStates[unitID] = nil + moveStates[unitID] = nil +end + +function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions) + if (cmdID ~= CMD_IDLE_DODGE and cmdID ~= CMD_MOVE_DODGE) then + return true + end + return CmdToggle(unitID, unitDefID, cmdID, cmdParams) +end + +function gadget:ProjectileDestroyed(projID) + if hitCache[projID] then + hitCache[projID] = nil + end +end + +function gadget:Initialize() + GG.UnitDodge = external + _G.hitCache = hitCache +end + +elseif DEBUG then -- ----- Unsynced ----- + +local SYNCED = SYNCED +local PI = math.pi +local TWO_PI = math.pi * 2 +local INCREMENT = PI/ 6 + +local cos = math.cos +local sin = math.sin +local atan2 = math.atan2 +local glColor = gl.Color +local glVertex = gl.Vertex +local glDepthTest = gl.DepthTest +local glBeginEnd = gl.BeginEnd +local glLineWidth = gl.LineWidth +local glPopMatrix = gl.PopMatrix +local glPushMatrix = gl.PushMatrix +local GL_LINE_LOOP = GL.LINE_LOOP + +local function DrawHitZone(x1, y1, z1, x2, y2, z2, aoe) + local dirX, dirZ = x1 - x2, z1 - z2 + local angle = atan2(x2*dirZ - z2*dirX, x2*dirX + z2*dirZ) - PI/4 + for theta = angle, PI + angle, INCREMENT do + glVertex({x1 + aoe * cos(theta), y1 + 5, z1 + aoe * sin(theta)}) + end + for theta = PI + angle, TWO_PI + angle, INCREMENT do + glVertex({x2 + aoe * cos(theta), y2 + 5, z2 + aoe * sin(theta)}) + end +end + +function gadget:DrawWorld() + if SYNCED.hitCache then + glDepthTest(true) + glColor({0,1,0,0.25}) + glLineWidth(2) + for _, hitdata in pairs(SYNCED.hitCache) do + local line = hitdata.line + local radius = hitdata.radius + glPushMatrix() + glBeginEnd(GL_LINE_LOOP, DrawHitZone, line[1][1], line[2], line[1][2], line[3][1], line[4], line[3][2], radius) + glPopMatrix() + end + glLineWidth(1) + glColor({1,1,1,1}) + glDepthTest(false) + end +end + +end diff --git a/LuaRules/Gadgets/unit_tactical_ai.lua b/LuaRules/Gadgets/unit_tactical_ai.lua index 8441aa5132..40652d7f95 100644 --- a/LuaRules/Gadgets/unit_tactical_ai.lua +++ b/LuaRules/Gadgets/unit_tactical_ai.lua @@ -43,6 +43,7 @@ local random = math.random local sqrt = math.sqrt local min = math.min +local ClampPosition = Spring.Utilities.ClampPosition local GiveClampedOrderToUnit = Spring.Utilities.GiveClampedOrderToUnit local GetEffectiveWeaponRange = Spring.Utilities.GetEffectiveWeaponRange @@ -275,6 +276,7 @@ end local function ReturnUnitToIdlePos(unitID, unitData, force) unitData.queueReturnX = unitData.idleX unitData.queueReturnZ = unitData.idleZ + unitData.returnCommandPos = true unitData.setReturn = true unitData.forceReturn = force end @@ -441,11 +443,14 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end + cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = ClampPosition(cx, cz) + if move then - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end --Spring.SetUnitMoveGoal(unitID, cx, cy, cz) unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -493,13 +498,15 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cy = ey cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end + cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData. cy, unitData.cz = cx, cy, cz @@ -533,13 +540,15 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = uz-(-(uz-ez)*behaviour.jinkAwayParallelLength+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength)/pointDis end end + cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -695,13 +704,15 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty local cx = ux - wantedDis*ex/eDist local cy = uy local cz = uz - wantedDis*ez/eDist + cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -767,20 +778,22 @@ local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typ local cx = ux+(ux-ex)*f local cy = uy local cz = uz+(uz-ez)*f + cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if cmdID then if move then - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, CMD.OPT_ALT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end elseif isIdleAttack then - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz }, CMD_OPT_RIGHT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) else - cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz }, CMD_OPT_RIGHT ) + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -815,6 +828,94 @@ local function DoAiLessIdleCheck(unitID, behaviour, unitData, frame, enemy, enem UpdateIdleAgressionState(unitID, behaviour, unitData, frame, enemy, typeKnown and enemyUnitDef, 250, pointDis, ux, uz, ex, ez) end +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Idle Handling + +local function AddIdleUnit(unitID, unitDefID) + if not (unit[unitID] and spValidUnitID(unitID)) then + return + end + local unitData = unit[unitID] + unitData.wasIdle = true + + local doDebug = (debugUnit and debugUnit[unitID]) or debugAll + if doDebug then + Spring.Utilities.UnitEcho(unitID, "Idle " .. unitID) + Spring.Echo("=== Unit Idle", unitID, " ===") + end + + if unitData.wasDodge then + return + end + + if unitData.idleWantReturn and unitData.idleX then + if doDebug then + Spring.Echo("Return to idle position", unitData.idleX, unitData.idleZ) + end + ReturnUnitToIdlePos(unitID, unitData) + return + end + + local behaviour = GetUnitBehavior(unitID, unitData.udID) + local nearbyEnemy = spGetUnitNearestEnemy(unitID, behaviour.leashAgressRange, true) or false + local x, _, z = Spring.GetUnitPosition(unitID) + + unitData.idleX = x + unitData.idleZ = z + unitData.wantFightReturn = nil + unitData.idleWantReturn = nil + + if doDebug then + Spring.Echo("New Idle", unitData.idleX, unitData.idleZ, nearbyEnemy) + end + + if nearbyEnemy then + local enemyUnitDef, typeKnown = GetUnitVisibleInformation(nearbyEnemy, unitData.allyTeam) + if enemyUnitDef and typeKnown then + local enemyRange = GetEnemyRealRange(enemyUnitDef) + if enemyRange and enemyRange > 0 then + local enemyDist = spGetUnitSeparation(nearbyEnemy, unitID, true) + if enemyRange + behaviour.leashEnemyRangeLeeway < enemyDist then + nearbyEnemy = false -- Don't aggress against nearby enemy that cannot shoot. + end + end + end + end + + if doDebug then + Spring.Echo("After nearby check", nearbyEnemy) + end + + SetIdleAgression(unitID, unitData, nearbyEnemy) + --Spring.Utilities.UnitEcho(unitID, "I") +end + +function gadget:UnitIdle(unitID, unitDefID) + AddIdleUnit(unitID, unitDefID) +end + +function gadget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOpts, cmdTag, playerID, fromSynced, fromLua) + if playerID == -1 or fromLua then + return + end + if cmdID and stateCommands[cmdID] then + return + end + local unitData = unit[unitID] + if not unitData then + return + end + if (cmdID == CMD_FIGHT or cmdID == CMD_ATTACK) and unitData.receivedOrder and not cmdOpts.shift then + needNextUpdate = needNextUpdate or {} + needNextUpdate[#needNextUpdate + 1] = unitID + end + unitData.wasIdle = false + unitData.wasDodge = false + unitData.returnCommandPos = false + unitData.idleWantReturn = false +end + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ---- Unit AI Selection @@ -831,7 +932,7 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, behaviour = behaviour.fightOnlyOverride end - if isIdleAttack and enemy and (not unitData.idleAgression) and typeKnown + if isIdleAttack and enemy and (not unitData.idleAgression or unitData.idleDodge) and typeKnown and ((behaviour.idleFleeCombat and armedUnitDefIDs[enemyUnitDef]) or (behaviour.idleFlee and behaviour.idleFlee[enemyUnitDef])) then local orderSent = DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typeKnown, move, isIdleAttack, cmdID, cmdTag, frame) if not orderSent then @@ -900,6 +1001,56 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, return false end +local function IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) + if not (unitData.wasIdle and unitData.idleX) then + return true + end + + local dodgeDistSq = DistSq(unitData.idleX, unitData.idleZ, cx, cz) + if dodgeDistSq > ((dodgeSize + 16)*2)^2 then + return false + end + return true +end + +local function DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight, holdPos) + if (unitData.wasIdle) and not holdPos then + local cx, cz, dodgeSize = GG.UnitDodge.Idle(unitID) + if cx then + cx, cz = ClampPosition(cx, cz) + local ux, uy, uz = spGetUnitPosition(unitID) + --Spring.MarkerAddPoint(cx, 0, cz, "" .. dodgeSize) + if IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) then + if move then + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, uy, cz, -1}, CMD.OPT_ALT ) + spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) + else + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, uy, cz, -1}, CMD.OPT_ALT ) + end + unitData.cx, unitData.cy, unitData.cz = cx, uy, cz + unitData.receivedOrder = true + unitData.wasDodge = true + return true + end + end + end + + if unitData.wasDodge and unitData.wasIdle then + unitData.idleWantReturn = true + local canIdleReturn = true + local rx, rz = unitData.queueReturnX or unitData.idleX, unitData.queueReturnZ or unitData.idleZ + local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, rx, rz) + -- return to idle pos if safe + if dodgeSize == 0 then + -- Spring.MarkerAddPoint(cx, 0, cz, "moving") + unitData.wasDodge = false + AddIdleUnit(unitID, unitData) + end + end + + return false +end + local function DoUnitUpdate(unitID, frame, slowUpdate) local unitData = unit[unitID] @@ -973,8 +1124,9 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) Spring.Echo("wasIdle", unitData.wasIdle, "isIdleAttack", isIdleAttack, "idleWantReturn", unitData.idleWantReturn) Spring.Echo("queueReturnX queueReturnZ", unitData.queueReturnX, unitData.queueReturnZ, "setReturn", unitData.setReturn) end - - unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack) + + local dodgeSentOrder = DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight, holdPos) + unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack or unitData.wasDodge) if doDebug then Spring.Echo("after", "idleWantReturn", unitData.idleWantReturn) Spring.Utilities.UnitEcho(unitID, unitData.idleWantReturn and "W" or "O_O") @@ -1001,7 +1153,7 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) enemyUnitDef, typeKnown = GetUnitVisibleInformation(enemy, unitData.allyTeam) end - if not (exitEarly or behaviour.onlyIdleHandling) then + if not (exitEarly or behaviour.onlyIdleHandling or dodgeSentOrder) then --Spring.Echo("cmdID", cmdID, cmdTag, move, math.random()) aiTargetFound, sentAiOrder = DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, fightX, fightY, fightZ, unitData, behaviour, enemy, enemyUnitDef, typeKnown, @@ -1046,6 +1198,7 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) else -- If the command queue is empty (ie still idle) then return to position local ry = math.max(0, Spring.GetGroundHeight(rx, rz) or 0) + --Spring.MarkerAddPoint(rx, 0, rz, "returning") GiveClampedOrderToUnit(unitID, unitData.wantFightReturn and CMD_FIGHT or CMD_RAW_MOVE, {rx, ry, rz}, CMD.OPT_ALT ) unitData.setReturn = nil unitData.forceReturn = nil @@ -1094,88 +1247,6 @@ function gadget:GameFrame(n) UpdateUnits(n, n%UPDATE_RATE + 1, UPDATE_RATE) end --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- Idle Handling - -local function AddIdleUnit(unitID, unitDefID) - if not (unit[unitID] and spValidUnitID(unitID)) then - return - end - local unitData = unit[unitID] - unitData.wasIdle = true - - local doDebug = (debugUnit and debugUnit[unitID]) or debugAll - if doDebug then - Spring.Utilities.UnitEcho(unitID, "Idle " .. unitID) - Spring.Echo("=== Unit Idle", unitID, " ===") - end - - if unitData.idleWantReturn and unitData.idleX then - if doDebug then - Spring.Echo("Return to idle position", unitData.idleX, unitData.idleZ) - end - ReturnUnitToIdlePos(unitID, unitData) - return - end - - local behaviour = GetUnitBehavior(unitID, unitData.udID) - local nearbyEnemy = spGetUnitNearestEnemy(unitID, behaviour.leashAgressRange, true) or false - local x, _, z = Spring.GetUnitPosition(unitID) - - unitData.idleX = x - unitData.idleZ = z - unitData.wantFightReturn = nil - unitData.idleWantReturn = nil - - if doDebug then - Spring.Echo("New Idle", unitData.idleX, unitData.idleZ, nearbyEnemy) - end - - if nearbyEnemy then - local enemyUnitDef, typeKnown = GetUnitVisibleInformation(nearbyEnemy, unitData.allyTeam) - if enemyUnitDef and typeKnown then - local enemyRange = GetEnemyRealRange(enemyUnitDef) - if enemyRange and enemyRange > 0 then - local enemyDist = spGetUnitSeparation(nearbyEnemy, unitID, true) - if enemyRange + behaviour.leashEnemyRangeLeeway < enemyDist then - nearbyEnemy = false -- Don't aggress against nearby enemy that cannot shoot. - end - end - end - end - - if doDebug then - Spring.Echo("After nearby check", nearbyEnemy) - end - - SetIdleAgression(unitID, unitData, nearbyEnemy) - --Spring.Utilities.UnitEcho(unitID, "I") -end - -function gadget:UnitIdle(unitID, unitDefID) - AddIdleUnit(unitID, unitDefID) -end - -function gadget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOpts, cmdTag, playerID, fromSynced, fromLua) - if playerID == -1 or fromLua then - return - end - if cmdID and stateCommands[cmdID] then - return - end - local unitData = unit[unitID] - if not unitData then - return - end - if (cmdID == CMD_FIGHT or cmdID == CMD_ATTACK) and unitData.receivedOrder and not cmdOpts.shift then - needNextUpdate = needNextUpdate or {} - needNextUpdate[#needNextUpdate + 1] = unitID - end - unitData.wasIdle = false - unitData.idleWantReturn = false -end - -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Command Handling diff --git a/LuaRules/Utilities/projectile_targets.lua b/LuaRules/Utilities/projectile_targets.lua new file mode 100644 index 0000000000..79924cf72f --- /dev/null +++ b/LuaRules/Utilities/projectile_targets.lua @@ -0,0 +1,75 @@ +local vector = Spring.Utilities.Vector +local spGetGroundHeight = Spring.GetGroundHeight +local spGetUnitPosition = Spring.GetUnitPosition +local spGetUnitDirection = Spring.GetUnitDirection +local spGetFeaturePosition = Spring.GetFeaturePosition +local spGetProjectileTarget = Spring.GetProjectileTarget +local spGetProjectileGravity = Spring.GetProjectileGravity +local spGetProjectilePosition = Spring.GetProjectilePosition +local spGetProjectileVelocity = Spring.GetProjectileVelocity + +local abs = math.abs +local sqrt = math.sqrt + +local function GetKinematicProjTimeTo(velocity, acceleration, distance, multi) + local root = (velocity * velocity) + (2 * acceleration * distance) * multi + if root <= 0 then + return 1 + end + return (-velocity - sqrt(root)) / acceleration +end + +local function GetKinematicETA(velocity, acceleration, distance) + local eta = 1 + if distance >= 0 then + eta = GetKinematicProjTimeTo(velocity, acceleration, distance, 1) + elseif distance < 0 then + distance = -distance + eta = GetKinematicProjTimeTo(velocity, acceleration, distance, -1) + end + return eta +end + +local function GetKinematic(projID, target, targetY, height, config) + local pGravity = spGetProjectileGravity(projID) + local pPos, pPosY = vector.New3(spGetProjectilePosition(projID)) + local pVel, pVelY = vector.New3(spGetProjectileVelocity(projID)) + local heightETA = GetKinematicETA(pVelY, pGravity, height - pPosY) + local a = vector.Add(pPos, vector.Mult(heightETA, pVel)) + local targetETA = GetKinematicETA(pVelY, pGravity, targetY - pPosY) + local b = vector.Add(pPos, vector.Mult(targetETA, pVel)) + return {a, height, b, targetY}, heightETA +end + +local function GetLinear(projID, target, targetY, height, config) + local pPos, pPosY = vector.New3(spGetProjectilePosition(projID)) + local pVel, pVelY = vector.New3(spGetProjectileVelocity(projID)) + local eta = 40 + local b = vector.Add(pPos, vector.Mult(eta, pVel)) + return {pPos, pPosY, b, targetY}, eta +end + +local function GetTarget(projID, target, targetY, height, config) + local _, pPosY = spGetProjectilePosition(projID) + local _, pVelY = spGetProjectileVelocity(projID) + local eta = -1 + if pVelY < 0 then + eta = (pPosY - height) / -pVelY + end + local offset_target = {target[1] + 1, target[2] + 1} + return {offset_target, targetY, target, targetY}, eta +end + +local atHeight = { + ["Cannon"] = GetKinematic, + ["AircraftBomb"] = GetKinematic, + ["MissileLauncher"] = GetLinear, + ["StarburstLauncher"] = GetTarget, + ["BeamLaser"] = GetLinear, +} + +local function ProjectileTargets(config, projID, target, targetY, height) + return atHeight[config.wType](projID, target, targetY, height, config) +end + +return ProjectileTargets \ No newline at end of file diff --git a/LuaRules/Utilities/quad_tree.lua b/LuaRules/Utilities/quad_tree.lua new file mode 100644 index 0000000000..1d2e11eb84 --- /dev/null +++ b/LuaRules/Utilities/quad_tree.lua @@ -0,0 +1,119 @@ +local Rect = VFS.Include("LuaRules/Utilities/rect.lua") + +local QuadTree = {} +QuadTree.__index = QuadTree + +function QuadTree.New(x, y, width, height, capacity, maxDepth, depth) + local instance = { + data = {}, + dataCount = 0, + capacity = capacity, + depth = depth or 0, + maxDepth = maxDepth, + isSubdivided = false, + rect = Rect.New(x, y, width, height) + } + setmetatable(instance, QuadTree) + return instance +end + +function QuadTree:Insert(x, y, data) + if self.rect:HasPoint(x, y) then + self:insert(x, y, data) + end +end + +function QuadTree:Remove(x, y, data) + if self.rect:HasPoint(x, y) then + self:remove(x, y, data) + end +end + +function QuadTree:Query(x, y, radius) + local found = {} + local count = self:query(Rect.New(x - radius, y - radius, radius * 2, radius * 2), found, 0) + return found, count +end + +function QuadTree:insert(x, y, data) + if self.dataCount < self.capacity or self.depth >= self.maxDepth then + self.data[data] = {x, y} + self.dataCount = self.dataCount + 1 + elseif self.isSubdivided then + self:getPointSubNode(x, y):insert(x, y, data) + else + self:subdivide() + self:getPointSubNode(x, y):insert(x, y, data) + end +end + +function QuadTree:remove(x, y, data) + if self.data[data] then + self.data[data] = nil + self.dataCount = self.dataCount - 1 + elseif self.isSubdivided then + self:getPointSubNode(x, y):remove(x, y, data) + if self:areChildrenEmpty() then + self.topLeft = nil + self.topRight = nil + self.bottomLeft = nil + self.bottomRight = nil + self.isSubdivided = false + end + end +end + +function QuadTree:query(rect, found, count) + for data, point in pairs(self.data) do + if rect:HasPoint(point[1], point[2]) then + count = count + 1 + found[count] = data + end + end + + if self.isSubdivided and self.rect:Intersects(rect) then + count = self.topLeft:query(rect, found, count) + count = self.topRight:query(rect, found, count) + count = self.bottomLeft:query(rect, found, count) + count = self.bottomRight:query(rect, found, count) + end + return count +end + +function QuadTree:areChildrenEmpty() + return self.topLeft:isEmpty() and self.topRight:isEmpty() and self.bottomLeft:isEmpty() and self.bottomRight:isEmpty() +end + +function QuadTree:isEmpty() + return self.isSubdivided == false and self.dataCount == 0 +end + +function QuadTree:subdivide() + local x = self.rect.x + local y = self.rect.y + local width = self.rect.width * 0.5 + local height = self.rect.height * 0.5 + self.topLeft = QuadTree.New(x, y, width, height, self.capacity, self.maxDepth, self.depth + 1) + self.topRight = QuadTree.New(x + width, y, width, height, self.capacity, self.maxDepth, self.depth + 1) + self.bottomLeft = QuadTree.New(x, y + height, width, height, self.capacity, self.maxDepth, self.depth + 1) + self.bottomRight = QuadTree.New(x + width, y + height, width, height, self.capacity, self.maxDepth, self.depth + 1) + self.isSubdivided = true +end + +function QuadTree:getPointSubNode(x, y) + local cX = self.rect.x + self.rect.width * 0.5 + local cY = self.rect.y + self.rect.height * 0.5 + if x < cX then + if y < cY then + return self.topLeft + end + return self.bottomLeft + else + if y < cY then + return self.topRight + end + return self.bottomRight + end +end + +return QuadTree \ No newline at end of file diff --git a/LuaRules/Utilities/rect.lua b/LuaRules/Utilities/rect.lua new file mode 100644 index 0000000000..c20c57b4b3 --- /dev/null +++ b/LuaRules/Utilities/rect.lua @@ -0,0 +1,29 @@ +local Rect = {} +Rect.__index = Rect + +function Rect.New(x, y, width, height) + local instance = { + x = x, + y = y, + width = width, + height = height + } + setmetatable(instance, Rect) + return instance +end + +function Rect:HasPoint(x, y) + local x2, y2 = self.x + self.width, self.y + self.height + return x >= self.x and x <= x2 and y >= self.y and y <= y2 +end + +function Rect:Intersects(rect) + return not ( + rect.x - rect.width > self.x + self.width or + rect.x + rect.width < self.x - self.width or + rect.y - rect.height > self.y + self.height or + rect.y + rect.height < self.y - self.height + ) +end + +return Rect \ No newline at end of file diff --git a/LuaRules/Utilities/vector.lua b/LuaRules/Utilities/vector.lua index 89f059d1e9..8b593fab06 100644 --- a/LuaRules/Utilities/vector.lua +++ b/LuaRules/Utilities/vector.lua @@ -2,6 +2,15 @@ local sqrt = math.sqrt local pi = math.pi local cos = math.cos local sin = math.sin +local atan2 = math.atan2 + +local function New3(x,y,z) + return {x, z}, y +end + +local function Clone(v) + return {v[1], v[2]} +end local function DistSq(x1,z1,x2,z2) return (x1 - x2)*(x1 - x2) + (z1 - z2)*(z1 - z2) @@ -23,6 +32,11 @@ local function Subtract(v1, v2) return {v1[1] - v2[1], v1[2] - v2[2]} end +local function Distance(v1, v2) + local dir = {v1[1] - v2[1], v1[2] - v2[2]} + return sqrt(dir[1]*dir[1] + dir[2]*dir[2]) +end + local function AbsVal(x, y, z) if z then return sqrt(x*x + y*y + z*z) @@ -73,7 +87,11 @@ local function Angle(x, z) return 0 end -function Dot(v1, v2) +local function AngleTo(v1, v2) + return atan2(v1[1]*v2[2] - v1[2]*v2[1], v1[1]*v2[1] + v1[2]*v2[2]); +end + +local function Dot(v1, v2) if v1[3] then return v1[1]*v2[1] + v1[2]*v2[2] + v1[3]*v2[3] else @@ -81,7 +99,7 @@ function Dot(v1, v2) end end -function Cross(v1, v2) +local function Cross(v1, v2) return {v1[2]*v2[3] - v1[3]*v2[2], v1[3]*v2[1] - v1[1]*v2[3], v1[1]*v2[2] - v1[2]*v2[1]} end @@ -97,6 +115,23 @@ local function Normal(v1, v2) return Subtract(v1, projection), projection end +local function SlopeIntercept(v1, v2) + local a = v2[2] - v1[2] + local b = v1[1] - v2[1] + local c = (a * v1[1]) + (b * v1[2]) + return a, b, c +end + +local function Intersection(v1, d1, v2, d2) + local a1, b1, c1 = SlopeIntercept(v1, Add(v1, d1)) + local a2, b2, c2 = SlopeIntercept(v2, Add(v2, d2)) + local delta = a1 * b2 - b1 * a2 + if delta == 0 then + return nil + end + return {((b2 * c1) - (b1 * c2)) / delta, ((a1 * c2) - (a2 * c1)) / delta} +end + -- Spring.GetHeadingFromVector is actually broken at angles close to pi/4 and reflections local function AngleSpringHeaving(x, z) if z then @@ -196,6 +231,8 @@ local function AngleAverageShortest(angleA, angleB) end Spring.Utilities.Vector = { + New3 = New3, + Clone = Clone, DistSq = DistSq, Dist3D = Dist3D, Mult = Mult, @@ -205,11 +242,15 @@ Spring.Utilities.Vector = { Cross = Cross, Norm = Norm, Angle = Angle, + AngleTo = AngleTo, Project = Project, Normal = Normal, + SlopeIntercept = SlopeIntercept, + Intersection = Intersection, PolarToCart = PolarToCart, Add = Add, Subtract = Subtract, + Distance = Distance, GetAngleBetweenUnitVectors = GetAngleBetweenUnitVectors, InverseBasis = InverseBasis, ChangeBasis = ChangeBasis, diff --git a/LuaUI/Configs/customCmdTypes.lua b/LuaUI/Configs/customCmdTypes.lua index 02b96c2359..1a333e1e2e 100644 --- a/LuaUI/Configs/customCmdTypes.lua +++ b/LuaUI/Configs/customCmdTypes.lua @@ -121,6 +121,8 @@ local custom_cmd_actions = { divestate = {cmdType = 2, cmdID = CMD_UNIT_BOMBER_DIVE_STATE, name = "Raven Dive", states = {'Never', 'Under Shields', 'For Mobiles', 'Always Low'}}, globalbuild = {cmdType = 2, cmdID = CMD_GLOBAL_BUILD, name = "Constructor Global AI", states = {'Off', 'On'}}, toggledrones = {cmdType = 2, cmdID = CMD_TOGGLE_DRONES, name = "Drone Construction.", states = {'Off', 'On'}}, + idledodge = {cmdType = 2, cmdID = CMD_IDLE_DODGE, name = "Idle Dodge", states = {'0', '1', '2'}}, + movedodge = {cmdType = 2, cmdID = CMD_MOVE_DODGE, name = "Move Dodge", states = {'0', '1', '2'}}, } -- These actions are created from echoing all actions that appear when all units are selected. @@ -209,6 +211,8 @@ local usedActions = { ["fireatshields"] = true, ["firetowards"] = true, ["goostate"] = true, + ["idledodge"] = true, + ["movedodge"] = true, -- These actions are used, just not by selecting everything with default UI ["globalbuild"] = true, diff --git a/LuaUI/Configs/integral_menu_commands.lua b/LuaUI/Configs/integral_menu_commands.lua index f18633a510..37ca929176 100644 --- a/LuaUI/Configs/integral_menu_commands.lua +++ b/LuaUI/Configs/integral_menu_commands.lua @@ -84,6 +84,8 @@ local cmdPosDef = { [CMD.IDLEMODE] = {pos = 1, priority = 18}, [CMD_AP_FLY_STATE] = {pos = 1, priority = 19}, [CMD_AUTO_CALL_TRANSPORT] = {pos = 1, priority = 21}, + [CMD_IDLE_DODGE] = {pos = 1, priority = 10.1}, + [CMD_MOVE_DODGE] = {pos = 1, priority = 10.2}, } -------------------------------------------------------------------------------- diff --git a/LuaUI/Configs/integral_menu_config.lua b/LuaUI/Configs/integral_menu_config.lua index b14879fd1b..806df12671 100644 --- a/LuaUI/Configs/integral_menu_config.lua +++ b/LuaUI/Configs/integral_menu_config.lua @@ -37,7 +37,9 @@ local tooltips = { UNIT_FLOAT_STATE = "Float State (_STATE_)\n Set when certain amphibious units float to the surface.", SELECTION_RANK = "Selection Rank (_STATE_)\n Priority for selection filtering.", FORMATION_RANK = "Formation Rank (_STATE_)\n set rank in formation.", - TOGGLE_DRONES = "Drone Construction (_STATE_)\n Toggle drone creation." + TOGGLE_DRONES = "Drone Construction (_STATE_)\n Toggle drone creation.", + IDLE_DODGE = "Idle Dodge (_STATE_)\n", + MOVE_DODGE = "Move Dodge (_STATE_)\n", } local tooltipsAlternate = { @@ -304,6 +306,22 @@ local commandDisplayConfig = { tooltips.TOGGLE_DRONES:gsub("_STATE_", "Enabled"), } }, + [CMD_IDLE_DODGE] = { + texture = {imageDir .. 'states/move_roam.png', imageDir .. 'states/move_engage.png', imageDir .. 'states/move_hold.png'}, + stateTooltip = { + tooltips.IDLE_DODGE:gsub("_STATE_", "Always"), + tooltips.IDLE_DODGE:gsub("_STATE_", "Not on Hold Pos"), + tooltips.IDLE_DODGE:gsub("_STATE_", "Never") + }, + }, + [CMD_MOVE_DODGE] = { + texture = {imageDir .. 'states/move_roam.png', imageDir .. 'states/move_engage.png', imageDir .. 'states/move_hold.png'}, + stateTooltip = { + tooltips.MOVE_DODGE:gsub("_STATE_", "Always"), + tooltips.MOVE_DODGE:gsub("_STATE_", "Not on Hold Pos"), + tooltips.MOVE_DODGE:gsub("_STATE_", "Never") + }, + }, } -------------------------------------------------------------------------------- diff --git a/LuaUI/Configs/integral_menu_culling.lua b/LuaUI/Configs/integral_menu_culling.lua index c503cc223d..596043787c 100644 --- a/LuaUI/Configs/integral_menu_culling.lua +++ b/LuaUI/Configs/integral_menu_culling.lua @@ -53,6 +53,8 @@ local configList = { {cmdID = CMD.REPEAT , state = true, default = true, name = "Repeat"}, {cmdID = CMD_RETREAT , state = true, default = true, name = "Retreat"}, {cmdID = CMD.TRAJECTORY , state = true, default = true, name = "Trajectory"}, + {cmdID = CMD_IDLE_DODGE , state = true, default = true, name = "Idle Dodge"}, + {cmdID = CMD_MOVE_DODGE , state = true, default = true, name = "Move Dodge"}, {label = "Advanced States (hidden by default)"}, {cmdID = CMD_DISABLE_ATTACK , state = true, default = false, name = "Allow Attack Commands"}, @@ -111,6 +113,8 @@ local defaultValues = { [CMD_FORMATION_RANK] = true, [CMD_FIRE_AT_SHIELD] = true, --[CMD_FIRE_TOWARDS_ENEMY] = true, + [CMD_IDLE_DODGE] = true, + [CMD_MOVE_DODGE] = true, } return configList, defaultValues diff --git a/LuaUI/Configs/stateTypes.lua b/LuaUI/Configs/stateTypes.lua index 04e0c60926..2cc734f26b 100644 --- a/LuaUI/Configs/stateTypes.lua +++ b/LuaUI/Configs/stateTypes.lua @@ -31,6 +31,8 @@ local stateData = { [CMD_FIRE_TOWARDS_ENEMY] = 2, --[CMD_SELECTION_RANK] = 2, -- Handled entirely in luaUI so not included here. [CMD_UNIT_AI] = 2, + [CMD_IDLE_DODGE] = 2, + [CMD_MOVE_DODGE] = 3, } local specialHandling = { diff --git a/LuaUI/Widgets/unit_transport_ai.lua b/LuaUI/Widgets/unit_transport_ai.lua index be0ef955a9..41e8b562a7 100644 --- a/LuaUI/Widgets/unit_transport_ai.lua +++ b/LuaUI/Widgets/unit_transport_ai.lua @@ -199,6 +199,8 @@ local ignoredCommand = { [CMD_FIRE_AT_SHIELD] = true, [CMD_FIRE_TOWARDS_ENEMY] = true, [CMD_SELECTION_RANK] = true, + [CMD_IDLE_DODGE] = true, + [CMD_MOVE_DODGE] = true, } local function ProcessCommand(unitID, cmdID, params, noUsefuless, noPosition) From b08861bdde9b8bccb9e49ea8f0b52901aa4f6c85 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Mon, 16 May 2022 18:06:30 -0700 Subject: [PATCH 2/9] track hover skirm projectile --- LuaRules/Configs/projectile_dodge_defs.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/LuaRules/Configs/projectile_dodge_defs.lua b/LuaRules/Configs/projectile_dodge_defs.lua index 55190b1c53..b51f9bcfc4 100644 --- a/LuaRules/Configs/projectile_dodge_defs.lua +++ b/LuaRules/Configs/projectile_dodge_defs.lua @@ -12,6 +12,7 @@ local TRACKED_WEAPONS = { gunshipassault_vtol_salvo = {}, gunshipheavyskirm_emg = {}, hoverarty_ata = {}, + hoverskirm_missile = {}, jumparty_napalm_sprayer = {}, jumpblackhole_black_hole = {}, napalmmissile_weapon = {}, From 64ad4b210a25de2dc35fdc0714895aca7451fac0 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Mon, 16 May 2022 18:10:18 -0700 Subject: [PATCH 3/9] linear projectile eta 1 --- LuaRules/Utilities/projectile_targets.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LuaRules/Utilities/projectile_targets.lua b/LuaRules/Utilities/projectile_targets.lua index 79924cf72f..ab9554788c 100644 --- a/LuaRules/Utilities/projectile_targets.lua +++ b/LuaRules/Utilities/projectile_targets.lua @@ -44,9 +44,9 @@ end local function GetLinear(projID, target, targetY, height, config) local pPos, pPosY = vector.New3(spGetProjectilePosition(projID)) local pVel, pVelY = vector.New3(spGetProjectileVelocity(projID)) - local eta = 40 - local b = vector.Add(pPos, vector.Mult(eta, pVel)) - return {pPos, pPosY, b, targetY}, eta + local lookAhead = 40 + local b = vector.Add(pPos, vector.Mult(lookAhead, pVel)) + return {pPos, pPosY, b, targetY}, 1 end local function GetTarget(projID, target, targetY, height, config) From 727bd7a337a759db60aa7dbfd2f9eb9f0bc2b820 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Mon, 16 May 2022 18:11:49 -0700 Subject: [PATCH 4/9] replace dodge move commands --- LuaRules/Gadgets/cmd_raw_move.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/LuaRules/Gadgets/cmd_raw_move.lua b/LuaRules/Gadgets/cmd_raw_move.lua index c7aca95836..72bf3abae0 100644 --- a/LuaRules/Gadgets/cmd_raw_move.lua +++ b/LuaRules/Gadgets/cmd_raw_move.lua @@ -160,7 +160,7 @@ local COMMON_STOP_RADIUS_ACTIVE_DIST_SQ = 120^2 -- Commands shorter than this do local CONSTRUCTOR_UPDATE_RATE = 30 local CONSTRUCTOR_TIMEOUT_RATE = 2 -local DODGE_UPDATE_RATE = 30 +local DODGE_UPDATE_RATE = 20 local STOPPING_HAX = not Spring.Utilities.IsCurrentVersionNewerThan(104, 271) @@ -280,9 +280,9 @@ local function RemoveProjectileDodge(unitID) end local function UnitProjectileDodge(unitID) - if rawMoveUnit[unitID] then + if rawMoveUnit[unitID] and dodgeMoveUnitByID[unitID] then local unitData = rawMoveUnit[unitID] - local dodgeX, dodgeZ, dodgeSize = GG.UnitDodge.Move(unitID, unitData.mx, unitData.mz) + local dodgeX, dodgeZ, dodgeSize = GG.UnitDodge.Move(unitID, unitData.mx, unitData.mz, DODGE_UPDATE_RATE) if dodgeSize > 5 then local cmdID, _, cmdTag, _, _, _, isDodge = spGetUnitCurrentCommand(unitID) if (cmdID == CMD_MOVE or cmdID == CMD_RAW_MOVE) and isDodge == -1 then @@ -301,7 +301,6 @@ local function AddProjectileDodge(unitID) dodgeMoveUnitCount = dodgeMoveUnitCount + 1 dodgeMoveUnit[dodgeMoveUnitCount] = unitID dodgeMoveUnitByID[unitID] = dodgeMoveUnitCount - UnitProjectileDodge(unitID) end end @@ -520,8 +519,11 @@ function gadget:CommandFallback(unitID, unitDefID, teamID, cmdID, cmdParams, cmd return false end local cmdUsed, cmdRemove = HandleRawMove(unitID, unitDefID, cmdParams) - if cmdUsed and not cmdRemove and cmdParams[4] ~= -1 then + if cmdUsed and not cmdRemove and cmdParams[4] ~= -2 then AddProjectileDodge(unitID) + if not cmdParams[4] or cmdParams[4] >= 0 then + UnitProjectileDodge(unitID) + end end return cmdUsed, cmdRemove end From 2d00b7e01fda3d7da0fd8aa8b0c254fd54aac556 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Mon, 16 May 2022 18:14:35 -0700 Subject: [PATCH 5/9] account for proj distance, tac ai integeration --- LuaRules/Gadgets/unit_dodge.lua | 109 +++++++++++++++----------- LuaRules/Gadgets/unit_tactical_ai.lua | 79 ++++++++++++++----- 2 files changed, 122 insertions(+), 66 deletions(-) diff --git a/LuaRules/Gadgets/unit_dodge.lua b/LuaRules/Gadgets/unit_dodge.lua index a39caccc70..b091089047 100644 --- a/LuaRules/Gadgets/unit_dodge.lua +++ b/LuaRules/Gadgets/unit_dodge.lua @@ -21,8 +21,8 @@ local CACHE_TIME = 15 local DODGE_HEIGHT = 50 local QUERY_RADIUS = 300 local SAFETY_RADIUS = 30 -local RAY_DISTANCE = 140 -local MAX_DISTANCE = 140 +local RAY_DISTANCE = 80 +local MAX_DISTANCE = 80000 local abs = math.abs local max = math.max @@ -40,7 +40,6 @@ local spGetGameFrame = Spring.GetGameFrame local spGetUnitDefID = Spring.GetUnitDefID local spGetUnitStates = Spring.GetUnitStates local spGetUnitRadius = Spring.GetUnitRadius -local spGetUnitHeight = Spring.GetUnitHeight local spGetUnitIsDead = Spring.GetUnitIsDead local spGetUnitDirection = Spring.GetUnitDirection local spGetUnitPosition = Spring.GetUnitPosition @@ -114,25 +113,6 @@ local function GetHitData(obsticle) return hitData end -local function QueryHitData(unitID) - local query = {} - local currFrame = spGetGameFrame() - local allyTeam = spGetUnitAllyTeam(unitID) - local uPosX, _, uPosZ = spGetUnitPosition(unitID) - local obsticles = GG.MapObsticles(uPosX, uPosZ, QUERY_RADIUS, {"projectiles"}) - for i = 1, #obsticles do - local obsticle = obsticles[i] - local pPos, pPosY = vector.New3(spGetProjectilePosition(obsticle[3])) - if spIsPosInLos(pPos[1], pPosY, pPos[2], allyTeam) then - local hitData = GetHitData(obsticle) - if hitData.eta > currFrame then - query[#query+1] = GetHitData(obsticle) - end - end - end - return query -end - local function GetNearestPointToLine(point, a, b) local line = vector.Subtract(b, a) if line[1] ~= 0 or line[2] ~= 0 then @@ -148,25 +128,55 @@ local function GetNearestPointToLine(point, a, b) return vector.Clone(a) end +local function QueryHitData(unitID, uRadius, updateRate) + local query = {} + local currFrame = spGetGameFrame() + local allyTeam = spGetUnitAllyTeam(unitID) + local uPosX, _, uPosZ = spGetUnitPosition(unitID) + local obsticles = GG.MapObsticles(uPosX, uPosZ, QUERY_RADIUS, {"projectiles"}) + for i = 1, #obsticles do + local obsticle = obsticles[i] + local pPos, pPosY = vector.New3(spGetProjectilePosition(obsticle[3])) + if spIsPosInLos(pPos[1], pPosY, pPos[2], allyTeam) then + local hitData = GetHitData(obsticle) + local hitTime = hitData.eta - currFrame + if hitTime > 0 then + local uPos = {uPosX, uPosZ} + local unitDefID = spGetUnitDefID(unitID) + local line = hitData.line + local near = GetNearestPointToLine(uPos, line[1], line[3]) + local speed = UnitDefs[unitDefID].speed + local distance = vector.Distance(uPos, near) + hitData.radius + uRadius + SAFETY_RADIUS + local dodgeTime = distance / speed * updateRate + if dodgeTime > hitTime then + hitData.near = near + query[#query+1] = hitData + end + end + end + end + return query +end -local function BoidDodge(query, uPos, uRadius) +local function BoidDodge(query, uPos, uDir, uRadius) local dodge, dodgeMag = {0, 0}, 0 for i = 1, #query do local hitData = query[i] local line = hitData.line - local hitPoint = GetNearestPointToLine(uPos, line[1], line[3]) + local hitPoint = hitData.near if uPos[1] == hitPoint[1] and uPos[2] == hitPoint[2] then - hitPoint[1] = hitPoint[1] + 1 - hitPoint[2] = hitPoint[2] + 1 - end - local dodgeRadius = vector.Distance(uPos, hitPoint) - if dodgeRadius < hitData.radius + uRadius + SAFETY_RADIUS then - local hitNormal = vector.Norm(1, vector.Subtract(uPos, hitPoint)) - dodge = vector.Add(dodge, hitNormal) - dodgeMag = max(dodgeMag, hitData.radius + uRadius + SAFETY_RADIUS + SAFETY_RADIUS - dodgeRadius) + local lineDir = vector.Subtract(line[3], line[1]) + dodge = vector.Add(dodge, {-lineDir[2], lineDir[1]}) + dodgeMag = max(dodgeMag, hitData.radius + uRadius + SAFETY_RADIUS + SAFETY_RADIUS) + else + local dodgeRadius = vector.Distance(uPos, hitPoint) + if dodgeRadius < hitData.radius + uRadius + SAFETY_RADIUS then + local hitNormal = vector.Norm(1, vector.Subtract(uPos, hitPoint)) + dodge = vector.Add(dodge, hitNormal) + dodgeMag = max(dodgeMag, hitData.radius + uRadius + SAFETY_RADIUS + SAFETY_RADIUS - dodgeRadius) + end end end - dodgeMag = min(dodgeMag, MAX_DISTANCE) dodge = vector.Norm(dodgeMag, dodge) return dodge, dodgeMag end @@ -206,14 +216,19 @@ local function RayDodge(query, uPos, uDir, rDir, uRadius) end local dodgeRadius = uRadius + closestRadius + SAFETY_RADIUS + SAFETY_RADIUS local target = vector.Add(closestPoint, vector.Norm(dodgeRadius, closestDir)) - local toTarget = vector.Norm(RAY_DISTANCE, vector.Subtract(target, uPos)) - return uPos[1] + toTarget[1], uPos[2] + toTarget[2], RAY_DISTANCE + local origTarget = vector.Add(uPos, rDir) + if vector.Distance(uPos, target) < vector.Distance(uPos, origTarget) then + local toTarget = vector.Norm(RAY_DISTANCE, vector.Subtract(target, uPos)) + return uPos[1] + toTarget[1], uPos[2] + toTarget[2], RAY_DISTANCE + end end - return uPos[1] + rDir[1], uPos[2] + rDir[2], 0 + local length = sqrt(rDir[1]*rDir[1] + rDir[2]*rDir[2]) + local move = vector.Norm(length, rDir) + return uPos[1] + move[1], uPos[2] + move[2], 0 end -local function IdleDodge(unitID) +local function IdleDodge(unitID, updateRate) if not spValidUnitID(unitID) or spGetUnitIsDead(unitID) then return 0, 0, 0 end @@ -229,13 +244,17 @@ local function IdleDodge(unitID) return uPos[1], uPos[2], 0 end - local query = QueryHitData(unitID) + + local uRadius = spGetUnitRadius(unitID) - local dodge, dodgeMag = BoidDodge(query, uPos, uRadius) + local query = QueryHitData(unitID, uRadius, updateRate) + -- Spring.MarkerAddPoint(uPos[1], 0, uPos[2], "" .. #query) + local uDir = vector.New3(spGetUnitDirection(unitID)) + local dodge, dodgeMag = BoidDodge(query, uPos, uDir, uRadius) return uPos[1] + dodge[1], uPos[2] + dodge[2], dodgeMag end -local function MoveDodge(unitID, move_x, move_z) +local function MoveDodge(unitID, move_x, move_z, updateRate) if not spValidUnitID(unitID) or spGetUnitIsDead(unitID) then return move_x, move_z, 0 end @@ -246,17 +265,17 @@ local function MoveDodge(unitID, move_x, move_z) local moveDodge = moveStates[unitID][1] local moveState = spGetUnitStates(unitID).movestate - if (moveDodge == 1 and moveState == 0) or moveDodge == 2 then + if not force and ((moveDodge == 1 and moveState == 0) or moveDodge == 2) then return move_x, move_z, 0 end - local query = QueryHitData(unitID) - local uRadius = spGetUnitRadius(unitID) local uPos = vector.New3(spGetUnitPosition(unitID)) - local dodge, dodgeMag = BoidDodge(query, uPos, uRadius) + local uRadius = spGetUnitRadius(unitID) + local query = QueryHitData(unitID, uRadius, updateRate) + local uDir = vector.New3(spGetUnitDirection(unitID)) + local dodge, dodgeMag = BoidDodge(query, uPos, uDir, uRadius) if dodgeMag <= 0 and move_x then - local uDir = vector.New3(spGetUnitDirection(unitID)) local rDir = vector.Subtract({move_x, move_z}, uPos) return RayDodge(query, uPos, uDir, rDir, uRadius) end diff --git a/LuaRules/Gadgets/unit_tactical_ai.lua b/LuaRules/Gadgets/unit_tactical_ai.lua index 40652d7f95..dec13d5a15 100644 --- a/LuaRules/Gadgets/unit_tactical_ai.lua +++ b/LuaRules/Gadgets/unit_tactical_ai.lua @@ -443,7 +443,7 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) cx, cz = ClampPosition(cx, cz) if move then @@ -498,7 +498,7 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cy = ey cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true @@ -540,7 +540,7 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = uz-(-(uz-ez)*behaviour.jinkAwayParallelLength+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength)/pointDis end end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true @@ -704,7 +704,7 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty local cx = ux - wantedDis*ex/eDist local cy = uy local cz = uz - wantedDis*ez/eDist - cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true @@ -726,6 +726,33 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty return behaviour.skirmKeepOrder end +local function DoMoveDodge(unitID, behaviour, fx, fz, unitData, move, isIdleAttack, cmdID, cmdTag) + if (((cmdID == CMD_FIGHT) or move) and fz) then + local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, fx, fz, UPDATE_RATE) + if dodgeSize > 0 then + local cy = Spring.GetGroundHeight(cx, cz) + GG.recursion_GiveOrderToUnit = true + if cmdID then + if move then + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) + else + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + end + elseif isIdleAttack then + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) + else + cx, cy, cz = spGiveOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) + end + GG.recursion_GiveOrderToUnit = false + unitData.cx, unitData.cy, unitData.cz = cx, cy, cz + unitData.receivedOrder = true + return true + end + end + return false +end + local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typeKnown, move, isIdleAttack, cmdID, cmdTag, frame) local unitData = unit[unitID] local enemyRange = behaviour.minFleeRange @@ -778,7 +805,7 @@ local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typ local cx = ux+(ux-ex)*f local cy = uy local cz = uz+(uz-ez)*f - cx, cz = GG.UnitDodge.Move(unitID, cx, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true @@ -931,7 +958,7 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, if behaviour.fightOnlyUnits and behaviour.fightOnlyUnits[enemyUnitDef] and behaviour.fightOnlyOverride then behaviour = behaviour.fightOnlyOverride end - + if isIdleAttack and enemy and (not unitData.idleAgression or unitData.idleDodge) and typeKnown and ((behaviour.idleFleeCombat and armedUnitDefIDs[enemyUnitDef]) or (behaviour.idleFlee and behaviour.idleFlee[enemyUnitDef])) then local orderSent = DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typeKnown, move, isIdleAttack, cmdID, cmdTag, frame) @@ -958,6 +985,11 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, end if not enemy then + if DoMoveDodge(unitID, behaviour, fx, fz, unitData, move, isIdleAttack, cmdID, cmdTag) then + return true, true + else + ClearOrder(unitID, unitData, cmdID, cmdTag, cp_1, cp_2, cp_3) + end return false end @@ -1013,13 +1045,16 @@ local function IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) return true end -local function DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight, holdPos) - if (unitData.wasIdle) and not holdPos then - local cx, cz, dodgeSize = GG.UnitDodge.Idle(unitID) +local function DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight) + if (unitData.wasIdle) then + + local cx, cz, dodgeSize = GG.UnitDodge.Idle(unitID, UPDATE_RATE) if cx then + -- local ux, uy, uz = Spring.GetUnitPosition(unitID) + -- Spring.MarkerAddPoint(ux, uy, uz, "Dodge") cx, cz = ClampPosition(cx, cz) local ux, uy, uz = spGetUnitPosition(unitID) - --Spring.MarkerAddPoint(cx, 0, cz, "" .. dodgeSize) + -- Spring.MarkerAddPoint(cx, 0, cz, "" .. dodgeSize) if IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) then if move then spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, uy, cz, -1}, CMD.OPT_ALT ) @@ -1039,7 +1074,7 @@ local function DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight, holdPo unitData.idleWantReturn = true local canIdleReturn = true local rx, rz = unitData.queueReturnX or unitData.idleX, unitData.queueReturnZ or unitData.idleZ - local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, rx, rz) + local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, rx, rz, UPDATE_RATE) -- return to idle pos if safe if dodgeSize == 0 then -- Spring.MarkerAddPoint(cx, 0, cz, "moving") @@ -1125,13 +1160,6 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) Spring.Echo("queueReturnX queueReturnZ", unitData.queueReturnX, unitData.queueReturnZ, "setReturn", unitData.setReturn) end - local dodgeSentOrder = DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight, holdPos) - unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack or unitData.wasDodge) - if doDebug then - Spring.Echo("after", "idleWantReturn", unitData.idleWantReturn) - Spring.Utilities.UnitEcho(unitID, unitData.idleWantReturn and "W" or "O_O") - end - local aiTargetFound, aiSentOrder = false, false if (enemy) then -- if I am fighting/patroling ground, idle, or targeting an enemy local particularEnemy = ((enemy ~= -1) or autoAttackEnemyID) and true @@ -1153,15 +1181,15 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) enemyUnitDef, typeKnown = GetUnitVisibleInformation(enemy, unitData.allyTeam) end - if not (exitEarly or behaviour.onlyIdleHandling or dodgeSentOrder) then + if not (exitEarly or behaviour.onlyIdleHandling) then --Spring.Echo("cmdID", cmdID, cmdTag, move, math.random()) - aiTargetFound, sentAiOrder = DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, + aiTargetFound, aiSentOrder = DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, fightX, fightY, fightZ, unitData, behaviour, enemy, enemyUnitDef, typeKnown, move, haveFight, holdPos, unitData.idleWantReturn, particularEnemy, frame, alwaysJink) if autoAttackEnemyID and not aiTargetFound then enemyUnitDef, typeKnown = GetUnitVisibleInformation(autoAttackEnemyID, unitData.allyTeam) - aiTargetFound, sentAiOrder = DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, + aiTargetFound, aiSentOrder = DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, fightX, fightY, fightZ, unitData, behaviour, autoAttackEnemyID, enemyUnitDef, typeKnown, move, haveFight, holdPos, unitData.idleWantReturn, particularEnemy, frame, alwaysJink) end @@ -1182,6 +1210,15 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) DoAiLessIdleCheck(unitID, behaviour, unitData, frame, enemy, enemyUnitDef, typeKnown) end end + + if not aiSentOrder then + local dodgeSentOrder = DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight) + unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack or unitData.wasDodge) + if doDebug then + Spring.Echo("after", "idleWantReturn", unitData.idleWantReturn) + Spring.Utilities.UnitEcho(unitID, unitData.idleWantReturn and "W" or "O_O") + end + end if unitData.queueReturnX or ((not cmdID) and unitData.setReturn and unitData.idleX) then local rx, rz = unitData.queueReturnX or unitData.idleX, unitData.queueReturnZ or unitData.idleZ From 41e3e0e357ee403cf5ee85f6d2c35b767ea6ea5d Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Sat, 21 May 2022 21:47:01 -0700 Subject: [PATCH 6/9] only ground units can dodge --- LuaRules/Gadgets/unit_dodge.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/LuaRules/Gadgets/unit_dodge.lua b/LuaRules/Gadgets/unit_dodge.lua index b091089047..d18c51ebdb 100644 --- a/LuaRules/Gadgets/unit_dodge.lua +++ b/LuaRules/Gadgets/unit_dodge.lua @@ -288,7 +288,7 @@ local external = { Move = MoveDodge, } -local function CmdToggle(unitID, unitDefID, cmdID, cmdParams) +local function CmdToggle(unitID, cmdID, cmdParams) local cmdDescID, cmdDesc if cmdID == CMD_IDLE_DODGE then cmdDesc = idleDodgeCmdDesc @@ -308,10 +308,12 @@ local function CmdToggle(unitID, unitDefID, cmdID, cmdParams) end function gadget:UnitCreated(unitID, unitDefID, teamID) + if UnitDefs[unitDefID].isGroundUnit then spInsertUnitCmdDesc(unitID, idleDodgeCmdDesc) spInsertUnitCmdDesc(unitID, moveDodgeCmdDesc) - CmdToggle(unitID, unitDefID, CMD_IDLE_DODGE, {1}) - CmdToggle(unitID, unitDefID, CMD_MOVE_DODGE, {1}) + CmdToggle(unitID, CMD_IDLE_DODGE, {1}) + CmdToggle(unitID, CMD_MOVE_DODGE, {1}) + end end function gadget:UnitDestroyed(unitID) @@ -323,7 +325,7 @@ function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOpt if (cmdID ~= CMD_IDLE_DODGE and cmdID ~= CMD_MOVE_DODGE) then return true end - return CmdToggle(unitID, unitDefID, cmdID, cmdParams) + return CmdToggle(unitID, cmdID, cmdParams) end function gadget:ProjectileDestroyed(projID) From f8b4ac69a84297f4bbb57f6934f4748c17cea98e Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Sat, 21 May 2022 21:47:36 -0700 Subject: [PATCH 7/9] dodge state icons --- LuaUI/Configs/integral_menu_config.lua | 4 ++-- .../Images/commands/states/idle_dodge_always.png | Bin 0 -> 597 bytes LuaUI/Images/commands/states/idle_dodge_hold.png | Bin 0 -> 662 bytes LuaUI/Images/commands/states/idle_dodge_never.png | Bin 0 -> 767 bytes .../Images/commands/states/move_dodge_always.png | Bin 0 -> 778 bytes LuaUI/Images/commands/states/move_dodge_hold.png | Bin 0 -> 859 bytes LuaUI/Images/commands/states/move_dodge_never.png | Bin 0 -> 970 bytes 7 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 LuaUI/Images/commands/states/idle_dodge_always.png create mode 100644 LuaUI/Images/commands/states/idle_dodge_hold.png create mode 100644 LuaUI/Images/commands/states/idle_dodge_never.png create mode 100644 LuaUI/Images/commands/states/move_dodge_always.png create mode 100644 LuaUI/Images/commands/states/move_dodge_hold.png create mode 100644 LuaUI/Images/commands/states/move_dodge_never.png diff --git a/LuaUI/Configs/integral_menu_config.lua b/LuaUI/Configs/integral_menu_config.lua index 806df12671..fc25ec7722 100644 --- a/LuaUI/Configs/integral_menu_config.lua +++ b/LuaUI/Configs/integral_menu_config.lua @@ -307,7 +307,7 @@ local commandDisplayConfig = { } }, [CMD_IDLE_DODGE] = { - texture = {imageDir .. 'states/move_roam.png', imageDir .. 'states/move_engage.png', imageDir .. 'states/move_hold.png'}, + texture = {imageDir .. 'states/idle_dodge_always.png', imageDir .. 'states/idle_dodge_hold.png', imageDir .. 'states/idle_dodge_never.png'}, stateTooltip = { tooltips.IDLE_DODGE:gsub("_STATE_", "Always"), tooltips.IDLE_DODGE:gsub("_STATE_", "Not on Hold Pos"), @@ -315,7 +315,7 @@ local commandDisplayConfig = { }, }, [CMD_MOVE_DODGE] = { - texture = {imageDir .. 'states/move_roam.png', imageDir .. 'states/move_engage.png', imageDir .. 'states/move_hold.png'}, + texture = {imageDir .. 'states/move_dodge_always.png', imageDir .. 'states/move_dodge_hold.png', imageDir .. 'states/move_dodge_never.png'}, stateTooltip = { tooltips.MOVE_DODGE:gsub("_STATE_", "Always"), tooltips.MOVE_DODGE:gsub("_STATE_", "Not on Hold Pos"), diff --git a/LuaUI/Images/commands/states/idle_dodge_always.png b/LuaUI/Images/commands/states/idle_dodge_always.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b328d9d1fd091ba9369e6d5bb3d2f4e5852b29 GIT binary patch literal 597 zcmV-b0;>IqP)Px%4@pEpR9J=0moZNwQ4q&}yLycm8@!6d7DI!*2{}K*bw_&^eiyCmucqe{aJih+ z(ilt5#6&_1;i57Y@4vTi**$n$ znqI-x=0NKP;QyJLSD4KrP*>aMvcTE$bl5DOXQi=KwM8!_tuY=f(p_IU0`J*vI&zs45Gg?;-BkHgWDJ3x6?28z)bL7>TnI zZ;iA>Vq7zE4;*am6~KWwpJHHwYbF4`Z9IUK;|7?-YNPsE{IP`q5{`b_!UaEwoHF21 z?395&M$RbU=hztqZb+PQfR-$HQ~Ckg^5BC>u+ePSGx>)+8#twWIKAVo;nc}B j{%}e%9j_|yIG(=(-6v5gmC_bq00000NkvXXu0mjf(8(Dg literal 0 HcmV?d00001 diff --git a/LuaUI/Images/commands/states/idle_dodge_hold.png b/LuaUI/Images/commands/states/idle_dodge_hold.png new file mode 100644 index 0000000000000000000000000000000000000000..8494cd6df1372482ff3b47cee2eee6e926468286 GIT binary patch literal 662 zcmV;H0%`q;P)Px%P)S5VR9J=0m$7OaK@f&Nov;HgR%A&U1zgG~1O$Enfm%0d(y4XuL&T+1+Lb%` z31l~E)EeSmAP5R+4N2i76SRfE*g`3GR$iYJN2mCt3><>F{pXw0-R}NdV1aN#5!e9M zG<5?Ejb)UzRv{y}3N(P4UYmOB66oo5ZY-Z4wE-v8f%m{mz(%(Q`df-}0`!4*#`5u> z8gN1xXaPH#8UbD41UNGm(Qy`d1MC9ZKvBncfVal-?Qf0Df;XKYP8d7k$_YnK*qTGP zrDLyDD^6%;g&(VU?1Z)xDhoV7MYW;M@w~!jBs$Cro>A{GsyFpd6jFoBz$f4-fUy*u za1H!0mKPbpUx8=F^27;aP5%O3#ot7d(zO(R8o}3rvJ=`F;BBA`tUKY_SPHCaeNBIu7y3Yi8v9y%M_E%{U+8-}NIz~O<)k#_B=xw17)tI*MX%+f;SVFKuLGCTtgWpW!@yf5N{;FZ>f1lVDx}tqBbft zu)Bl?0;6Xvqwo!!$yBllB@ z;1gq#5q?jn1|_(p$!0W}hH4-?z;ob0)338YBY50wsBxgRJF!_^xgnV)T&gAelW29O wG%BzZTfXvx$^U&co6*Q>4yQ93d9>sCZ^^ryDo&Ttpa1{>07*qoM6N<$f^yO)mjD0& literal 0 HcmV?d00001 diff --git a/LuaUI/Images/commands/states/idle_dodge_never.png b/LuaUI/Images/commands/states/idle_dodge_never.png new file mode 100644 index 0000000000000000000000000000000000000000..61079046c895e31d0efcefae8fc37d5ab507d73a GIT binary patch literal 767 zcmVPx%xk*GpR9J=0moZONK@^3*eJhY)=c0)aEFs(MN~a{mUqEAp6gGrpwJ^rSl5hy6 zi7^(s38ArAsA%{F#02dWnvIka+(f`m5+nqRxr4LAhTW$u>uu(}nLGEKd2i<4Ga3HE zBxQkKpv$_BfCEX#X=7zl0_TByKmjNMgEq7cYyg|UnxyK55|A_o%maDgMliH*Z7%5h z4pe~|N$>xOfTVt)42%OOz%Sqvun4S3`eA<8fcwA{FamU2>{Xy7>04?7k|u#yz;&Po zRDcCZUz(5}2Bv{Y;3lvOJe5>wnSi9%z+)f_ECaKWw$1}?5Mv0K115lD;EkjwO$pe_ z-`UtpNzc;&H;DHFc!v9h592S9iJbd^&$!Qyq>Qyo);?$f@Qkmd6MNhRZijm!iRPCL zd^rLiZ3Z0Ks3#IEWZ7buL&qd=8pG}O8ZaAy-%3H^)ITI)F>AoMVbv4Bo%0i*B55lC zAHZ#*z7`4eng2jY!ZvZmFy`wBScc2v&iO(RY#rDE4qF8Gc4+g~qy7cM$Qvg9KbL^F zz(+}UQ-Y;3Ngseaz#~cJ#0pEn#uwWO9$^$DWfNRgD_ZBuB@pl!MW8pqmDE9C-{=gCF5MEGnAiq#R@K^ShLJPOqXam>i3xBewcX}1Hh_b~WYng0PF(^4kFhD~ z_=>%(;grmXq+wt4gH$?-WkN|;&xdm6S0o>Ij&CT*b+# zxuD;Z_{!Pb-pEdUBmFREu~$Q}8s~u$PBcseA|fQws?~v8tO<)<3LXD1Nfu1%oG1+f xbWWBB!8&&_iHLOOYSw^AXD+7=h+Nq9{5SLaN1^vhqk;ec002ovPDHLkV1j`TR__1+ literal 0 HcmV?d00001 diff --git a/LuaUI/Images/commands/states/move_dodge_always.png b/LuaUI/Images/commands/states/move_dodge_always.png new file mode 100644 index 0000000000000000000000000000000000000000..b4fb1fca6c6b0238e658acbaee81b365fa10e471 GIT binary patch literal 778 zcmV+l1NHogP)Px%#7RU!R9J=0m%nRMQ545NH@4uff~JCy(lw<;>ZBAkD&pemW(FsR?ymj^?bN+< z_pUlrq>b3Af{1QKD7G|$LQ1qvz7F@i=Dxfi$*V&=kH<^y`SCsX+;h*l*KrmS=YWgA zIB*p>0-8Af6gB0{V9Wj=0$av;mI6ErJOJi_o4~Yjz5zSHN8l}v&ndcV_UjIC%`h%S z%ogIr-rYbr;@1!xhbY1p7=o`@m}I=_K;Wb! zRnwK(TFJ!gQ>>dp2eDKbIB9RmY@Ng+ks(|O!Y&(Y#n)?S;N8m}$xEGJTwqJ&!u zz@x0q2wAF~wM4e}mSa6jsTL$$wNEMGfTX*U+LGpS90(^<4HC1K(82HBE@C;wSYq~) z?n(M7sUvA2!-@WSvTPU|{lc^O0@5C8tMwfCB~MLBw+1UQV;KARs(Q*;w9qt$0XGL0 zECOBiD7hR-CmM#e2r!0kEc3=*%>lduyac8I0oQ>GME%5%eUfzBIk%nT!YZ%;%zJDx zf!H*!%2`^HrX+pUU}~o3L@xqm!`ehll<+Ne8sAQTkZVH8ThF}|Nq<}HEAUe4jmzHfj>IZ|a{(lC!p_|wdev7hDudPIJBH0!w_#WZB{aRO-p)}G1D_NBQG?Ovnqf^w>Uv4fHC8b?w$^ZZW07*qo IM6N<$f`SWcQ2+n{ literal 0 HcmV?d00001 diff --git a/LuaUI/Images/commands/states/move_dodge_hold.png b/LuaUI/Images/commands/states/move_dodge_hold.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f3ef19c8e125adf356237e049d18937a9040e9 GIT binary patch literal 859 zcmV-h1ElPx&6-h)vR9J=0m(NR8K@`V7=b455xaK7Yp43t?1VJ#OLNMACv}-4{D*OXl<~D+$ zR_^pK&^9e>6$rUd5(J@)A_#&aDkt7RkIZMM#XYx%`|f=|DtsNdJeWD(^Zm}7nRCtv ze_-Cx1snvr@Al9qP_4n%ogTio8%x?G9s(wS5&KTs)LUS~zNIMU#Xl9`9pk`l;1m!C zQx*H|4aOX>3EYZep8S;n?$D)`6-k~=91sD#`L@9%tHS&dkd_Pe3 zj`=po=YcY?-#fOW7y%Y6_H5`|3P9r|r~MN!ZNm?XNL2Nls(w?|Q%&@wF{Ul{W#g(- zy=?n*Kf9W|)vM}xRjsJ%uBx8NiC2fS+o9gEoOK@ZjxFz~wQ|)00bUc^Qq{3$7`vi1 z;~IkBTQEVA;nt&=m#qi@YJl1o15cZ6hs9nu{|SJ7R#-;NXRZjWsz-sFK);b52YUE@ zlN5Nas;5O{t$~g?URSK8dR^3FtR-Vqzus&^bI0o^23 zt&-;UA`Q^A+@{;HQY6ASD#qEJ%>#dH>2|D$$Z9Kq60k#3&wCqff%zV8FrQ|-$H0Xm zC*$Zf&YcoaC21reDXB&F0k{YZlk^4GfUg0$0BgV{5&702PRg+XR7+9JZXn=#5lTdA zBJx^9o{Pu};0^F2od(uK>*$UxRST>9Nd&~Y}WOS-Z36@KLY{zuf`8Vk`;kE*OUeU_M|QE_`~G?ewyV7>CEFa lN64?|xee_ARjVs)*-x42S}6*jwu=A&002ovPDHLkV1ll|k<|bI literal 0 HcmV?d00001 diff --git a/LuaUI/Images/commands/states/move_dodge_never.png b/LuaUI/Images/commands/states/move_dodge_never.png new file mode 100644 index 0000000000000000000000000000000000000000..d10369c3eaa805a0e109987967324e7dbab50e7a GIT binary patch literal 970 zcmV;*12z1KP)Px&gh@m}R9J=0mp^D#NfgF^_a;&NQ>gI|DsCBxO0p?dBqD;?h}hVeO%c!#2o@sB zmcdW76P9HQ3B)uOF_n!77Ox2p5;29EWqByV!iq*Tu`{SiycTmNPR93c-c#Xunt5-| zne%-!cjlWj3EyCn8i2JxQ?Xdwm}S{qnx?abLg8brxsn=zTYi;3M!57zS=jdTVi;fRjKk z&;hKn+;@O}NiS4RBu4NYw-+{SBN28h}yY zlBB13z-4l@0GEL-U=g@3>D-D0Z00x3_ot*kY5^{j?;3Cc_X&IAl}MuC9l#^pvLh+6 z_LjB3R{{7{tfb6-z5#v>_e2_vA2jgM2)uJ8;K({XkzgXDmU}P^OoO09xZOStT#CSd ziAhL0DCxbVJu$eC4@p?gY2bumEhm6G=NT{}>1hDI6Ss*rM?eYi0r*GKfuMi0#qSJB z*d`t^j4R6sn8LN<&iPP~Y#exDK;AZf3pb(IE4dLro|aKJE2jdroLS3A6FWM8ad5m^oZE8t&0kdA)E116v z^2~pZiAp34W7aU6VjcKi^Q$l+>3%f>QeYl;pEujBmKVD|$M4my034|lWEjncIiCV^ zxMBj_m0HPO0Y`y-z#gCnco%>tz$9=?()*k|9^)}E*C=UG(v)@f0yio!UjUv3|C@nN z_BR10CGF1v_SS3dl%z$k17o;KrbE*1KnuK-c4A6c;hYzMW9Ij(GJ^?`*A%N2It*(J zz9?Y{S4OSE6%(O(IbI$0r?{gzfz$Vez_;4Q#Fto~VU&E`7@mRw+){R1G2xkeIc~67 zdHWAnsB}i(e8ys50OP^zTX0oUVpvPFzygZ68g4tTlJWhX#Qy}^F6p_Xt9)%sv5}l# zw%m6V6sxfg^y7+#uAqnrNmOggz%5so3MEl^T3>>#aeTH29DgC9#gBD!2kdN07*qoM6N<$g4N}*%>V!Z literal 0 HcmV?d00001 From bd9c365723e54169bf8f96f4355fe161f2f84f85 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Sat, 21 May 2022 21:48:13 -0700 Subject: [PATCH 8/9] dodge cleanup --- LuaRules/Gadgets/cmd_raw_move.lua | 19 ++++++++----------- LuaRules/Gadgets/unit_dodge.lua | 26 ++++++-------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/LuaRules/Gadgets/cmd_raw_move.lua b/LuaRules/Gadgets/cmd_raw_move.lua index 72bf3abae0..1d9e47b389 100644 --- a/LuaRules/Gadgets/cmd_raw_move.lua +++ b/LuaRules/Gadgets/cmd_raw_move.lua @@ -268,6 +268,14 @@ end ---------------------------------------------------------------------------------------------- -- Dodge Move Handling +local function AddProjectileDodge(unitID) + if unitID and dodgeMoveUnitByID[unitID] == nil then + dodgeMoveUnitCount = dodgeMoveUnitCount + 1 + dodgeMoveUnit[dodgeMoveUnitCount] = unitID + dodgeMoveUnitByID[unitID] = dodgeMoveUnitCount + end +end + local function RemoveProjectileDodge(unitID) if unitID and dodgeMoveUnitByID[unitID] then local index = dodgeMoveUnitByID[unitID] @@ -296,14 +304,6 @@ local function UnitProjectileDodge(unitID) end end -local function AddProjectileDodge(unitID) - if unitID and dodgeMoveUnitByID[unitID] == nil then - dodgeMoveUnitCount = dodgeMoveUnitCount + 1 - dodgeMoveUnit[dodgeMoveUnitCount] = unitID - dodgeMoveUnitByID[unitID] = dodgeMoveUnitCount - end -end - local function UpdateProjectileDodge(frame) if frame % DODGE_UPDATE_RATE == 0 then for i = dodgeMoveUnitCount, 1, -1 do @@ -521,9 +521,6 @@ function gadget:CommandFallback(unitID, unitDefID, teamID, cmdID, cmdParams, cmd local cmdUsed, cmdRemove = HandleRawMove(unitID, unitDefID, cmdParams) if cmdUsed and not cmdRemove and cmdParams[4] ~= -2 then AddProjectileDodge(unitID) - if not cmdParams[4] or cmdParams[4] >= 0 then - UnitProjectileDodge(unitID) - end end return cmdUsed, cmdRemove end diff --git a/LuaRules/Gadgets/unit_dodge.lua b/LuaRules/Gadgets/unit_dodge.lua index d18c51ebdb..975022d7b0 100644 --- a/LuaRules/Gadgets/unit_dodge.lua +++ b/LuaRules/Gadgets/unit_dodge.lua @@ -22,18 +22,14 @@ local DODGE_HEIGHT = 50 local QUERY_RADIUS = 300 local SAFETY_RADIUS = 30 local RAY_DISTANCE = 80 -local MAX_DISTANCE = 80000 +local MIN_DISTANCE = 80 local abs = math.abs local max = math.max -local min = math.min -local cos = math.cos local sin = math.sin -local acos = math.acos local sqrt = math.sqrt local vector = Spring.Utilities.Vector - local spIsPosInLos = Spring.IsPosInLos local spValidUnitID = Spring.ValidUnitID local spGetGameFrame = Spring.GetGameFrame @@ -47,20 +43,7 @@ local spGetUnitAllyTeam = Spring.GetUnitAllyTeam local spFindUnitCmdDesc = Spring.FindUnitCmdDesc local spEditUnitCmdDesc = Spring.EditUnitCmdDesc local spInsertUnitCmdDesc = Spring.InsertUnitCmdDesc -local spGetProjectileDefID = Spring.GetProjectileDefID -local spGetProjectileGravity = Spring.GetProjectileGravity local spGetProjectilePosition = Spring.GetProjectilePosition -local spGetProjectileVelocity = Spring.GetProjectileVelocity - -local markerX = 0 -local markerZ = 0 - -local function PlaceMarker(x, z, msg) - Spring.MarkerErasePosition(markerX, 0, markerZ) - markerX = x - markerZ = z - Spring.MarkerAddPoint(markerX, 0, markerZ, msg) -end local idleDodgeCmdDesc = { id = CMD_IDLE_DODGE, @@ -177,6 +160,9 @@ local function BoidDodge(query, uPos, uDir, uRadius) end end end + if dodgeMag > 0 then + dodgeMag = max(dodgeMag, MIN_DISTANCE) + end dodge = vector.Norm(dodgeMag, dodge) return dodge, dodgeMag end @@ -309,8 +295,8 @@ end function gadget:UnitCreated(unitID, unitDefID, teamID) if UnitDefs[unitDefID].isGroundUnit then - spInsertUnitCmdDesc(unitID, idleDodgeCmdDesc) - spInsertUnitCmdDesc(unitID, moveDodgeCmdDesc) + spInsertUnitCmdDesc(unitID, idleDodgeCmdDesc) + spInsertUnitCmdDesc(unitID, moveDodgeCmdDesc) CmdToggle(unitID, CMD_IDLE_DODGE, {1}) CmdToggle(unitID, CMD_MOVE_DODGE, {1}) end From 883fc55fa4baa6613b1826ac3d4d6a375ee3e349 Mon Sep 17 00:00:00 2001 From: PetTurtle Date: Sat, 21 May 2022 21:49:16 -0700 Subject: [PATCH 9/9] tactical ai handles dodge better --- LuaRules/Gadgets/unit_tactical_ai.lua | 324 +++++++++++--------------- 1 file changed, 140 insertions(+), 184 deletions(-) diff --git a/LuaRules/Gadgets/unit_tactical_ai.lua b/LuaRules/Gadgets/unit_tactical_ai.lua index dec13d5a15..c8f7615777 100644 --- a/LuaRules/Gadgets/unit_tactical_ai.lua +++ b/LuaRules/Gadgets/unit_tactical_ai.lua @@ -276,7 +276,6 @@ end local function ReturnUnitToIdlePos(unitID, unitData, force) unitData.queueReturnX = unitData.idleX unitData.queueReturnZ = unitData.idleZ - unitData.returnCommandPos = true unitData.setReturn = true unitData.forceReturn = force end @@ -406,6 +405,13 @@ local function UpdateJinkRotation(behaviour, unitData) end end +local function InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) + cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) + cx, cz = ClampPosition(cx, cz) + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + return cx, cy, cz +end + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ---- Unit AI Execution @@ -443,14 +449,11 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) - cx, cz = ClampPosition(cx, cz) - if move then - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end --Spring.SetUnitMoveGoal(unitID, cx, cy, cz) unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -498,15 +501,13 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cy = ey cz = ez+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength/pointDis end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) - cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData. cy, unitData.cz = cx, cy, cz @@ -540,15 +541,13 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty cz = uz-(-(uz-ez)*behaviour.jinkAwayParallelLength+(ux-ex)*unitData.jinkDir*behaviour.jinkTangentLength)/pointDis end end - cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) - cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -704,15 +703,13 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty local cx = ux - wantedDis*ex/eDist local cy = uy local cz = uz - wantedDis*ez/eDist - cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) - cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if move then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -726,33 +723,6 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty return behaviour.skirmKeepOrder end -local function DoMoveDodge(unitID, behaviour, fx, fz, unitData, move, isIdleAttack, cmdID, cmdTag) - if (((cmdID == CMD_FIGHT) or move) and fz) then - local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, fx, fz, UPDATE_RATE) - if dodgeSize > 0 then - local cy = Spring.GetGroundHeight(cx, cz) - GG.recursion_GiveOrderToUnit = true - if cmdID then - if move then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) - spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) - else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) - end - elseif isIdleAttack then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) - else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) - end - GG.recursion_GiveOrderToUnit = false - unitData.cx, unitData.cy, unitData.cz = cx, cy, cz - unitData.receivedOrder = true - return true - end - end - return false -end - local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typeKnown, move, isIdleAttack, cmdID, cmdTag, frame) local unitData = unit[unitID] local enemyRange = behaviour.minFleeRange @@ -805,22 +775,20 @@ local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typ local cx = ux+(ux-ex)*f local cy = uy local cz = uz+(uz-ez)*f - cx, cz = GG.UnitDodge.Move(unitID, cx, cz, UPDATE_RATE) - cx, cz = ClampPosition(cx, cz) GG.recursion_GiveOrderToUnit = true if cmdID then if move then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + cx, cy, cz = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end elseif isIdleAttack then - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) + cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz }, CMD_OPT_RIGHT ) else - cx, cy, cz = spGiveOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz, -1}, CMD_OPT_RIGHT ) + cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_FIGHT, {cx, cy, cz }, CMD_OPT_RIGHT ) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -855,94 +823,6 @@ local function DoAiLessIdleCheck(unitID, behaviour, unitData, frame, enemy, enem UpdateIdleAgressionState(unitID, behaviour, unitData, frame, enemy, typeKnown and enemyUnitDef, 250, pointDis, ux, uz, ex, ez) end --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- Idle Handling - -local function AddIdleUnit(unitID, unitDefID) - if not (unit[unitID] and spValidUnitID(unitID)) then - return - end - local unitData = unit[unitID] - unitData.wasIdle = true - - local doDebug = (debugUnit and debugUnit[unitID]) or debugAll - if doDebug then - Spring.Utilities.UnitEcho(unitID, "Idle " .. unitID) - Spring.Echo("=== Unit Idle", unitID, " ===") - end - - if unitData.wasDodge then - return - end - - if unitData.idleWantReturn and unitData.idleX then - if doDebug then - Spring.Echo("Return to idle position", unitData.idleX, unitData.idleZ) - end - ReturnUnitToIdlePos(unitID, unitData) - return - end - - local behaviour = GetUnitBehavior(unitID, unitData.udID) - local nearbyEnemy = spGetUnitNearestEnemy(unitID, behaviour.leashAgressRange, true) or false - local x, _, z = Spring.GetUnitPosition(unitID) - - unitData.idleX = x - unitData.idleZ = z - unitData.wantFightReturn = nil - unitData.idleWantReturn = nil - - if doDebug then - Spring.Echo("New Idle", unitData.idleX, unitData.idleZ, nearbyEnemy) - end - - if nearbyEnemy then - local enemyUnitDef, typeKnown = GetUnitVisibleInformation(nearbyEnemy, unitData.allyTeam) - if enemyUnitDef and typeKnown then - local enemyRange = GetEnemyRealRange(enemyUnitDef) - if enemyRange and enemyRange > 0 then - local enemyDist = spGetUnitSeparation(nearbyEnemy, unitID, true) - if enemyRange + behaviour.leashEnemyRangeLeeway < enemyDist then - nearbyEnemy = false -- Don't aggress against nearby enemy that cannot shoot. - end - end - end - end - - if doDebug then - Spring.Echo("After nearby check", nearbyEnemy) - end - - SetIdleAgression(unitID, unitData, nearbyEnemy) - --Spring.Utilities.UnitEcho(unitID, "I") -end - -function gadget:UnitIdle(unitID, unitDefID) - AddIdleUnit(unitID, unitDefID) -end - -function gadget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOpts, cmdTag, playerID, fromSynced, fromLua) - if playerID == -1 or fromLua then - return - end - if cmdID and stateCommands[cmdID] then - return - end - local unitData = unit[unitID] - if not unitData then - return - end - if (cmdID == CMD_FIGHT or cmdID == CMD_ATTACK) and unitData.receivedOrder and not cmdOpts.shift then - needNextUpdate = needNextUpdate or {} - needNextUpdate[#needNextUpdate + 1] = unitID - end - unitData.wasIdle = false - unitData.wasDodge = false - unitData.returnCommandPos = false - unitData.idleWantReturn = false -end - -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- ---- Unit AI Selection @@ -958,8 +838,8 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, if behaviour.fightOnlyUnits and behaviour.fightOnlyUnits[enemyUnitDef] and behaviour.fightOnlyOverride then behaviour = behaviour.fightOnlyOverride end - - if isIdleAttack and enemy and (not unitData.idleAgression or unitData.idleDodge) and typeKnown + + if isIdleAttack and enemy and (not unitData.idleAgression) and typeKnown and ((behaviour.idleFleeCombat and armedUnitDefIDs[enemyUnitDef]) or (behaviour.idleFlee and behaviour.idleFlee[enemyUnitDef])) then local orderSent = DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typeKnown, move, isIdleAttack, cmdID, cmdTag, frame) if not orderSent then @@ -985,11 +865,6 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, end if not enemy then - if DoMoveDodge(unitID, behaviour, fx, fz, unitData, move, isIdleAttack, cmdID, cmdTag) then - return true, true - else - ClearOrder(unitID, unitData, cmdID, cmdTag, cp_1, cp_2, cp_3) - end return false end @@ -1033,51 +908,108 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, return false end -local function IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) - if not (unitData.wasIdle and unitData.idleX) then +local function DoMoveDodge(unitID, fx, fz, unitData) + local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, fx, fz, UPDATE_RATE) + if dodgeSize > 1 then + cx, cz = ClampPosition(cx, cz) + local cy = Spring.GetGroundHeight(cx, cz) + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT) + unitData.cx, unitData.cy, unitData.cz = cx, cy, cz + unitData.receivedOrder = true return true end + return false +end - local dodgeDistSq = DistSq(unitData.idleX, unitData.idleZ, cx, cz) - if dodgeDistSq > ((dodgeSize + 16)*2)^2 then - return false +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Idle Handling + +local function AddIdleUnit(unitID, unitDefID) + if not (unit[unitID] and spValidUnitID(unitID)) then + return end - return true + local unitData = unit[unitID] + unitData.wasIdle = true + + local doDebug = (debugUnit and debugUnit[unitID]) or debugAll + if doDebug then + Spring.Utilities.UnitEcho(unitID, "Idle " .. unitID) + Spring.Echo("=== Unit Idle", unitID, " ===") + end + + if unitData.wasDodge then + return + end + + if unitData.idleWantReturn and unitData.idleX then + if doDebug then + Spring.Echo("Return to idle position", unitData.idleX, unitData.idleZ) + end + ReturnUnitToIdlePos(unitID, unitData) + return + end + + local behaviour = GetUnitBehavior(unitID, unitData.udID) + local nearbyEnemy = spGetUnitNearestEnemy(unitID, behaviour.leashAgressRange, true) or false + local x, _, z = Spring.GetUnitPosition(unitID) + + unitData.idleX = x + unitData.idleZ = z + unitData.wantFightReturn = nil + unitData.idleWantReturn = nil + + if doDebug then + Spring.Echo("New Idle", unitData.idleX, unitData.idleZ, nearbyEnemy) + end + + if nearbyEnemy then + local enemyUnitDef, typeKnown = GetUnitVisibleInformation(nearbyEnemy, unitData.allyTeam) + if enemyUnitDef and typeKnown then + local enemyRange = GetEnemyRealRange(enemyUnitDef) + if enemyRange and enemyRange > 0 then + local enemyDist = spGetUnitSeparation(nearbyEnemy, unitID, true) + if enemyRange + behaviour.leashEnemyRangeLeeway < enemyDist then + nearbyEnemy = false -- Don't aggress against nearby enemy that cannot shoot. + end + end + end + end + + if doDebug then + Spring.Echo("After nearby check", nearbyEnemy) + end + + SetIdleAgression(unitID, unitData, nearbyEnemy) + --Spring.Utilities.UnitEcho(unitID, "I") end -local function DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight) - if (unitData.wasIdle) then +function gadget:UnitIdle(unitID, unitDefID) + AddIdleUnit(unitID, unitDefID) +end +local function DoIdleProjDodge(unitID, cmdTag, unitData, move) local cx, cz, dodgeSize = GG.UnitDodge.Idle(unitID, UPDATE_RATE) - if cx then - -- local ux, uy, uz = Spring.GetUnitPosition(unitID) - -- Spring.MarkerAddPoint(ux, uy, uz, "Dodge") + if dodgeSize > 1 then cx, cz = ClampPosition(cx, cz) - local ux, uy, uz = spGetUnitPosition(unitID) - -- Spring.MarkerAddPoint(cx, 0, cz, "" .. dodgeSize) - if IsSmallEnoughDodgeOrNotIdle(unitID, unitData, cx, cz, dodgeSize) then - if move then - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, uy, cz, -1}, CMD.OPT_ALT ) - spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) - else - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, uy, cz, -1}, CMD.OPT_ALT ) - end - unitData.cx, unitData.cy, unitData.cz = cx, uy, cz - unitData.receivedOrder = true - unitData.wasDodge = true - return true + local cy = Spring.GetGroundHeight(cx, cz) + if move then + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) + spGiveOrderToUnit(unitID, CMD_REMOVE, {cmdTag}, 0 ) + else + spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz, -1}, CMD.OPT_ALT ) end + unitData.cx, unitData.cy, unitData.cz = cx, cy, cz + unitData.receivedOrder = true + unitData.wasDodge = true + return true end - end - if unitData.wasDodge and unitData.wasIdle then + if unitData.wasDodge then unitData.idleWantReturn = true - local canIdleReturn = true local rx, rz = unitData.queueReturnX or unitData.idleX, unitData.queueReturnZ or unitData.idleZ local cx, cz, dodgeSize = GG.UnitDodge.Move(unitID, rx, rz, UPDATE_RATE) - -- return to idle pos if safe - if dodgeSize == 0 then - -- Spring.MarkerAddPoint(cx, 0, cz, "moving") + if dodgeSize < 1 then unitData.wasDodge = false AddIdleUnit(unitID, unitData) end @@ -1159,7 +1091,13 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) Spring.Echo("wasIdle", unitData.wasIdle, "isIdleAttack", isIdleAttack, "idleWantReturn", unitData.idleWantReturn) Spring.Echo("queueReturnX queueReturnZ", unitData.queueReturnX, unitData.queueReturnZ, "setReturn", unitData.setReturn) end - + + unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack) + if doDebug then + Spring.Echo("after", "idleWantReturn", unitData.idleWantReturn) + Spring.Utilities.UnitEcho(unitID, unitData.idleWantReturn and "W" or "O_O") + end + local aiTargetFound, aiSentOrder = false, false if (enemy) then -- if I am fighting/patroling ground, idle, or targeting an enemy local particularEnemy = ((enemy ~= -1) or autoAttackEnemyID) and true @@ -1199,7 +1137,7 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) Spring.Echo("sentAiOrder", aiSentOrder, autoCmdTag, autoAttackEnemyID, enemy) end - if not aiSentOrder and autoCmdTag and (autoAttackEnemyID or -1) >= 0 and (enemy or -1) >= 0 and autoAttackEnemyID ~= enemy then + if autoCmdTag and (autoAttackEnemyID or -1) >= 0 and (enemy or -1) >= 0 and autoAttackEnemyID ~= enemy then -- Removes a fight/autotarget-issued attack command if there is a closer enemy. -- Prevents chasing past enemies that are good targets. -- Only do this if no order was sent, so swarming may be less affected, which seems fine. @@ -1212,11 +1150,10 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) end if not aiSentOrder then - local dodgeSentOrder = DoIdleProjDodge(unitID, cmdTag, unitData, move, haveFight) - unitData.idleWantReturn = unitData.wasIdle and ((unitData.idleWantReturn and (enemy == -1 or move) and not haveFight) or isIdleAttack or unitData.wasDodge) - if doDebug then - Spring.Echo("after", "idleWantReturn", unitData.idleWantReturn) - Spring.Utilities.UnitEcho(unitID, unitData.idleWantReturn and "W" or "O_O") + if fightZ then + DoMoveDodge(unitID, fightX, fightZ, unitData) + elseif unitData.wasIdle then + DoIdleProjDodge(unitID, cmdTag, unitData, move) end end @@ -1235,7 +1172,6 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) else -- If the command queue is empty (ie still idle) then return to position local ry = math.max(0, Spring.GetGroundHeight(rx, rz) or 0) - --Spring.MarkerAddPoint(rx, 0, rz, "returning") GiveClampedOrderToUnit(unitID, unitData.wantFightReturn and CMD_FIGHT or CMD_RAW_MOVE, {rx, ry, rz}, CMD.OPT_ALT ) unitData.setReturn = nil unitData.forceReturn = nil @@ -1284,6 +1220,26 @@ function gadget:GameFrame(n) UpdateUnits(n, n%UPDATE_RATE + 1, UPDATE_RATE) end +function gadget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOpts, cmdTag, playerID, fromSynced, fromLua) + if playerID == -1 or fromLua then + return + end + if cmdID and stateCommands[cmdID] then + return + end + local unitData = unit[unitID] + if not unitData then + return + end + if (cmdID == CMD_FIGHT or cmdID == CMD_ATTACK) and unitData.receivedOrder and not cmdOpts.shift then + needNextUpdate = needNextUpdate or {} + needNextUpdate[#needNextUpdate + 1] = unitID + end + unitData.wasIdle = false + unitData.wasDodge = false + unitData.idleWantReturn = false +end + -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Command Handling