How to correctly spawn Armed NPCs (Raiders) via UE4SS World:SpawnActor?

Modding

Hey everyone! I’m currently using a custom UE4SS Lua mod to spawn custom Horde events in SCUM. I have a script that writes target coordinates to a text file, which my Lua mod reads to distribute entities in a grid using World:SpawnActor.

The Current Implementation: My main.lua successfully spawns zombies using: World:SpawnActor(ZombieClass, SpawnLocation, SpawnRotation) This works perfectly for zombies—they spawn, register, and engage players.

The Problem: When I change the ZombieClassPath to an Armed NPC Blueprint (e.g., BP_Guard_Lvl_3_C), the spawn fails completely (no entities appear) or causes a game crash/Lua runtime error. It seems these NPCs refuse to instantiate outside of the Encounter Manager.

My Questions for the modding wizards:

  1. Is there a way to manually register an actor spawned via SpawnActor into the GlobalEncounterManager so the game accepts it?
  2. Are there any hidden initialization functions I need to call on the NPC Blueprint after spawning it to prevent the crash?
  3. Or is it strictly impossible to use SpawnActor for NPCs, and I should focus on finding a way to trigger an EncounterPreset via Kismet?

Code Reference: local Config = require("config")

local ZombieClassPath = "/Game/ConZ_Files/Characters/Zombies2/Blueprints/BP_Zombie_Civilian.BP_Zombie_Civilian_C"

function SpawnZumbi(ContextObj, X, Y, Z, deslocamentoX, deslocamentoY)

local ZombieClass = StaticFindObject(ZombieClassPath)

if not ZombieClass then ZombieClass = StaticFindObject("BlueprintGeneratedClass " .. ZombieClassPath) end

if ZombieClass then

local SpawnLocation = {

X = tonumber(X) + deslocamentoX, 

Y = tonumber(Y) + deslocamentoY, 

Z = tonumber(Z) + 20

}

local SpawnRotation = {Pitch = 0, Yaw = math.random(0, 360), Roll = 0}

local World = nil

if ContextObj then

local RealObj = ContextObj:get()

if RealObj then World = RealObj:GetWorld() end

end

if not World then

local GameMode = FindFirstOf("ConZGameMode")

if not GameMode or not GameMode:IsValid() then

GameMode = FindFirstOf("GameModeBase")

end

if GameMode and GameMode:IsValid() then

World = GameMode:GetWorld()

end

end

if World then

World:SpawnActor(ZombieClass, SpawnLocation, SpawnRotation)

end

end

end

-- =====================================================================

-- FUNÇÃO CENTRAL: LÓGICA DE DISTRIBUIÇÃO EM GRADE (RETÂNGULO)

-- =====================================================================

local function DistribuirHorda(ContextObj, qtdTotal, baseX, baseY, baseZ)

local maxPorPonto = 2      -- Máximo de zumbis colados um no outro

local distancia = 700      -- Distância entre os grupos

local limite = 1400        -- Limite máximo para não passar dos 1500 que você pediu

-- Começa no canto superior esquerdo do "retângulo" imaginário

local offsetX = -limite

local offsetY = -limite

local subContador = 0

for i = 1, qtdTotal do

SpawnZumbi(ContextObj, baseX, baseY, baseZ, offsetX, offsetY)

subContador = subContador + 1

-- Se atingiu 2 zumbis neste ponto, move 700 unidades pro lado (Eixo X)

if subContador >= maxPorPonto then

subContador = 0

offsetX = offsetX + distancia

-- Se o X bater no limite (1400), "pula de linha" (Avança o Y e reseta o X)

if offsetX > limite then

offsetX = -limite

offsetY = offsetY + distancia

-- Se o Y também passar do limite, volta pro centro pra não vazar o raio máximo

if offsetY > limite then

offsetY = -limite

offsetX = -limite

end

end

end

end

end

-- =====================================================================

-- 1. SISTEMA ORIGINAL (Funciona pelo Chat do Jogo)

-- =====================================================================

RegisterHook("/Script/SCUM.PlayerRpcChannel:Chat_Server_BroadcastChatMessage", function(self, Message, Channel)

local msgObj = Message:get()

local msg = (type(msgObj) == "userdata") and msgObj:ToString() or tostring(msgObj)

if string.find(msg, "!spawnhorda") then

local partes = {}

for palavra in string.gmatch(msg, "%S+") do table.insert(partes, palavra) end

if #partes >= 5 then

local qtdTotal = tonumber(partes[2])

local baseX = tonumber(partes[3])

local baseY = tonumber(partes[4])

local baseZ = tonumber(partes[5])

print("[SpawnZumbiCoords] [CHAT] Spawnando " .. tostring(qtdTotal) .. " zumbis em grade!")

DistribuirHorda(self, qtdTotal, baseX, baseY, baseZ)

end

end

end)

-- =====================================================================

-- 2. SISTEMA VIA ARQUIVO (Comunicação direta com o Bot do Discord)

-- =====================================================================

ExecuteAsync(function()

while true do

-- Truque do Ping

os.execute("ping -n 3 127.0.0.1 > nul")

local caminho_arquivo = Config.CaminhoArquivoQueue

local file = io.open(caminho_arquivo, "r")

if file then

local content = file:read("*a")

file:close()

if content and string.len(content) > 5 then

os.remove(caminho_arquivo)

local partes = {}

for palavra in string.gmatch(content, "%S+") do table.insert(partes, palavra) end

if #partes >= 4 then

local qtdTotal = tonumber(partes[1])

local baseX = tonumber(partes[2])

local baseY = tonumber(partes[3])

local baseZ = tonumber(partes[4])

print("[SpawnZumbiCoords] [ARQUIVO] Horda detectada! (" .. tostring(qtdTotal) .. " zumbis em grade)")

ExecuteInGameThread(function()

DistribuirHorda(nil, qtdTotal, baseX, baseY, baseZ)

print("[SpawnZumbiCoords] [ARQUIVO] Horda spawnada e distribuida com sucesso!")

end)

end

end

end

end

end)

Posted Jul 10, 2026, 5:51 PM UTC

42
Loading comments…