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..b51f9bcfc4 --- /dev/null +++ b/LuaRules/Configs/projectile_dodge_defs.lua @@ -0,0 +1,64 @@ +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 = {}, + hoverskirm_missile = {}, + 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..1d9e47b389 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 = 20 + 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,61 @@ local function ResetUnitData(unitData) unitData.possiblyTurning = nil 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] + 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] and dodgeMoveUnitByID[unitID] then + local unitData = rawMoveUnit[unitID] + 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 + 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 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 +352,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 +519,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] ~= -2 then + AddProjectileDodge(unitID) + end return cmdUsed, cmdRemove end @@ -800,6 +863,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 +881,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..975022d7b0 --- /dev/null +++ b/LuaRules/Gadgets/unit_dodge.lua @@ -0,0 +1,376 @@ +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 = 80 +local MIN_DISTANCE = 80 + +local abs = math.abs +local max = math.max +local sin = math.sin +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 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 spGetProjectilePosition = Spring.GetProjectilePosition + +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 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 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, uDir, uRadius) + local dodge, dodgeMag = {0, 0}, 0 + for i = 1, #query do + local hitData = query[i] + local line = hitData.line + local hitPoint = hitData.near + if uPos[1] == hitPoint[1] and uPos[2] == hitPoint[2] then + 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 + if dodgeMag > 0 then + dodgeMag = max(dodgeMag, MIN_DISTANCE) + end + 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 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 + + 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, updateRate) + 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 uRadius = spGetUnitRadius(unitID) + 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, updateRate) + 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 not force and ((moveDodge == 1 and moveState == 0) or moveDodge == 2) then + return move_x, move_z, 0 + end + + local uPos = vector.New3(spGetUnitPosition(unitID)) + 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 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, 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) + if UnitDefs[unitDefID].isGroundUnit then + spInsertUnitCmdDesc(unitID, idleDodgeCmdDesc) + spInsertUnitCmdDesc(unitID, moveDodgeCmdDesc) + CmdToggle(unitID, CMD_IDLE_DODGE, {1}) + CmdToggle(unitID, CMD_MOVE_DODGE, {1}) + end +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, 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..c8f7615777 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 @@ -404,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 @@ -442,10 +450,10 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty end if move then - spGiveOrderToUnit(unitID, CMD_INSERT, {0, CMD_RAW_MOVE, CMD_OPT_INTERNAL, cx, cy, cz }, 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 }, 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 @@ -496,10 +504,10 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData. cy, unitData.cz = cx, cy, cz @@ -536,10 +544,10 @@ local function DoSwarmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -698,10 +706,10 @@ local function DoSkirmEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, ty 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end GG.recursion_GiveOrderToUnit = false unitData.cx, unitData.cy, unitData.cz = cx, cy, cz @@ -772,10 +780,10 @@ local function DoFleeEnemy(unitID, behaviour, unitData, enemy, enemyUnitDef, typ 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) 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 = InsertMoveDodgeOrderToUnit(unitID, cx, cy, cz) end elseif isIdleAttack then cx, cy, cz = GiveClampedOrderToUnit(unitID, CMD_RAW_MOVE, {cx, cy, cz }, CMD_OPT_RIGHT ) @@ -900,6 +908,116 @@ local function DoTacticalAI(unitID, cmdID, cmdOpts, cmdTag, cp_1, cp_2, cp_3, return false end +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 + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- 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 + +local function DoIdleProjDodge(unitID, cmdTag, unitData, move) + local cx, cz, dodgeSize = GG.UnitDodge.Idle(unitID, UPDATE_RATE) + if dodgeSize > 1 then + cx, cz = ClampPosition(cx, cz) + 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 + + if unitData.wasDodge then + unitData.idleWantReturn = 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) + if dodgeSize < 1 then + unitData.wasDodge = false + AddIdleUnit(unitID, unitData) + end + end + + return false +end + local function DoUnitUpdate(unitID, frame, slowUpdate) local unitData = unit[unitID] @@ -1003,13 +1121,13 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) 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 @@ -1019,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. @@ -1030,6 +1148,14 @@ local function DoUnitUpdate(unitID, frame, slowUpdate) DoAiLessIdleCheck(unitID, behaviour, unitData, frame, enemy, enemyUnitDef, typeKnown) end end + + if not aiSentOrder then + if fightZ then + DoMoveDodge(unitID, fightX, fightZ, unitData) + elseif unitData.wasIdle then + DoIdleProjDodge(unitID, cmdTag, unitData, move) + 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 @@ -1094,69 +1220,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 @@ -1173,6 +1236,7 @@ function gadget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOp needNextUpdate[#needNextUpdate + 1] = unitID end unitData.wasIdle = false + unitData.wasDodge = false unitData.idleWantReturn = false end diff --git a/LuaRules/Utilities/projectile_targets.lua b/LuaRules/Utilities/projectile_targets.lua new file mode 100644 index 0000000000..ab9554788c --- /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 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) + 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..fc25ec7722 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/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"), + tooltips.IDLE_DODGE:gsub("_STATE_", "Never") + }, + }, + [CMD_MOVE_DODGE] = { + 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"), + 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/Images/commands/states/idle_dodge_always.png b/LuaUI/Images/commands/states/idle_dodge_always.png new file mode 100644 index 0000000000..d7b328d9d1 Binary files /dev/null and b/LuaUI/Images/commands/states/idle_dodge_always.png differ 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 0000000000..8494cd6df1 Binary files /dev/null and b/LuaUI/Images/commands/states/idle_dodge_hold.png differ 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 0000000000..61079046c8 Binary files /dev/null and b/LuaUI/Images/commands/states/idle_dodge_never.png differ 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 0000000000..b4fb1fca6c Binary files /dev/null and b/LuaUI/Images/commands/states/move_dodge_always.png differ 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 0000000000..e1f3ef19c8 Binary files /dev/null and b/LuaUI/Images/commands/states/move_dodge_hold.png differ 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 0000000000..d10369c3ea Binary files /dev/null and b/LuaUI/Images/commands/states/move_dodge_never.png differ 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)