myspace / Lua /Exploration.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
11.2 kB
if FirstLoad then
g_LastExploration = false -- For debug
end
MapVar("g_Exploration", false)
DefineClass.Exploration = {
__parents = { "InitDone" },
-- Threads
visibility_thread = false,
npc_custom_highlight_thread = false,
map_border_thread = false,
sus_thread = false,
npc_movement_thread = false,
-- Sus meshes
nearby_enemies = false,
hash_nearby_enemies = false,
fx_nearby_enemies = false,
}
function Exploration:Init()
assert(not g_Combat)
NetUpdateHash("Exploration_Init")
if #(g_Teams or "") == 0 then
SetupDummyTeams()
end
UpdateTeamDiplomacy()
Msg("ExplorationStart")
self.visibility_thread = CreateGameTimeThread(Exploration.VisibilityInvalidateThread, self)
self.npc_custom_highlight_thread = CreateGameTimeThread(Exploration.NPCCustomHighlightThread, self)
self.map_border_thread = CreateGameTimeThread(Exploration.UpdateMapBorderThread, self)
self.sus_thread = CreateGameTimeThread(Exploration.SusThread, self)
self.npc_movement_thread = CreateGameTimeThread(Exploration.NPCMovementThread, self)
-- sanity check
local team = GetPoVTeam()
local alive_player_units
for _, unit in ipairs(team.units) do
alive_player_units = alive_player_units or not unit:IsDead()
end
if not alive_player_units then return end
for _, unit in ipairs(g_Units) do
if not unit:IsDead() and unit:IsAware() and unit.team and unit.team:IsEnemySide(team) then
NetSyncEvent("ExplorationStartCombat")
return
end
end
end
function Exploration:Done()
if IsValidThread(self.visibility_thread) then DeleteThread(self.visibility_thread) end
if IsValidThread(self.npc_custom_highlight_thread) then
DeleteThread(self.npc_custom_highlight_thread)
HighlightCustomUnitInteractables("delete")
end
if IsValidThread(self.map_border_thread) then DeleteThread(self.map_border_thread) end
if IsValidThread(self.sus_thread) then
NetUpdateHash("Exploration:Done_killing_sus_thread")
DeleteThread(self.sus_thread)
self:UpdateSusVisualization(false)
end
if IsValidThread(self.npc_movement_thread) then DeleteThread(self.npc_movement_thread) end
end
------------
-- Threads
------------
function Exploration:VisibilityInvalidateThread()
local last_computed_visibility_msg_time = 0
while true do
assert(g_Exploration and not g_Combat)
VisibilityUpdate()
UpdateApproachBanters()
if GameTime() > last_computed_visibility_msg_time then
Msg("ExplorationComputedVisibility")
last_computed_visibility_msg_time = GameTime() + 1000
end
UpdateMarkerAreaEffects()
local timeBetweenTicks = 500
Sleep(timeBetweenTicks)
Msg("ExplorationTick", timeBetweenTicks)
ListCallReactions(g_Units, "OnExplorationTick")
end
end
function Exploration:NPCCustomHighlightThread()
while true do
assert(g_Exploration and not g_Combat)
HighlightCustomUnitInteractables()
Sleep(2000)
end
end
function Exploration:UpdateMapBorderThread()
while true do
assert(g_Exploration and not g_Combat)
if gv_CurrentSectorId then
local cursor_pos = GetCursorPos()
UpdateBorderAreaMarkerVisibility(cursor_pos)
end
Sleep(50)
end
end
function Exploration:SusThread()
self:UpdateSusVisualization(empty_table)
while true do
assert(g_Exploration and not g_Combat)
local pus = GetAllPlayerUnitsOnMap()
local unit = pus and pus[1]
NetUpdateHash("ExplorationSuspicionThread", GameState.sync_loading, unit or false, #(GetAllEnemyUnits(unit) or ""), HasAnyAttackActionInProgress())
if not GameState.sync_loading and unit and unit.team and not HasAnyAttackActionInProgress() then
local enemies = GetAllEnemyUnits(unit)
if #enemies > 0 then
local allies = GetAllAlliedUnits(unit)
allies = table.copy(allies)
allies[#allies + 1] = unit
local changes = UpdateSuspicion(allies, enemies, "intermediate") -- Enemies alerted by allies (incl merc)
allies = table.ifilter(allies, function(_, u) return u.team and not u.team.player_team end)
UpdateSuspicion(enemies, allies) -- Allies alerted by enemies
AlertPendingUnits("sync_code")
if changes then
self:UpdateSusVisualization(changes)
end
else
self:UpdateSusVisualization(false)
end
end
Sleep(35)
end
end
function Exploration:UpdateSusVisualization(data)
local playerMercs = GetAllPlayerUnitsOnMap()
if not data or IsSetpiecePlaying() then
for _, obj in ipairs(self.fx_nearby_enemies) do
DoneObject(obj)
end
self.hash_nearby_enemies = false
self.nearby_enemies = false
local change
for _, unit in pairs(playerMercs) do
change = change or unit.suspicion ~= 0
unit.suspicion = 0
end
if change then
UnitsSusBeingRaised = {}
ObjModified("UnitsSusBeingRaised")
end
return
end
self.nearby_enemies = data
self.hash_nearby_enemies = self.hash_nearby_enemies or {}
local drawnData = self.hash_nearby_enemies
if not self.fx_nearby_enemies then self.fx_nearby_enemies = {} end
-- Spawn detection mesh for units that having sus raised or are hidden.
-- If they are just hidden the mesh is shown without the arrow (mesh3)
local stillValid = {}
for i, ally in ipairs(playerMercs) do
local dataForAlly = table.find_value(data, "sees", ally)
local shouldHaveMesh = (ally.suspicion or 0) > 0 or ally:HasStatusEffect("Hidden") or dataForAlly
if not shouldHaveMesh then goto continue end
local hash = ally.handle
stillValid[hash] = true
-- Create new line
if not drawnData[hash] then
local newMesh = SpawnDetectionIndicator(ally)
drawnData[hash] = newMesh
self.fx_nearby_enemies[#self.fx_nearby_enemies + 1] = newMesh
end
local mesh = drawnData[hash]
mesh:SetProgress(MulDivRound(ally.suspicion or 0, 1000, SuspicionThreshold))
if dataForAlly then
mesh:SetGameFlags(const.gofLockedOrientation)
local unit = dataForAlly.unit
mesh:Face(unit:GetPos())
mesh:Rotate(axis_z, 90 * 60)
mesh.mesh3:SetVisible(true) -- The arrow
elseif mesh:GetGameFlags(const.gofLockedOrientation) ~= 0 then
mesh:ClearGameFlags(const.gofLockedOrientation)
mesh:SetAngle(90 * 60)
mesh.mesh3:SetVisible(false) -- The arrow
end
::continue::
end
local raisingSus = {}
for _, d in ipairs(data) do
local unit = d.unit
local ally = d.sees
-- Assign keys for easy lookup by badge logic
if not data[unit] or data[unit].amount < d.amount then
data[unit] = d
end
local hash = ally.handle
if d.amount > 0 or unit:HasStatusEffect("Suspicious") then
if unit.command ~= "OverheardConversationHeadTo" then
EnsureUnitHasAwareBadge(unit)
PlayUnitStartleAnim(unit)
raisingSus[hash] = true
end
end
end
UnitsSusBeingRaised = raisingSus
ObjModified("UnitsSusBeingRaised")
-- Delete lines that are no longer valid
local fade_out = MercDetectionConsts:GetById("MercDetectionConsts").fade_out
for hash, mesh in pairs(drawnData) do
if not stillValid[hash] then
if IsValid(mesh) then
mesh:SetOpacity(0, fade_out)
CreateMapRealTimeThread(function()
Sleep(fade_out)
if IsValid(mesh) then
DoneObject(mesh)
end
end)
end
drawnData[hash] = nil
table.remove_value(self.fx_nearby_enemies, mesh)
end
end
end
function Exploration:NPCMovementThread()
Sleep(2 * 1000)
while true do
assert(g_Exploration and not g_Combat)
--NetUpdateHash("NpcRandomMovement_ThreadProc")
NpcRandomMovement()
Sleep(10 * 1000)
end
end
-----------
-- API
-----------
function SyncStartExploration()
if not GetInGameInterface() then
ShowInGameInterface(true, false, { Mode = "IModeExploration" })
elseif not GetInGameInterfaceMode() ~= "IModeExploration" then
SetInGameInterfaceMode("IModeExploration")
end
cameraTac.SetForceOverview(false)
cameraTac.SetForceMaxZoom(false)
cameraTac.SetFixedLookat(false)
if g_LastExploration then
local oldSusThread = g_LastExploration.sus_thread
assert(not IsValidThread(oldSusThread)) -- Double explore
end
if g_Exploration then -- Ensure we dont leak double threads
DoneObject(g_Exploration)
end
assert(not g_Combat)
NetUpdateHash("SyncStartExploration")
g_Exploration = Exploration:new()
g_LastExploration = g_Exploration
if not SelectedObj then
local igi = GetInGameInterfaceModeDlg()
if igi then
igi:NextUnit()
end
end
if GetUIStyleGamepad() then
local unitsInMap = GetAllPlayerUnitsOnMap()
unitsInMap = table.ifilter(unitsInMap, function(_, o) return o:IsLocalPlayerControlled() and not o:IsDead() end)
SelectionSet(unitsInMap)
end
end
function NetSyncEvents.StartExploration()
SyncStartExploration()
end
function StartExploration()
NetSyncEvent("StartExploration")
end
function NetSyncEvents.ExplorationStartCombat(team_idx, unit_id)
if g_Combat or g_StartingCombat then return end
if not g_Exploration then return end -- can occur via GroupAlert prior to deploy
if config.GamepadTestOnly then return end
print("starting combat")
if not (GameState.Conflict or GameState.ConflictScripted) then
KickOutUnits()
end
-- Find the team and unit that goes first.
local team
if team_idx ~= nil then
if team_idx then
team = g_Teams[team_idx]
end
else
team = GetPoVTeam()
end
local unit = unit_id and g_Units[unit_id]
SetActivePause() -- make sure active pause is off
g_StartingCombat = true
g_Exploration:Done()
g_Exploration = false
CreateGameTimeThread(function()
if g_Combat then
return
end
local igi = GetInGameInterfaceMode()
if IsKindOf(igi, "IModeExploration") then
igi:StopFollow()
end
CloseWeaponModificationCoOpAware()
-- Evaluate TCEs for any "starting combat" tces, and wait for any
-- setpieces that might be triggered (FaucheuxLeave)
QuestTCEEvaluation()
while IsSetpiecePlaying() do
WaitMsg("SetpieceEnded", 100)
end
-- setup combat
local combat = Combat:new{
stealth_attack_start = g_LastAttackStealth,
last_attack_kill = g_LastAttackKill,
}
g_Combat = combat
g_Combat.starting_unit = unit
if team then
g_CurrentTeam = table.find(g_Teams, team)
end
if #Selection > 1 and g_CurrentTeam and g_Combat:AreEnemiesAware(g_CurrentTeam) then
SelectObj()
end
--start combat
combat:Start()
SetInGameInterfaceMode("IModeCombatMovement")
end)
end
--------
-- LOGIC
--------
function AreEnemiesPresent()
for i, team in ipairs(g_Teams) do
if (team.side == "enemy1" or team.side == "enemy2") and next(team.units) then
return true
end
end
end
function NpcRandomMovement()
local radius = 10*guim
NetUpdateHash("NpcRandomMovement_Start")
for i, team in ipairs(g_Teams) do
if team.control == "AI" then
--NetUpdateHash("NpcRandomMovement", team.side, hashParamTable(team.units))
for j, unit in ipairs(team.units) do
--NetUpdateHash("NpcRandomMovement1", unit, unit.command, unit:IsDead())
if not unit.command and unit:IsValidPos() and not unit:IsDead() and not IsSetpieceActor(unit) and not unit.being_interacted_with then
local cx, cy, cz = unit:GetVisualPosXYZ()
local dx = unit:Random(2*radius) - radius
local dy = unit:Random(2*radius) - radius
local target_pos = SnapToPassSlab(cx + dx, cy + dy, cz)
if target_pos then
unit:SetCommand("GotoSlab", target_pos)
end
end
end
end
end
end