Skip to content

Visualize links + link checks #449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: dev
Choose a base branch
from
185 changes: 185 additions & 0 deletions lua/acf/core/utilities/util_cl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -935,4 +935,189 @@ do -- Default turret menus
Menu:AddLabel(MassText:format(Data.Mass))
end
end
end

do -- Link distance gizmo stuff
local EntGizmoDifferences = {}

local ColorLinkOk = Color(55, 235, 55, 255)
local ColorLinkFail = Color(255, 88, 88)
local ColorLinkFailDistMissed = Color(255, 200, 81)
local ColorLink = Color(205, 235, 255, 255)

function ACF.ToolCL_RegisterLinkGizmoData(From, To, Callback)
EntGizmoDifferences[From] = EntGizmoDifferences[From] or {}
EntGizmoDifferences[To] = EntGizmoDifferences[To] or {}

EntGizmoDifferences[From][To] = Callback
EntGizmoDifferences[To][From] = Callback
end

function ACF.ToolCL_GetLinkGizmoData(EntFrom, EntTo)
local FromTbl = EntGizmoDifferences[EntFrom:GetClass()]
if not FromTbl then return end

local ToTbl = FromTbl[EntTo:GetClass()]
if not ToTbl then return end

return true, ToTbl(EntFrom, EntTo)
end

function ACF.ToolCL_CanLink(From, To)
if not IsValid(From) then return false, "Link target not valid!" end
if not IsValid(To) then return false, "Target not valid!" end

if From == To then return false, "Cannot link an entity to itself!" end

local HadData, CanLink, WhyNot, RenderData = ACF.ToolCL_GetLinkGizmoData(From, To)
if not HadData then return false, "No link data." end
return CanLink == nil and true or CanLink, WhyNot, RenderData
end

local LinkDistanceTooFar = {
Text = "The entity is too far away.",
Renderer = function(Data)
local FromPos, ToPos = Data.FromPos, Data.ToPos
local Normal = (ToPos - FromPos):GetNormalized()
local ToMaxDist = FromPos + (Normal * Data.MaxDist)

render.SetColorMaterial()
render.DepthRange(0, 0)
render.DrawBeam(FromPos, ToMaxDist, 2, 0, 1, color_black)
render.DrawBeam(ToMaxDist, ToPos, 2, 0, 1, color_black)
render.DrawBeam(FromPos, ToMaxDist, 1, 0, 1, ColorLinkFailDistMissed)
render.DrawBeam(ToMaxDist, ToPos, 1, 0, 1, ColorLinkFail)
render.DepthRange(0, 1)
end
}

local function GenericLinkDistanceCheck(From, To)
local FromPos, ToPos = From:GetPos(), To:GetPos()
local Dist = FromPos:Distance(ToPos)
local MaxDist = ACF.LinkDistance
if Dist > MaxDist then return false, LinkDistanceTooFar, {FromPos = FromPos, ToPos = ToPos, Dist = Dist, MaxDist = MaxDist} end
end

local function MobilityLinkDistanceCheck(From, To)
local FromPos, ToPos = From:GetPos(), To:GetPos()
local Dist = FromPos:Distance(ToPos)
local MaxDist = ACF.MobilityLinkDistance
if Dist > MaxDist then return false, LinkDistanceTooFar, {FromPos = FromPos, ToPos = ToPos, Dist = Dist, MaxDist = MaxDist} end
end

local function AlwaysLinkableCheck()
return true
end

ACF.ToolCL_RegisterLinkGizmoData("acf_ammo", "acf_gun", GenericLinkDistanceCheck)
ACF.ToolCL_RegisterLinkGizmoData("acf_ammo", "acf_rack", GenericLinkDistanceCheck)
ACF.ToolCL_RegisterLinkGizmoData("acf_turret", "acf_turret_motor", GenericLinkDistanceCheck) -- TODO: Make this use the actual link distance check used in turrets
ACF.ToolCL_RegisterLinkGizmoData("acf_turret", "acf_turret_gyro", GenericLinkDistanceCheck)

ACF.ToolCL_RegisterLinkGizmoData("acf_engine", "acf_gearbox", function(From, To)
--[[
local Out = From.Out

if From:GetClass() == "acf_gearbox" then
local InPos = To.In and To.In.Pos or Vector()
local InPosWorld = To:LocalToWorld(InPos)

Out = From:WorldToLocal(InPosWorld).y < 0 and From.OutL or From.OutR
end

if ACF.IsDriveshaftAngleExcessive(To, To.In, From, Out) then
return false, { Text = "The driveshaft angle is excessive." }, {FromPos = From:GetPos(), ToPos = To:GetPos()}
end
]]
return MobilityLinkDistanceCheck(From, To)
end)

ACF.ToolCL_RegisterLinkGizmoData("acf_engine", "acf_fueltank", MobilityLinkDistanceCheck)

ACF.ToolCL_RegisterLinkGizmoData("acf_gun", "acf_turret_computer", AlwaysLinkableCheck)
ACF.ToolCL_RegisterLinkGizmoData("acf_gun", "acf_computer", AlwaysLinkableCheck)
ACF.ToolCL_RegisterLinkGizmoData("acf_rack", "acf_computer", AlwaysLinkableCheck)
ACF.ToolCL_RegisterLinkGizmoData("acf_rack", "acf_radar", AlwaysLinkableCheck)

local HUDText = {}

local function DrawText(Text, Color, X, Y)
if not Y then
local XY = X:ToScreen()
X, Y = XY.x, XY.y
end

HUDText[#HUDText + 1] = {Text = Text, X = X, Y = Y, Color = Color}
end

local DistText = "Distance: %.1f units"
local DistTextOK = "✓ OK"
local DistTextNo = "✗ Cannot link: %s"

hook.Add("PostDrawTranslucentRenderables", "ACF_PostDrawTranslucentRenderables_LinkDistanceVis", function()
if not ACF.ToolCL_InLinkState() then return end
table.Empty(HUDText)

local LocalPly = LocalPlayer()
local PlayerPos = LocalPly:GetPos()
local EyeTrace = LocalPly:GetEyeTrace()
local LookEnt = EyeTrace.Entity
local LookPos = EyeTrace.HitPos
local LookingAtEntity = IsValid(LookEnt)
local LinkEnts = ACF.ToolCL_GetLinkedEnts()

for Ent in pairs(LinkEnts) do
if IsValid(Ent) then
local TargPos = LookingAtEntity and LookEnt:GetPos() or LookPos
local EntPos = Ent:GetPos()

local Dist = EntPos:Distance(TargPos)
local PlayerToTarget = math.Clamp(PlayerPos:Distance(TargPos) / 1.5, 0, Dist / 2)
local InBetween = TargPos + ((EntPos - TargPos):GetNormalized() * math.Clamp(Dist, 0, PlayerToTarget))

local LinkColor = ColorLink
local RenderOverride, RenderData

if LookingAtEntity then
local CanLink, Why, Data = ACF.ToolCL_CanLink(Ent, LookEnt, Dist)
LinkColor = CanLink and ColorLinkOk or ColorLinkFail
local linkText = CanLink and DistTextOK or DistTextNo:format(Why.Text and Why.Text or Why)
if not CanLink then
RenderOverride = Why.Renderer
RenderData = Data
end
DrawText(linkText, LinkColor, InBetween)
else
DrawText(DistText:format(Dist), LinkColor, InBetween)
end

if RenderOverride then
RenderOverride(RenderData, From, To)
else
render.SetColorMaterial()
render.DepthRange(0, 0)
render.DrawBeam(EntPos, TargPos, 2, 0, 1, color_black)
render.DrawBeam(EntPos, TargPos, 1, 0, 1, LinkColor)
render.DepthRange(0, 1)
end
end
end
end)

hook.Add("HUDPaint", "ACF_HUDPaint_LinkDistanceVis", function()
if not ACF.ToolCL_InLinkState() then return end

local W, H = ScrW(), ScrH()
local Padding = 16

for _, V in ipairs(HUDText) do
surface.SetFont("ACF_Title")
local TX, TY = surface.GetTextSize(V.Text)
TX = TX / 2
TY = TY / 2
local X, Y = math.Clamp(V.X, TX + Padding, W - TX - Padding), math.Clamp(V.Y, TY + Padding, H - TY - Padding)

draw.SimpleTextOutlined(V.Text, "ACF_Title", X, Y, V.Color or color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 2, color_black)
end
end)
end
57 changes: 54 additions & 3 deletions lua/acf/menu/operations/acf_menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,51 @@ do -- Generic Spawner/Linker operation creator
local NameFormat = "%s[ID: %s]"
local PlayerEnts = {}

local InLinkState = false

if CLIENT then
net.Receive("ACF_MenuLinking", function()
local StateChange = net.ReadUInt(2)

if StateChange == 0 then -- Entering link state
InLinkState = true
table.Empty(PlayerEnts)
elseif StateChange == 1 then -- Exiting link state
InLinkState = false
table.Empty(PlayerEnts)
elseif StateChange == 2 then -- Adding entity to link table
PlayerEnts[net.ReadEntity()] = true
elseif StateChange == 3 then -- Removing entity from link table
PlayerEnts[net.ReadEntity()] = false
end
end)
end

local function UpdateLinkState(Player, State, Entity)
if CLIENT then return end

net.Start("ACF_MenuLinking")
net.WriteUInt(State, 2)

if State > 1 then
net.WriteEntity(Entity)
end

net.Send(Player)
end

function ACF.ToolCL_InLinkState()
return InLinkState
end

function ACF.ToolCL_GetLinkedEnts()
return PlayerEnts
end

if SERVER then
util.AddNetworkString("ACF_MenuLinking")
end

local function GetPlayerEnts(Player)
local Ents = PlayerEnts[Player]

Expand Down Expand Up @@ -86,6 +131,7 @@ do -- Generic Spawner/Linker operation creator
Entity:SetColor(EntColor)

Ents[Entity] = nil
UpdateLinkState(Player, 3, Entity)

if not next(Ents) then
Tool:SetMode("Spawner", Name)
Expand All @@ -100,9 +146,11 @@ do -- Generic Spawner/Linker operation creator

if not next(Ents) then
Tool:SetMode("Linker", Name)
UpdateLinkState(Player, 0)
end

Ents[Entity] = Entity:GetColor()
UpdateLinkState(Player, 2, Entity)

Entity:CallOnRemove("ACF_ToolLinking", UnselectEntity, Name, Tool)
Entity:SetColor(Green)
Expand Down Expand Up @@ -263,14 +311,15 @@ do -- Generic Spawner/Linker operation creator
if Trace.HitWorld then Tool:Holster() return true end

local Entity = Trace.Entity
local Player = Tool:GetOwner()

if not IsValid(Entity) then return false end
if not IsValid(Entity) then UpdateLinkState(Player, 1) return false end

local Player = Tool:GetOwner()
local Ents = GetPlayerEnts(Player)
local Ents = GetPlayerEnts(Player)

if not Player:KeyDown(IN_SPEED) then
LinkEntities(Player, Name, Tool, Entity, Ents)
UpdateLinkState(Player, 1)

return true
end
Expand All @@ -292,6 +341,8 @@ do -- Generic Spawner/Linker operation creator
for Entity in pairs(Ents) do
UnselectEntity(Entity, Name, Tool)
end

UpdateLinkState(Player, 1)
end,
})

Expand Down
Loading