myspace / Lua /GameTests.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
34.1 kB
GameTests.BuildingButtons = nil
config.EditorsToTest = false and
{
"AutoAttachPreset", "TextStyle","XTemplate", "ClassDef",
"ParticleSystemPreset", "MapDataPreset", "Achievement", "FXPreset","SoundPreset",
"LootDef", "ConstDef", "Camera", "BanterDef", "CampaignPreset", "Conversation",
"EnemySquads", "PopupNotification", "QuestsDef", "ConflictDescription", "SectorOperation", "SetpiecePrg",
"AppearancePreset", "InventoryItemCompositeDef", "UnitDataCompositeDef",
"AIArchetype", "ActionCameraDef", "ChanceToHitModifier", "CharacterEffectCompositeDef",
}
g_UIGetBuildingsList = function() return {} end
GameTestsNightly.NonInferedShaders = nil
GameTestsNightly.ReferenceImages = nil
GameTestsNightly.RenderingBenchmark = nil
--[[
function GameTestsNightly.CopyDailyTests(time_start_up)
GameTestsPrintf("Night Watch:Zulu daily tests for now...")
--g_UIAutoTestButtonsMap = "_alt1"
for test, func in sorted_pairs(GameTests) do
func(time_start_up)
end
end
]]
function GameTests_LoadTestSaves()
local err, savegames = AsyncListFiles("svnAssets/Source/TestSaves/", "*.savegame.sav")
if err then
GameTestsPrint("Error while getting test savegames: %s", err)
end
table.sort(savegames)
local test_time = 1500
local max_time = 0
for _, savegame in ipairs(savegames) do
local _, file, ext = SplitPath(savegame)
if not string.match(file, "^%[TS 4%]") then
GameTestsPrintf("Loading savegame: %s", savegame)
local start_time = GetPreciseTicks()
local err = LoadGame(file .. ext)
if not err then
GameTestsPrintf("Testing savegame '%s' for %.1fs", savegame, test_time * 0.001)
WaitLoadingScreenClose()
local end_time = GetPreciseTicks() + test_time
while GetPreciseTicks() <= end_time do
Sleep(1)
end
local time = GetPreciseTicks() - start_time
max_time = max_time <= time and time or max_time
else
GameTestsErrorf("Error loading savegame: '%s'", savegame)
end
end
end
GameTestsPrintf("Longest load save took: %.1fs", max_time * 0.001)
end
function GetMapRevision(map_id)
local err, list = AsyncListFiles("Maps/" .. map_id)
if err then return 0 end
local last_rev = 0
for _, file in ipairs(list) do
local rev = GetAssetFileRevision(file)
if rev > last_rev and rev ~= 999999999 then
last_rev = rev
end
end
return last_rev
end
ChangeGameplayMaps_LastChangedToTest = 6
function ViewRandomMapPositions(duration)
local border_areas = MapGetMarkers("BorderArea")
local bam = border_areas and border_areas[1]
if bam then
local end_time = GetPreciseTicks() + (duration or 1500)
for _, pos in random_ipairs(bam:GetAreaPositions(), 0) do
ViewPos(point(point_unpack(pos)))
WaitNextFrame()
if GetPreciseTicks() > end_time then break end
end
end
end
ChangeVideoSettings_ViewPositions = ViewRandomMapPositions
function GetMapsToTest(game_tests_name)
local campaign_maps = {}
for _, map in pairs(MapData) do
if map.Status ~= "Not started" then
campaign_maps[#campaign_maps+1] = { map.id, GetMapRevision(map.id) }
end
end
table.sortby_field_descending(campaign_maps, 2)
local maps_to_test = (game_tests_name == "GameTestsNightly") and #campaign_maps or ChangeGameplayMaps_LastChangedToTest
while #campaign_maps > maps_to_test and campaign_maps[#campaign_maps][2] ~= campaign_maps[1][2] do
table.remove(campaign_maps)
end
return campaign_maps
end
function GameTests.ChangeGameplayMaps(_, game_tests_name)
GameTestMapLoadRandom = xxhash("GameTestMapLoadRandomSeed")
MapLoadRandom = InitMapLoadRandom()
ResetInteractionRand(0) -- same reset at map game time 0 to get control values for interaction rand results
local maps_to_test = GetMapsToTest(game_tests_name)
for _, map_descr in ipairs(maps_to_test) do
local map = MapData[map_descr[1]]
ClearErrorSource()
GameTestsPrint(map.id)
local time = GetPreciseTicks()
ChangeMap(map.id) -- PostNewMapLoaded will emit ValidateMap and show VMEs when in dev mode
GameTestsPrint("... changing map took " .. (GetPreciseTicks() - time) .. " ms")
ValidateMapObjects({ validate_properties = true })
WaitLoadingScreenClose()
GameTestsPrint("... changing map and validation took " .. (GetPreciseTicks() - time) .. " ms")
ViewRandomMapPositions()
GameTestsFlushErrors()
end
end
GameTestsNightly.ChangeGameplayMaps = GameTests.ChangeGameplayMaps
GameTestsNightly.TestMapEntranceMarkersAL = GameTests.TestMapEntranceMarkersAL
function GameTests.Ladders()
ChangeMap("__CombatTest")
if not HasGameSession() then
NewGameSession()
end
local tf = GetTimeFactor()
SetTimeFactor(10000)
InitTestCombat(false, {
TestTeamDef:new{
mercs = { "Barry" },
team_color = RGB(0, 0, 200),
spawn_marker_group = "TestTeamA",
side = "player1"
},
TestTeamDef:new{
mercs = { "Grizzly" },
team_color = RGB(200, 0, 0),
spawn_marker_group = "TestTeamB",
side = "enemy1"
},
})
g_Combat:Start()
while GameTime() == 0 do
Sleep(10)
end
local function exec_action(action_id, unit, ...)
if g_Combat then
CombatActions[action_id]:Execute({unit}, ...)
--Sleep(100)
while #SyncEventsQueue > 0 do
WaitMsg("SyncEventsProcessed", 50)
end
while IsValidTarget(unit) and not unit:IsIdleCommand() do
WaitMsg("Idle", 200)
end
end
end
local ladder = MapGetFirst(true, "Ladder", function(ladder) return ladder.LadderParts > 0 end)
if not ladder then
GameTestsErrorf("Ladders not present on '%s' map", GetMapName())
return
end
local x1, y1, z1, x2, y2, z2 = ladder:GetTunnelPositions()
local pos1 = point(x1, y1, z1)
local pos2 = point(x2, y2, z2)
local Barry = g_Units.Barry
local Grizzly = g_Units.Grizzly
while not Barry:CanBeControlled() do
Sleep(10)
end
Barry:ForEachItemInSlot("Handheld A", false, function(item) Barry:RemoveItem("Handheld A", item) end)
local maxPoints = Barry:GetMaxActionPoints()
Barry.GetMaxActionPoints = function () return maxPoints * 3 end
Barry.ActionPoints = Barry:GetMaxActionPoints()
Barry:SetPos(pos1)
SnapCameraToObj(Barry)
Grizzly:SetPos(pos1 + point(const.SlabSizeX, const.SlabSizeY))
exec_action("Move", Barry, { goto_pos = pos2 })
if pos2:Dist(Barry:GetPos()) > 2 * guim / 10 then
GameTestsErrorf("Climbing ladder up error")
end
exec_action("Move", Barry, { goto_pos = pos1 })
if pos1:Dist(Barry:GetPos()) > 2 * guim / 10 then
GameTestsErrorf("Climbing ladder down error")
end
SetTimeFactor(tf)
end
local mat_props_names = {"Body", "Head", "Hat", "Pants", "Shirt"}
function TestEntityMaterialsUnitLighting()
for _, group in ipairs(Presets.AppearancePreset) do
for _, appearance in ipairs(group) do
for _, mat_name in ipairs(mat_props_names) do
local mat_name_value = appearance[mat_name]
if mat_name_value and mat_name_value ~= "" then
local filename = string.format("%s_mesh.mtl", mat_name_value)
if io.exists("Materials/" .. filename) then
local mat_props = GetMaterialProperties(filename)
if mat_props.UnitLighting == 0 then
local err_text = string.format("Unit Appearance material '%s' should have UnitLighting flag", filename)
GameTestsErrorf(err_text)
print(err_text)
end
end
end
end
end
end
for _, group in ipairs(Presets.WeaponComponent) do
for _, weapon_component in ipairs(group) do
for _, visual in ipairs(weapon_component.Visuals) do
local filename = string.format("%s_mesh.mtl", visual.Entity)
if io.exists("Materials/" .. filename) then
local mat_props = GetMaterialProperties(filename)
if mat_props.UnitLighting == 0 then
local err_text = string.format("Weapon material '%s' should have UnitLighting flag", filename)
GameTestsErrorf(err_text)
print(err_text)
end
end
end
end
end
end
function GameTests.EntityMaterials()
GameTests_LoadAnyMap()
local all_entities = GetAllEntities()
local materials = Presets.ObjMaterial.Default
for entity_name in pairs(all_entities) do
local entity_data = EntityData[entity_name]
if entity_data and entity_data.entity then
local material_type = entity_data.entity.material_type
local material_preset = materials[material_type]
if material_preset and entity_data.editor_category ~= "Decal" then
if material_preset.impenetrable and HasAnySurfaces(entity_name, ~0) then
local has_collision = HasCollisions(entity_name)
local has_obstruction = HasMeshWithCollisionMask(entity_name, const.cmObstruction)
if not (has_collision or has_obstruction) then
local err_text = string.format("Entity '%s' has impenetrable material '%s' without S collision mask/surfaces", entity_name, material_type)
GameTestsErrorf(err_text)
end
end
end
end
end
TestEntityMaterialsUnitLighting()
end
function GameTests.EntityMissingFiles()
GameTests_LoadAnyMap()
local all_entities = GetAllEntities()
for entity in sorted_pairs(all_entities) do
local existing, non_existing = EntitySpec.GetEntityFiles(nil, entity)
for _, ne in ipairs(non_existing) do
if not string.match(ne, ".json$") then
GameTestsErrorf("%s: missing %s", entity, ne)
end
end
end
end
function GameTests.Interactables()
print("Entering interaction test...")
NewGameSession()
local interactionTest = Presets.TestCombat.GameTest.InteractionTest
TestCombatEnterSector(interactionTest, interactionTest.map)
WaitLoadingScreenClose()
wait_interface_mode("IModeExploration")
local unitsToDeploy = GetAllPlayerUnitsOnMap()
local unit = unitsToDeploy[1]
local function lGetNearbyInteractable(obj)
return MapGetFirst(obj:GetPos(), guim * 5, "Interactable")
end
local impassable = MapGetMarkers("Position", "InteractableOnImpassable")[1]
local interactable = lGetNearbyInteractable(impassable)
unit:SetPos(impassable:GetPos())
UIInteractWith(unit, interactable)
Sleep(500)
CloseDialog("FullscreenGameDialogs") -- all test interactables are lootables
if not interactable.interaction_log or interactable.interaction_log == 0 then
GameTestsError("Cant interact with interactable on impassable.")
end
local behindWall = MapGetMarkers("Position", "InteractableBehindWall")[1]
interactable = lGetNearbyInteractable(behindWall)
unit:SetPos(behindWall:GetPos())
UIInteractWith(unit, interactable)
Sleep(500)
while unit.goto_target do
Sleep(100)
end
Sleep(1000)
CloseDialog("FullscreenGameDialogs")
if not interactable.interaction_log or interactable.interaction_log == 0 then
GameTestsError("Cant interact with interactable behind wall.")
end
if unit:GetPos() == behindWall:GetPos() then
GameTestsError("Interacted through a wall.")
end
end
function GameTests.TestSaveLoadSystem()
do return end
GameTests_LoadAnyMap()
print("Starting new game...")
CreateRealTimeThread(QuickStartCampaign)
WaitMsg("EnterSector", 5000)
WaitLoadingScreenClose()
local oldOpenPopup = OpenPopupNotification
OpenPopupNotification = empty_func
local intro = GetDialog("Intro")
if intro then
intro:Close()
WaitMsg("Resume", 2000)
end
while IsSetpiecePlaying() do
print("Waiting for setpiece...")
WaitMsg("SetpieceEnded", 100)
end
Sleep(500) --Setpiece fade in happens after msg for some reason..
-- Pause the game to ensure unit properties dont change due to game logic
Pause("g_TestingSaveLoadSystem") -- sat view won't open with PauseGame cuz it uses threads, but this pause does not persist through load
g_TestingSaveLoadSystem = true
local function finally(err)
if err then
GameTestsErrorf(err)
end
if OpenPopupNotification == empty_func then
OpenPopupNotification = oldOpenPopup
end
g_TestingSaveLoadSystem = false
table.restore(hr, "g_TestingSaveLoadSystem", "ignore_error")
ResumeGame()
Resume("g_TestingSaveLoadSystem")
print("Done!")
end
print("Grabbing unit info...")
local units = GetAllPlayerUnitsOnMap()
local propertiesPreSave = {}
local unitPropertiesProperties = UnitProperties:GetProperties()
for i, u in ipairs(units) do
local unitTable = {}
for i = 1, #unitPropertiesProperties do
local prop_meta = unitPropertiesProperties[i]
if prop_meta.dont_save then goto continue end
local prop_id = prop_meta.id
local value = u:GetProperty(prop_id)
local default = u:GetDefaultPropertyValue(prop_id, prop_meta)
local is_default = true
if type(value) == "table" and type(default) == "table" then
is_default = value == default or table.hash(value) == table.hash(default)
else
is_default = value == nil or value == default
end
if is_default then
unitTable[prop_id] = "~~default~~"
elseif type(value) == "table" then
unitTable[prop_id] = ValueToLuaCode(value)
else
unitTable[prop_id] = value
end
::continue::
end
propertiesPreSave[u.session_id] = unitTable
end
local function lComparePropertiesWithPreSave()
local units = GetAllPlayerUnitsOnMap()
for i, u in ipairs(units) do
local unitTable = propertiesPreSave[u.session_id]
-- What if a new unit appeared?
if unitTable then
for i = 1, #unitPropertiesProperties do
local prop_meta = unitPropertiesProperties[i]
if prop_meta.dont_save then goto continue end
local prop_id = prop_meta.id
local value = u:GetProperty(prop_id)
local default = u:GetDefaultPropertyValue(prop_id, prop_meta)
local is_default = true
if type(value) == "table" and type(default) == "table" then
is_default = value == default or table.hash(value) == table.hash(default)
else
is_default = value == nil or value == default
end
if is_default then
value = "~~default~~"
elseif type(value) == "table" then
value = ValueToLuaCode(value)
end
local preSaveVal = unitTable[prop_id]
if preSaveVal ~= nil and preSaveVal ~= value then
GameTestsErrorf("Property %s of unit %s changed its value from %s to %s", prop_id, u.session_id, preSaveVal, value)
end
::continue::
end
end
end
end
local function lReallyWaitForLSToClose()
--SatelliteToggleActionRun will refuse to do work if ls is up
local timeout = 5000
local ts = GetPreciseTicks()
while GetLoadingScreenDialog() do
if GetPreciseTicks() - ts > timeout then
break
end
print("waiting for ls to close...")
Sleep(100)
end
end
print("Opening satellite view...")
if SatelliteToggleActionState() ~= "enabled" then
finally("Satellite view is disabled!")
return
end
lReallyWaitForLSToClose()
local err = SatelliteToggleActionRun()
if not err then
WaitMsg("OpenSatelliteView", 10000)
end
if not gv_SatelliteView then
finally("Couldn't open satellite view, " .. (err or ""))
return
end
print("Closing satellite view...")
lReallyWaitForLSToClose()
err = SatelliteToggleActionRun()
if not err then
WaitMsg("CloseSatelliteView", 10000)
end
if gv_SatelliteView then
finally("Couldn't close satellite view, " .. (err or ""))
return
end
print("Checking if satellite sync changed unit properties...")
lComparePropertiesWithPreSave()
OpenPopupNotification = oldOpenPopup
local function lResetVariablesExpectedToChange()
InteractionSeed = 0
InteractionSeeds = {}
MapLoadRandom = 0
Game.loaded_from_id = false
end
-- Persists through load!
PauseGame() --use this pause for save/load testing cuz the other one does not persist;
table.change(hr, "g_TestingSaveLoadSystem", {CameraTacMouseEdgeScrolling = false, CameraTacClampToTerrain = false})
print("Saving game and loading it...")
lResetVariablesExpectedToChange()
local saveOne = GatherSessionData():str()
LoadGameSessionData(saveOne)
local filePath = "AppData/TestSave.lua"
local err = AsyncStringToFile(filePath, saveOne)
if err then
printf("Failed to save to file %s: %s", filePath, err)
end
print("Checking if loading the save changed unit properties...")
lComparePropertiesWithPreSave()
print("Saving game again, and checking difff...")
local filePathTwo = "AppData/TestSaveTwo.lua"
lResetVariablesExpectedToChange()
local saveTwo = GatherSessionData():str()
err = AsyncStringToFile(filePathTwo, saveTwo)
if err then
printf("Failed to save to file %s: %s", filePathTwo, err)
end
local _, str = SVNDiffTwoUnrelatedFiles(filePath, filePathTwo)
local diff = {}
for s in str:gmatch("[^\r\n]+") do
diff[#diff+1] = s
if #diff == 20 then break end
end
if #diff > 0 then
GameTestsError("Second save is different from the first?!")
GameTestsPrint(table.concat(diff, "\n"))
end
finally()
print("Success!")
end
function GameTests_LandmineLOF()
NewGameSession() -- combat test will try to keep the existing squads, prevent this
local retreatTest = Presets.TestCombat.GameTest.RetreatTest
TestCombatEnterSector(retreatTest, retreatTest.map)
WaitLoadingScreenClose()
wait_interface_mode("IModeDeployment")
local dlg = GetInGameInterfaceModeDlg()
dlg:StartExploration()
WaitMsg("ExplorationTick", 10000)
local landmine = PlaceObject("Landmine")
landmine:SetPos(point(198499, 117922, 6950))
landmine.discovered_by["player1"] = true
local kalyna = g_Units.Kalyna
local defaultAction = kalyna:GetDefaultAttackAction("ranged")
local results = defaultAction:GetActionResults(kalyna, { target = landmine })
if not results.target_hit then
GameTestsErrorf("LOF didn't hit landmine!")
end
end
function GameTests.DeploymentLogic()
print("Entering deployment combat test...")
NewGameSession()
local deploymentTest = Presets.TestCombat.GameTest.DeploymentTest
TestCombatEnterSector(deploymentTest, deploymentTest.map)
WaitLoadingScreenClose()
wait_interface_mode("IModeDeployment")
if not gv_Deployment then
GameTestsErrorf("Deployment didn't start!")
return
end
local allTheMarkers = MapGetMarkers()
local cliff = table.find_value(allTheMarkers, "ID", "Cliff")
local raised = table.find_value(allTheMarkers, "ID", "Raised")
local flat = table.find_value(allTheMarkers, "ID", "Flat")
local house = table.find_value(allTheMarkers, "ID", "House")
local beach = table.find_value(allTheMarkers, "ID", "Beach")
local raisedTerrain = table.find_value(allTheMarkers, "ID", "RaisedTerrain")
local markerInHouse = table.find_value(allTheMarkers, "ID", "InBuilding")
assert(cliff and raised and flat and house and beach and raisedTerrain)
local unitsToDeploy = GetCurrentDeploymentSquadUnits()
assert(#unitsToDeploy == 4)
ResetInteractionRand(0) -- Deployment uses randomization, reset to ensure the test is deterministic.
local function CheckDeploymentAllCool()
local highestZ = -9999999999
local lowestZ = 9999999999
for i, u in ipairs(unitsToDeploy) do
assert(u.visible, u.session_id) -- Did this unit get deployed?
local _, _, vZ = WorldToVoxel(u)
highestZ = Max(highestZ, vZ)
lowestZ = Min(lowestZ, vZ)
end
if abs(highestZ - lowestZ) > 1 then
GameTestsErrorf("Units were deployed very far apart on the z axis, possibly to an invalid pos.")
return false
end
return true
end
-- Test cliff deployment. Half the cliff is on the terrain, the other is on a rock object.
-- Common problems:
-- 1. Units going outside of area because it is too thin
-- 2. Units deploying inside the cliff face
SnapCameraToObj(cliff, true)
Sleep(300)
local pointOnTerrainPart = point(172200, 132600)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, cliff, "show", pointOnTerrainPart)
if not CheckDeploymentAllCool() then return end
end
local pointOnObjectPart = point(168600, 131400, 15400)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, cliff, "show", pointOnObjectPart)
if not CheckDeploymentAllCool() then return end
end
-- Test raised deployment. The marker is high above the ground on a slab. A small part of the area is on a lower z.
-- Common problems:
-- 1. Portion of the area is unclickable.
-- 2. Units deploying on the ground below
-- 3. Units not being able to deploy on the stairs down the marker
SnapCameraToObj(raised, true)
Sleep(300)
local OnArea = point(126600, 136200, 13300)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, raised, "show", OnArea)
if not CheckDeploymentAllCool() then return end
end
local downTheStairs = point(126600, 131400, 12600)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, raised, "show", downTheStairs)
if not CheckDeploymentAllCool() then return end
assert(unitsToDeploy[1]:GetPos() == downTheStairs)
end
local inTheVoid = point(124200, 137400)
if raised:IsInsideArea(inTheVoid) then
GameTestsErrorf("The point below the marker shouldn't be considered inside it.")
return
end
-- Test flat deployment. Nothing special here, just in case.
SnapCameraToObj(flat, true)
Sleep(300)
local partOne = point(127800, 177000)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, flat, "show", partOne)
if not CheckDeploymentAllCool() then return end
end
local partTwo = point(124200, 163800)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, flat, "show", partTwo)
if not CheckDeploymentAllCool() then return end
end
-- Test house deployment.
-- Deploying the mercs in the house should deploy the other mercs around it, rather than far away outside.
SnapCameraToObj(house, true)
Sleep(300)
local insideHouse = point(166200, 177000, 7000)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, house, "show", insideHouse)
if not CheckDeploymentAllCool() then return end
for i, u in ipairs(unitsToDeploy) do
local unitPos = u:GetPos()
assert(unitPos:IsValidZ()) -- Not on the terrain outside, but inside the house.
end
end
local outsideHouse = point(175800, 178200)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, house, "show", outsideHouse)
if not CheckDeploymentAllCool() then return end
end
-- Beach deployment.
-- This is used at the start of the game and is the only time you'll find deployment on a "Reachable Only" marker
beach.Reachable = true
SnapCameraToObj(beach, true)
Sleep(300)
local onTheBeach = point(178200, 157800)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, beach, "show", onTheBeach)
if not CheckDeploymentAllCool() then return end
end
-- Raised terrain deployment
-- Voxels on different Z levels but both on the terrain.
SnapCameraToObj(raisedTerrain, true)
local lowArea = point(185400, 108600)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, raisedTerrain, "show", lowArea)
if not CheckDeploymentAllCool() then return end
assert(unitsToDeploy[1]:GetPos() == lowArea)
end
local highArea = point(174600, 108600)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, raisedTerrain, "show", highArea)
if not CheckDeploymentAllCool() then return end
assert(unitsToDeploy[1]:GetPos() == highArea)
end
-- Marker inside house, deploying outside on terrain.
SnapCameraToObj(markerInHouse, true)
local outsideArea = point(102600, 162600)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, markerInHouse, "show", outsideArea)
if not CheckDeploymentAllCool() then return end
assert(unitsToDeploy[1]:GetPos() == outsideArea)
end
local insideArea = point(103800, 155400, 7700)
for i = 1, 5 do
LocalDeployUnitsOnMarker(unitsToDeploy, markerInHouse, "show", insideArea)
if not CheckDeploymentAllCool() then return end
assert(unitsToDeploy[1]:GetPos() == insideArea)
end
print("Entering retreat test...")
local retreatTest = Presets.TestCombat.GameTest.RetreatTest
TestCombatEnterSector(retreatTest, retreatTest.map)
WaitLoadingScreenClose()
wait_interface_mode("IModeDeployment")
local dlg = GetInGameInterfaceModeDlg()
dlg:StartExploration()
WaitMsg("ExplorationStart", 5000)
Sleep(100)
gv_Sectors.I1.Side = "player1" -- Provides vision on the satellite map
AllowRevealSectors({ "H2", "H3", "H4", "I1", "I2", "I3" })
unitsToDeploy = GetCurrentDeploymentSquadUnits()
local marker = MapGetMarkers("ExitZoneInteractable", "East")
marker[1]:UnitLeaveSector(unitsToDeploy[1])
WaitMsg("ZuluMessagePopup", 1000)
local popup = g_ZuluMessagePopup[1]
popup:Close(1)
WaitMsg("InitSatelliteView", 3000)
assert(gv_SatelliteView)
end
function WriteAnimSetsCSV()
local anims, csv, columns = {}, {}, { "name" }
for _, model in ipairs{ "Male", "Female" } do
for _, prefix in ipairs{ "ar_", "civ_", "dw_", "gr_", "hg_", "hmg_", "hw_", "mk_", "nw_", "sg_", "inf_" } do
local column = string.sub(model .. "_" .. prefix, 1, -2)
columns[#columns+1] = column
for _, anim in ipairs(table.map(EnumValidStates(model), GetStateName)) do
if anim:starts_with(prefix) then
local anim_name = anim:sub(#prefix+1)
anims[anim_name] = anims[anim_name] or { name = anim_name }
anims[anim_name][column] = GetAnimDuration(model, anim)
end
end
end
end
for name, columns in sorted_pairs(anims) do
csv[#csv+1] = columns
end
SaveCSV("AnimsBySet.csv", csv, columns, columns, ",")
end
function GameTests.AnimsCheckList()
return
end
--[[
GameTests_LoadAnyMap()
local model = "Male"
local weapon_anims = GetModelWeaponAnims(model)
local ar_anims = weapon_anims[1]
local function check_model(weapon_anims, start_idx, model2)
for k = start_idx, #weapon_anims do
local weapon_prefix = weapon_sets[k]
local anims = weapon_anims[k]
if #ar_anims ~= #anims then
GameTestsPrintf("%s:'%s' weapon set has %d animations, '%s:ar_' has %d",
model2, weapon_prefix, #anims, model, #ar_anims)
end
-- check animations sets differencies
local extra_anims = table.subtraction(anims, ar_anims)
local missing_anims = table.subtraction(ar_anims, anims)
if #extra_anims > 0 then
GameTestsPrintf("'%s:%s' weapon set has %d extra animations compared to the '%s:_ar' set: %s",
model2, weapon_prefix, #extra_anims, model, table.concat(extra_anims, ", "))
end
if #missing_anims > 0 then
GameTestsPrintf("'%s:%s' weapon set misses %d animations from the '%s:_ar' set: %s",
model2, weapon_prefix, #missing_anims, model, table.concat(missing_anims, ", "))
end
-- check matching anims for compensation and moments
local matching_anims = table.intersection(ar_anims, anims)
for _, anim in ipairs(matching_anims) do
-- check compensation
local ar_anim = "ar_" .. anim
local weapon_anim = weapon_prefix .. anim
local ar_compensatation = GetAnimCompensate(model, weapon_anim)
local weapon_compensation = GetAnimCompensate(model, ar_anim)
if ar_compensatation ~= weapon_compensation then
GameTestsPrintf("'%s:%s' is %s compensated but '%s:%s' is %s compensated",
model2, weapon_anim, ar_compensatation, model, ar_anim, weapon_compensation)
end
-- check moments
local ar_moments = table.map(GetEntityAnimMoments(model, ar_anim), "Type")
local weapon_moments = table.map(GetEntityAnimMoments(model, weapon_anim), "Type")
if #ar_moments ~= #weapon_moments then
GameTestsPrintf("'%s:%s' has %d moments, '%s:%s' has %d",
model2, weapon_anim, #weapon_moments, model, ar_anim, #ar_moments)
end
local moment_diff = #ar_moments ~= #weapon_moments
if not moment_diff then
for m = 1, #ar_moments do
if ar_moments[m] ~= weapon_moments[m] then
moment_diff = m
break
end
end
end
if moment_diff then
GameTestsPrintf("'%s:%s' moments sequence mismatches '%s:%s", model2, weapon_anim, model, ar_anim)
GameTestsPrintf("'%s:%s': %s", model2, weapon_anim, table.concat(weapon_moments, ", "))
GameTestsPrintf("'%s:%s': %s", model, ar_anim, table.concat(ar_moments, ", "))
local extra_moments = table.subtraction(weapon_moments, ar_moments)
local missing_moments = table.subtraction(ar_moments, weapon_moments)
if #extra_moments > 0 then
GameTestsPrintf("'%s:%s' has %d extra moments compared to the '%s:%s' set: %s",
model2, weapon_anim, #extra_moments, model, ar_anim, table.concat(extra_moments, ", "))
end
if #missing_moments > 0 then
GameTestsPrintf("'%s:%s' misses %d moments from the '%s:%s': %s",
model2, weapon_anim, #missing_moments, model, ar_anim, table.concat(missing_moments, ", "))
end
end
end
end
end
check_model(weapon_anims, 2, model)
local model2 = "Female"
local weapon_anims2 = GetModelWeaponAnims(model2)
-- trunc first model animations variations if more than corresponding animations in model2
-- e.g. Female:ar_Combat_Walk has less variations exported than Male:ar_CombatWalk
for k, anims in ipairs(weapon_anims2) do
for _, anim_name in ipairs(anims) do
local anim_base = string.match(anim_name, "([^%d]+)(%d+)")
if anim_base then
local model_vars = weapon_anims[k].vars[anim_base]
local model2_vars = weapon_anims2[k].vars[anim_base]
if model_vars and model2_vars and model_vars > model2_vars then
for var = model2_vars + 1, model_vars do
local anim_to_del = anim_base .. var
table.remove_entry(weapon_anims[k], anim_to_del)
end
end
end
end
end
check_model(weapon_anims2, 1, model2)
end
local weapon_sets = {"ar_", "mk_", "dw_", "hg_", "nw_", "hw_"}
local function GetWeaponPrefix(anim_name)
for _, prefix in ipairs(weapon_sets) do
local weapon_prefix = "^" .. prefix
if string.match(anim_name, weapon_prefix) then
return prefix
end
end
end
local s_WeaponAnim = false
local function GetModelWeaponAnims(model, sets)
local valid_states = EnumValidStates(model)
local anims = {}
for _, state in ipairs(valid_states) do
table.insert(anims, GetStateName(state))
end
local weapon_anims = {}
for idx, prefix in ipairs(sets or weapon_sets) do
weapon_anims[idx] = {vars = {}, exists = {}}
local weapon_prefix = "^" .. prefix
for _, anim in ipairs(anims) do
if string.match(anim, weapon_prefix) and anim:sub(-1, -1) ~= "*" then
local anim_name = string.match(anim, weapon_prefix .. "(.*)")
table.insert(weapon_anims[idx], anim_name)
local anim_base, anim_vars = string.match(anim_name, "([^%d]+)(%d+)")
if anim_base then
anim_vars = tonumber(anim_vars)
weapon_anims[idx].vars[anim_base] = Max(weapon_anims[idx].vars[anim_base] or 0, anim_vars)
end
local anim_base = string.sub(anim, string.len(prefix) + 1, -1)
weapon_anims[idx].exists[anim_base] = true
end
end
end
return weapon_anims
end
local function GetMissingSets(model, anim_base)
if not s_WeaponAnim then
s_WeaponAnim = {
["Male"] = GetModelWeaponAnims("Male"),
["Female"] = GetModelWeaponAnims("Female")
}
end
local missing_sets = {}
for k, weapon_prefix in ipairs(weapon_sets) do
if not s_WeaponAnim[model][k].exists[anim_base] then
table.insert(missing_sets, weapon_prefix)
end
end
return #missing_sets > 0 and missing_sets
end
function AnimMetadata:GetWarning()
local entity = self.group
local anim = self.id
local weapon_prefix = GetWeaponPrefix(anim)
local matching_other_weapons = true
if weapon_prefix then
local anim_base = string.sub(anim, string.len(weapon_prefix) + 1, -1)
for _, prefix in ipairs(weapon_sets) do
if weapon_prefix ~= prefix then
local missing_sets = GetMissingSets(entity, anim_base)
if missing_sets then
if #missing_sets > 1 then
return string.format("'%s' missing in {%s} sets", anim, table.concat(missing_sets, ", "))
else
return string.format("'%s' missing in '%s' set", anim, missing_sets[1])
end
end
end
end
end
end
]]
GameTestsNightly.TestDoesPrefabMapsSavingGenerateFakeDeltas = empty_func
function GameTestsNightly.TestBantersUsage()
GameTests_LoadAnyMap()
ClearErrorSource()
local undefinedBanters, unusedBanters, ignoredGroups = TestBantersUsage()
for _, undefinedBanterMarkers in ipairs(undefinedBanters) do
local id = undefinedBanterMarkers[1]
local obj = undefinedBanterMarkers[2]
local handle, map = obj.handle, obj.map
StoreErrorSource(HandleToObject[handle], string.format("Did not find a banter id or group '%s' for marker with handle %d on map: %s", id, handle, map))
end
for _, unusedBanter in ipairs(unusedBanters) do
StoreWarningSource(unusedBanter, string.format("Banter not used anywhere: '%s'", unusedBanter.id))
end
print("<color 255 0 0> These banter groups are ignored: " .. table.concat(ignoredGroups, ", ") .. "</color>")
end
function GameTestsNightly_TestLootTablesUsage()
GameTests_LoadAnyMap()
ClearErrorSource()
local undefinedLootTableIds, unusedLootTableIds, ignoredLootTableGroups = TestLootTablesUsage()
for _, undefinedBanterMarkers in ipairs(undefinedLootTableIds) do
StoreErrorSource(undefinedBanterMarkers[2], string.format("Did not find a loot table id or group '%s' on map: %s", undefinedBanterMarkers[1], undefinedBanterMarkers[2].map))
end
for _, unusedLootTable in ipairs(unusedLootTableIds) do
StoreWarningSource(unusedLootTable, string.format("Loot table not used anywhere: '%s'", unusedLootTable.id))
end
print("<color 255 0 0> These loot table groups are ignored: " .. table.concat(ignoredLootTableGroups, ", ") .. "</color>")
end
GameTests.CheckSpots = nil
function GameTests.CheckNonReachableAnimFXMoment()
local anim_moment = {}
for entity, anims in pairs(Presets.AnimMetadata) do
for _, anim in ipairs(anims) do
for _, moment in ipairs(anim.Moments) do
anim_moment[anim.id] = anim_moment[anim.id] or {}
anim_moment[anim.id][moment.Type] = true
end
end
end
local missing_anims, missing_moments = 0, 0
for fx, moments in sorted_pairs(FXRules) do
local anim = string.match(fx, "Anim:(.+)")
if anim then
if not anim_moment[anim] then
GameTestsPrintf("No such anim in AME: %s, required for: %s[%s]", anim, fx, table.concat(table.keys(moments, "sorted"), ","))
missing_anims = missing_anims + 1
else
for moment, actors in sorted_pairs(moments) do
for actor in sorted_pairs(actors) do
if not anim_moment[anim][moment] then
GameTestsPrintf("Missing FX moment in AME for '%s': %s-%s-%s", anim, fx, moment, actor)
missing_moments = missing_moments + 1
end
end
end
end
end
end
GameTestsPrintf("Missing FX anims: %d, Missing FX moments: %d", missing_anims, missing_moments)
end