2024-08-07 17:11:55 +03:00
|
|
|
local util = dofile_once("mods/quant.ew/files/core/util.lua")
|
|
|
|
local ctx = dofile_once("mods/quant.ew/files/core/ctx.lua")
|
|
|
|
local net = dofile_once("mods/quant.ew/files/core/net.lua")
|
|
|
|
local player_fns = dofile_once("mods/quant.ew/files/core/player_fns.lua")
|
2024-11-03 18:04:14 -05:00
|
|
|
local item_sync = dofile_once("mods/quant.ew/files/system/item_sync.lua")
|
2024-08-29 09:19:04 -04:00
|
|
|
local effect_sync = dofile_once("mods/quant.ew/files/system/game_effect_sync/game_effect_sync.lua")
|
2024-11-16 09:53:14 -05:00
|
|
|
local stain_sync = dofile_once("mods/quant.ew/files/system/effect_data_sync/effect_data_sync.lua")
|
2024-06-02 13:49:11 +03:00
|
|
|
local np = require("noitapatcher")
|
2024-05-30 12:41:56 +03:00
|
|
|
|
2024-08-16 22:54:58 +03:00
|
|
|
local ffi = require("ffi")
|
2024-05-30 12:41:56 +03:00
|
|
|
local rpc = net.new_rpc_namespace()
|
2024-05-08 20:33:41 +03:00
|
|
|
|
2024-08-16 21:52:15 +03:00
|
|
|
local EnemyData = util.make_type({
|
2024-08-16 23:58:11 +03:00
|
|
|
u32 = {"enemy_id"},
|
2024-10-16 06:45:23 -04:00
|
|
|
f32 = {"x", "y", "vx", "vy"},
|
2024-08-16 21:52:15 +03:00
|
|
|
})
|
2024-08-16 22:20:00 +03:00
|
|
|
|
2024-08-16 23:38:38 +03:00
|
|
|
-- Variant of EnemyData for when we don't have any motion (or no VelocityComponent).
|
|
|
|
local EnemyDataNoMotion = util.make_type({
|
2024-08-16 23:58:11 +03:00
|
|
|
u32 = {"enemy_id"},
|
2024-10-29 07:13:27 -04:00
|
|
|
f32 = {"x", "y"},
|
2024-08-17 00:55:05 +03:00
|
|
|
})
|
|
|
|
|
2024-10-16 06:45:23 -04:00
|
|
|
local EnemyDataWorm = util.make_type({
|
2024-11-03 07:28:17 -05:00
|
|
|
u32 = {"enemy_id"},
|
2024-10-16 06:45:23 -04:00
|
|
|
f32 = {"x", "y", "vx", "vy", "tx", "ty"},
|
|
|
|
})
|
|
|
|
|
2024-10-26 19:32:01 -04:00
|
|
|
local EnemyDataKolmi = util.make_type({
|
|
|
|
u32 = {"enemy_id"},
|
|
|
|
f32 = {"x", "y", "vx", "vy"},
|
2024-11-02 18:37:16 -04:00
|
|
|
bool = {"enabled"},
|
2024-10-26 19:32:01 -04:00
|
|
|
})
|
|
|
|
|
2024-11-10 09:20:34 -05:00
|
|
|
local EnemyDataMom = util.make_type({
|
|
|
|
u32 = {"enemy_id"},
|
|
|
|
f32 = {"x", "y", "vx", "vy"},
|
|
|
|
vecbool = {"orbs"},
|
|
|
|
})
|
|
|
|
|
2024-10-16 06:45:23 -04:00
|
|
|
local EnemyDataFish = util.make_type({
|
|
|
|
u32 = {"enemy_id"},
|
|
|
|
f32 = {"x", "y", "vx", "vy"},
|
2024-10-29 07:13:27 -04:00
|
|
|
u8 = {"r"},
|
2024-10-16 06:45:23 -04:00
|
|
|
})
|
|
|
|
|
2024-08-17 00:55:05 +03:00
|
|
|
local HpData = util.make_type({
|
|
|
|
u32 = {"enemy_id"},
|
|
|
|
f32 = {"hp", "max_hp"}
|
2024-08-16 23:38:38 +03:00
|
|
|
})
|
|
|
|
|
2024-08-16 22:20:00 +03:00
|
|
|
local FULL_TURN = math.pi * 2
|
|
|
|
|
2024-08-16 21:38:14 +03:00
|
|
|
local PhysData = util.make_type({
|
2024-08-16 22:20:00 +03:00
|
|
|
f32 = {"x", "y", "vx", "vy", "vr"},
|
|
|
|
-- We should be able to cram rotation into 1 byte.
|
|
|
|
u8 = {"r"}
|
2024-08-16 21:38:14 +03:00
|
|
|
})
|
|
|
|
|
2024-08-16 22:54:58 +03:00
|
|
|
-- Variant of PhysData for when we don't have any motion.
|
|
|
|
local PhysDataNoMotion = util.make_type({
|
|
|
|
f32 = {"x", "y"},
|
|
|
|
-- We should be able to cram rotation into 1 byte.
|
|
|
|
u8 = {"r"}
|
|
|
|
})
|
|
|
|
|
2024-11-13 18:54:28 -05:00
|
|
|
local frame = 0
|
|
|
|
|
2024-05-08 20:33:41 +03:00
|
|
|
local enemy_sync = {}
|
|
|
|
|
2024-10-12 10:20:54 -04:00
|
|
|
local unsynced_enemys = {}
|
|
|
|
|
2024-06-02 13:49:11 +03:00
|
|
|
local dead_entities = {}
|
2024-08-29 09:19:04 -04:00
|
|
|
--this basically never happens, doesn't seem that useful anymore. Perhaps should be removed to conserve memory.
|
|
|
|
--local confirmed_kills = {}
|
2024-06-02 13:49:11 +03:00
|
|
|
|
2024-08-13 18:43:01 +03:00
|
|
|
local spawned_by_us = {}
|
|
|
|
|
2024-08-15 23:23:24 +03:00
|
|
|
-- HACK
|
|
|
|
local times_spawned_last_minute = {}
|
|
|
|
|
2024-09-29 09:41:48 -04:00
|
|
|
local DISTANCE_LIMIT = 128*6
|
2024-08-17 18:46:04 +03:00
|
|
|
|
2024-08-15 21:02:23 +03:00
|
|
|
for filename, _ in pairs(constants.phys_sync_allowed) do
|
|
|
|
util.add_tag_to(filename, "prop_physics")
|
|
|
|
-- Idk it just causes the minecart to not appear at all.
|
|
|
|
-- util.replace_text_in(filename, 'kill_entity_after_initialized="1"', 'kill_entity_after_initialized="0"')
|
|
|
|
end
|
|
|
|
|
2024-11-17 17:10:57 +03:00
|
|
|
util.add_cross_call("ew_es_death_notify", function(enemy_id, responsible_id)
|
2024-06-02 13:49:11 +03:00
|
|
|
local player_data = player_fns.get_player_data_by_local_entity_id(responsible_id)
|
2024-08-15 09:16:29 -04:00
|
|
|
local responsible
|
2024-06-02 13:49:11 +03:00
|
|
|
if player_data ~= nil then
|
|
|
|
responsible = player_data.peer_id
|
|
|
|
else
|
|
|
|
responsible = responsible_id
|
|
|
|
end
|
|
|
|
table.insert(dead_entities, {enemy_id, responsible})
|
|
|
|
end)
|
|
|
|
|
2024-05-08 20:33:41 +03:00
|
|
|
local function world_exists_for(entity)
|
|
|
|
local x, y = EntityGetFirstHitboxCenter(entity)
|
|
|
|
local w, h = 5, 5 -- TODO
|
|
|
|
w = w * 0.5
|
|
|
|
h = h * 0.5
|
|
|
|
return DoesWorldExistAt(x - w, y - h, x + w, y + h)
|
|
|
|
end
|
|
|
|
|
2024-05-09 23:37:50 +03:00
|
|
|
local function table_extend(to, from)
|
|
|
|
for _, e in ipairs(from) do
|
|
|
|
to[#to+1] = e
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function table_extend_filtered(to, from, filter)
|
|
|
|
for _, e in ipairs(from) do
|
|
|
|
if filter(e) then
|
|
|
|
to[#to+1] = e
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-08-23 20:39:38 +03:00
|
|
|
local function serialize_phys_component(phys_component)
|
|
|
|
local px, py, pr, pvx, pvy, pvr = np.PhysBodyGetTransform(phys_component)
|
2024-09-17 14:37:33 +03:00
|
|
|
px, py = PhysicsPosToGamePos(px, py)
|
2024-08-23 20:39:38 +03:00
|
|
|
if math.abs(pvx) < 0.01 and math.abs(pvy) < 0.01 and math.abs(pvr) < 0.01 then
|
|
|
|
return PhysDataNoMotion {
|
|
|
|
x = px,
|
|
|
|
y = py,
|
|
|
|
r = math.floor((pr % FULL_TURN) / FULL_TURN * 255),
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return PhysData {
|
|
|
|
x = px,
|
|
|
|
y = py,
|
|
|
|
r = math.floor((pr % FULL_TURN) / FULL_TURN * 255),
|
|
|
|
vx = pvx,
|
|
|
|
vy = pvy,
|
|
|
|
vr = pvr,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function deserialize_phys_component(phys_component, phys_info)
|
2024-09-17 14:37:33 +03:00
|
|
|
local x, y = GamePosToPhysicsPos(phys_info.x, phys_info.y)
|
2024-08-23 20:39:38 +03:00
|
|
|
if ffi.typeof(phys_info) == PhysDataNoMotion then
|
2024-09-17 14:37:33 +03:00
|
|
|
np.PhysBodySetTransform(phys_component, x, y, phys_info.r / 255 * FULL_TURN, 0, 0, 0)
|
2024-08-23 20:39:38 +03:00
|
|
|
else
|
2024-09-17 14:37:33 +03:00
|
|
|
np.PhysBodySetTransform(phys_component, x, y, phys_info.r / 255 * FULL_TURN, phys_info.vx, phys_info.vy, phys_info.vr)
|
2024-08-23 20:39:38 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-08-17 18:46:04 +03:00
|
|
|
local function get_sync_entities(return_all)
|
2024-11-16 10:29:27 -05:00
|
|
|
local entities = EntityGetWithTag("enemy") or {}
|
2024-06-19 13:12:06 +03:00
|
|
|
table_extend(entities, EntityGetWithTag("ew_enemy_sync_extra"))
|
2024-10-13 18:58:22 -04:00
|
|
|
table_extend(entities, EntityGetWithTag("plague_rat"))
|
2024-11-16 09:27:29 -05:00
|
|
|
table_extend(entities, EntityGetWithTag("seed_f"))
|
|
|
|
table_extend(entities, EntityGetWithTag("seed_e"))
|
|
|
|
table_extend(entities, EntityGetWithTag("seed_d"))
|
|
|
|
table_extend(entities, EntityGetWithTag("seed_c"))
|
2024-10-15 17:55:56 -04:00
|
|
|
table_extend(entities, EntityGetWithTag("perk_fungus_tiny"))
|
|
|
|
table_extend(entities, EntityGetWithTag("helpless_animal"))
|
2024-08-13 14:18:45 +03:00
|
|
|
table_extend_filtered(entities, EntityGetWithTag("prop_physics"), function (ent)
|
2024-11-14 08:46:31 -05:00
|
|
|
local f = EntityGetFilename(ent)
|
|
|
|
if f ~= nil then
|
|
|
|
return constants.phys_sync_allowed[f]
|
|
|
|
end
|
|
|
|
return true
|
2024-08-13 14:18:45 +03:00
|
|
|
end)
|
|
|
|
|
2024-08-17 18:46:04 +03:00
|
|
|
local entities2 = {}
|
|
|
|
if return_all then
|
2024-10-20 06:27:38 -04:00
|
|
|
table_extend_filtered(entities2, entities, function(ent)
|
|
|
|
return not EntityHasTag(ent, "ew_no_enemy_sync")
|
|
|
|
end)
|
2024-08-17 18:46:04 +03:00
|
|
|
else
|
|
|
|
table_extend_filtered(entities2, entities, function(ent)
|
|
|
|
local x, y = EntityGetTransform(ent)
|
2024-08-29 18:01:52 -04:00
|
|
|
local has_anyone = EntityHasTag(ent, "worm")
|
|
|
|
or EntityGetFirstComponent(ent, "BossHealthBarComponent") ~= nil
|
2024-09-29 09:41:48 -04:00
|
|
|
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "ew_peer") ~= 0
|
2024-10-20 06:27:38 -04:00
|
|
|
return has_anyone and not EntityHasTag(ent, "ew_no_enemy_sync")
|
2024-08-17 18:46:04 +03:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
return entities2
|
2024-05-10 10:30:14 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function enemy_sync.host_upload_entities()
|
|
|
|
local entities = get_sync_entities()
|
2024-05-08 20:33:41 +03:00
|
|
|
local enemy_data_list = {}
|
2024-05-09 23:37:50 +03:00
|
|
|
for i, enemy_id in ipairs(entities) do
|
2024-05-08 20:33:41 +03:00
|
|
|
if not world_exists_for(enemy_id) then
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
local filename = EntityGetFilename(enemy_id)
|
2024-08-16 20:43:59 +03:00
|
|
|
filename = constants.interned_filename_to_index[filename] or filename
|
|
|
|
|
2024-10-15 17:55:56 -04:00
|
|
|
local x, y, rot = EntityGetTransform(enemy_id)
|
2024-05-09 23:37:50 +03:00
|
|
|
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
2024-05-08 22:13:17 +03:00
|
|
|
local vx, vy = 0, 0
|
2024-05-09 23:37:50 +03:00
|
|
|
if character_data ~= nil then
|
2024-05-08 22:13:17 +03:00
|
|
|
vx, vy = ComponentGetValue2(character_data, "mVelocity")
|
|
|
|
end
|
2024-05-09 23:37:50 +03:00
|
|
|
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "AnimalAIComponent")
|
2024-06-13 21:30:19 +03:00
|
|
|
if ai_component ~= 0 and ai_component ~= nil then
|
2024-05-09 23:37:50 +03:00
|
|
|
ComponentSetValue2(ai_component, "max_distance_to_cam_to_start_hunting", math.pow(2, 29))
|
|
|
|
end
|
2024-05-21 10:48:40 +03:00
|
|
|
local hp, max_hp, has_hp = util.get_ent_health(enemy_id)
|
|
|
|
|
2024-08-23 20:39:38 +03:00
|
|
|
local phys_info = {}
|
|
|
|
local phys_info_2 = {}
|
2024-08-13 14:18:45 +03:00
|
|
|
|
2024-08-23 20:39:38 +03:00
|
|
|
for _, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBodyComponent") or {}) do
|
|
|
|
if phys_component ~= nil and phys_component ~= 0 then
|
2024-08-26 13:34:02 +03:00
|
|
|
local ret, info = pcall(serialize_phys_component, phys_component)
|
|
|
|
if not ret then
|
|
|
|
GamePrint("Physics component has no body, deleting entity")
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_id)
|
2024-08-26 13:34:02 +03:00
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
table.insert(phys_info, info)
|
2024-08-23 20:39:38 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBody2Component") or {}) do
|
|
|
|
if phys_component ~= nil and phys_component ~= 0 then
|
|
|
|
local initialized = ComponentGetValue2(phys_component, "mInitialized")
|
|
|
|
if initialized then
|
2024-08-26 13:34:02 +03:00
|
|
|
local ret, info = pcall(serialize_phys_component, phys_component)
|
|
|
|
if not ret then
|
|
|
|
GamePrint("Physics component has no body, deleting entity")
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_id)
|
2024-08-26 13:34:02 +03:00
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
table.insert(phys_info_2, info)
|
2024-08-16 22:54:58 +03:00
|
|
|
else
|
2024-08-23 20:39:38 +03:00
|
|
|
table.insert(phys_info_2, nil)
|
2024-08-16 22:54:58 +03:00
|
|
|
end
|
2024-08-13 14:18:45 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-21 10:48:40 +03:00
|
|
|
if has_hp then
|
|
|
|
util.ensure_component_present(enemy_id, "LuaComponent", "ew_death_notify", {
|
2024-08-07 17:21:25 +03:00
|
|
|
script_death = "mods/quant.ew/files/resource/cbs/death_notify.lua"
|
2024-05-21 10:48:40 +03:00
|
|
|
})
|
|
|
|
end
|
2024-05-12 18:05:25 +03:00
|
|
|
|
2024-07-10 14:23:57 +03:00
|
|
|
-- TODO: figure out how to sync those.
|
|
|
|
-- local laser_sight_data = nil
|
|
|
|
-- local laser_sight = EntityGetFirstComponentIncludingDisabled(enemy_id, "SpriteComponent", "laser_sight")
|
|
|
|
-- if laser_sight ~= nil and laser_sight ~= 0 then
|
2024-08-15 09:16:29 -04:00
|
|
|
-- -- local x, y, r =
|
2024-07-10 14:23:57 +03:00
|
|
|
-- end
|
2024-08-16 23:58:11 +03:00
|
|
|
|
2024-11-02 18:37:16 -04:00
|
|
|
local death_triggers = {}
|
2024-10-29 07:13:27 -04:00
|
|
|
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
2024-11-02 18:37:16 -04:00
|
|
|
local script = ComponentGetValue2(com, "script_death")
|
|
|
|
if script ~= nil and script ~= "" then
|
|
|
|
table.insert(death_triggers, constants.interned_filename_to_index[script] or script)
|
2024-10-29 07:13:27 -04:00
|
|
|
end
|
|
|
|
end
|
2024-08-16 23:38:38 +03:00
|
|
|
local en_data
|
2024-10-16 06:45:23 -04:00
|
|
|
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
2024-11-03 07:28:17 -05:00
|
|
|
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
2024-10-26 19:32:01 -04:00
|
|
|
if EntityHasTag(enemy_id, "boss_centipede") then
|
|
|
|
en_data = EnemyDataKolmi {
|
|
|
|
enemy_id = enemy_id,
|
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
vx = vx,
|
|
|
|
vy = vy,
|
|
|
|
enabled = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent", "disabled_at_start") ~= nil,
|
|
|
|
}
|
2024-11-10 09:20:34 -05:00
|
|
|
elseif EntityHasTag(enemy_id, "boss_wizard") then
|
|
|
|
local orbs = {false, false, false, false, false, false, false, false}
|
2024-11-16 23:07:10 -05:00
|
|
|
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
2024-11-10 09:20:34 -05:00
|
|
|
local var = EntityGetFirstComponentIncludingDisabled(child, "VariableStorageComponent")
|
|
|
|
if EntityHasTag(child, "touchmagic_immunity") and var ~= nil then
|
|
|
|
local n = ComponentGetValue2(var, "value_int")
|
|
|
|
orbs[n] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
en_data = EnemyDataMom {
|
|
|
|
enemy_id = enemy_id,
|
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
vx = vx,
|
|
|
|
vy = vy,
|
|
|
|
orbs = orbs
|
|
|
|
}
|
2024-10-26 19:32:01 -04:00
|
|
|
elseif worm ~= nil then
|
2024-11-03 07:28:17 -05:00
|
|
|
local tx, ty = ComponentGetValue2(worm, "mTargetVec")
|
2024-10-16 06:45:23 -04:00
|
|
|
en_data = EnemyDataWorm {
|
|
|
|
enemy_id = enemy_id,
|
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
vx = vx,
|
|
|
|
vy = vy,
|
|
|
|
tx = tx,
|
|
|
|
ty = ty,
|
|
|
|
}
|
|
|
|
elseif math.abs(vx) < 0.01 and math.abs(vy) < 0.01 then
|
|
|
|
en_data = EnemyDataNoMotion {
|
|
|
|
enemy_id = enemy_id,
|
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
}
|
|
|
|
elseif EntityGetFirstComponentIncludingDisabled(enemy_id, "AdvancedFishAIComponent") ~= nil then
|
|
|
|
en_data = EnemyDataFish {
|
2024-08-16 23:58:11 +03:00
|
|
|
enemy_id = enemy_id,
|
2024-08-16 23:38:38 +03:00
|
|
|
x = x,
|
|
|
|
y = y,
|
2024-10-16 06:45:23 -04:00
|
|
|
vx = vx,
|
|
|
|
vy = vy,
|
|
|
|
r = math.floor((rot % FULL_TURN) / FULL_TURN * 255),
|
2024-08-16 23:38:38 +03:00
|
|
|
}
|
|
|
|
else
|
2024-10-16 06:45:23 -04:00
|
|
|
en_data = EnemyData {
|
2024-08-16 23:58:11 +03:00
|
|
|
enemy_id = enemy_id,
|
2024-08-16 23:38:38 +03:00
|
|
|
x = x,
|
|
|
|
y = y,
|
|
|
|
vx = vx,
|
|
|
|
vy = vy,
|
2024-08-17 00:55:05 +03:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2024-11-03 18:04:14 -05:00
|
|
|
local wand
|
2024-08-28 21:46:37 -04:00
|
|
|
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
|
|
|
if inv ~= nil then
|
|
|
|
local item = ComponentGetValue2(inv, "mActualActiveItem")
|
|
|
|
if item ~= nil and EntityGetIsAlive(item) then
|
2024-11-03 18:04:14 -05:00
|
|
|
if not EntityHasTag(item, "ew_global_item") then
|
|
|
|
item_sync.make_item_global(item)
|
|
|
|
else
|
|
|
|
wand = item_sync.get_global_item_id(item)
|
2024-08-29 07:29:48 -04:00
|
|
|
end
|
2024-08-28 21:46:37 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-10-04 18:24:26 -04:00
|
|
|
local effect_data = effect_sync.get_sync_data(enemy_id, true)
|
2024-08-29 09:19:04 -04:00
|
|
|
|
2024-08-29 11:10:44 -04:00
|
|
|
local sprite = EntityGetFirstComponent(enemy_id, "SpriteComponent")
|
|
|
|
local animation
|
|
|
|
if sprite ~= nil then
|
|
|
|
animation = ComponentGetValue2(sprite, "rect_animation")
|
|
|
|
end
|
|
|
|
|
2024-11-16 10:29:27 -05:00
|
|
|
local dont_cull = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent") ~= nil
|
|
|
|
or worm ~= nil
|
2024-09-29 09:41:48 -04:00
|
|
|
|
2024-11-16 09:53:14 -05:00
|
|
|
local stains = stain_sync.get_stains(enemy_id)
|
|
|
|
|
|
|
|
table.insert(enemy_data_list, {filename, en_data, phys_info, phys_info_2, wand,
|
|
|
|
effect_data, animation, dont_cull, death_triggers, stains})
|
2024-05-08 20:33:41 +03:00
|
|
|
::continue::
|
|
|
|
end
|
2024-05-21 10:48:40 +03:00
|
|
|
|
2024-05-30 12:41:56 +03:00
|
|
|
rpc.handle_enemy_data(enemy_data_list)
|
|
|
|
if #dead_entities > 0 then
|
|
|
|
rpc.handle_death_data(dead_entities)
|
|
|
|
end
|
2024-06-02 13:49:11 +03:00
|
|
|
dead_entities = {}
|
2024-08-17 12:58:01 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
local function host_upload_health()
|
|
|
|
local entities = get_sync_entities()
|
|
|
|
|
|
|
|
local enemy_health_list = {}
|
|
|
|
for i, enemy_id in ipairs(entities) do
|
|
|
|
if not world_exists_for(enemy_id) then
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
|
|
|
|
local hp, max_hp, has_hp = util.get_ent_health(enemy_id)
|
|
|
|
|
|
|
|
if has_hp then
|
|
|
|
table.insert(enemy_health_list, HpData {
|
|
|
|
enemy_id = enemy_id,
|
|
|
|
hp = hp,
|
|
|
|
max_hp = max_hp,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
|
2024-08-17 00:55:05 +03:00
|
|
|
if #enemy_health_list > 0 then
|
|
|
|
rpc.handle_enemy_health(enemy_health_list)
|
|
|
|
end
|
2024-05-08 20:33:41 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function enemy_sync.client_cleanup()
|
2024-08-17 18:46:04 +03:00
|
|
|
local entities = get_sync_entities(true)
|
2024-10-12 10:20:54 -04:00
|
|
|
for _, enemy_id in ipairs(entities) do
|
2024-05-08 20:33:41 +03:00
|
|
|
if not EntityHasTag(enemy_id, "ew_replicated") then
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_id)
|
2024-08-13 18:43:01 +03:00
|
|
|
elseif not spawned_by_us[enemy_id] then
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_id)
|
2024-05-08 20:33:41 +03:00
|
|
|
end
|
|
|
|
end
|
2024-05-10 10:30:14 +03:00
|
|
|
for remote_id, enemy_data in pairs(ctx.entity_by_remote_id) do
|
2024-11-13 18:54:28 -05:00
|
|
|
if frame > enemy_data.frame then
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_data.id)
|
2024-05-10 10:30:14 +03:00
|
|
|
ctx.entity_by_remote_id[remote_id] = nil
|
2024-05-08 20:33:41 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-30 12:41:56 +03:00
|
|
|
function enemy_sync.on_world_update_host()
|
2024-08-30 17:12:17 -04:00
|
|
|
local int = 3 --ctx.proxy_opt.enemy_sync_interval
|
|
|
|
local num = 2
|
|
|
|
if int == 1 then
|
2024-08-29 18:01:52 -04:00
|
|
|
num = 0
|
2024-08-30 17:12:17 -04:00
|
|
|
elseif int == 2 then
|
|
|
|
num = 1
|
2024-08-29 18:01:52 -04:00
|
|
|
end
|
2024-08-30 17:12:17 -04:00
|
|
|
if GameGetFrameNum() % int == num then
|
2024-05-30 12:41:56 +03:00
|
|
|
enemy_sync.host_upload_entities()
|
|
|
|
end
|
2024-08-17 12:58:01 +03:00
|
|
|
if GameGetFrameNum() % 10 == 5 then
|
|
|
|
host_upload_health()
|
|
|
|
end
|
2024-05-30 12:41:56 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function enemy_sync.on_world_update_client()
|
2024-11-13 18:54:28 -05:00
|
|
|
if GameGetFrameNum() % 10 == 1 then
|
2024-05-30 12:41:56 +03:00
|
|
|
enemy_sync.client_cleanup()
|
|
|
|
end
|
2024-08-15 23:23:24 +03:00
|
|
|
if GameGetFrameNum() % (60*60) == 1 then
|
|
|
|
times_spawned_last_minute = {}
|
|
|
|
end
|
2024-05-30 12:41:56 +03:00
|
|
|
end
|
|
|
|
|
2024-10-12 10:20:54 -04:00
|
|
|
local function sync_enemy(enemy_info_raw, force_no_cull)
|
|
|
|
local filename = enemy_info_raw[1]
|
|
|
|
filename = constants.interned_index_to_filename[filename] or filename
|
|
|
|
|
|
|
|
local en_data = enemy_info_raw[2]
|
2024-11-15 20:45:57 -05:00
|
|
|
local dont_cull = enemy_info_raw[8]
|
|
|
|
local death_triggers = enemy_info_raw[9]
|
2024-11-16 09:53:14 -05:00
|
|
|
local stains = enemy_info_raw[10]
|
2024-10-12 10:20:54 -04:00
|
|
|
local remote_enemy_id = en_data.enemy_id
|
|
|
|
local x, y = en_data.x, en_data.y
|
|
|
|
if not force_no_cull and not dont_cull then
|
|
|
|
local my_x, my_y = EntityGetTransform(ctx.my_player.entity)
|
2024-10-12 15:55:00 -04:00
|
|
|
if my_x == nil then
|
|
|
|
goto continue
|
|
|
|
end
|
2024-10-12 10:20:54 -04:00
|
|
|
local c_x, c_y = GameGetCameraPos()
|
|
|
|
local dx, dy = my_x - x, my_y - y
|
|
|
|
local cdx, cdy = c_x - x, c_y - y
|
|
|
|
if dx * dx + dy * dy > DISTANCE_LIMIT * DISTANCE_LIMIT and cdx * cdx + cdy * cdy > DISTANCE_LIMIT * DISTANCE_LIMIT then
|
|
|
|
if ctx.entity_by_remote_id[remote_enemy_id] ~= nil then
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(ctx.entity_by_remote_id[remote_enemy_id].id)
|
2024-10-12 10:20:54 -04:00
|
|
|
ctx.entity_by_remote_id[remote_enemy_id] = nil
|
|
|
|
end
|
|
|
|
unsynced_enemys[remote_enemy_id] = enemy_info_raw
|
|
|
|
goto continue
|
|
|
|
else
|
|
|
|
unsynced_enemys[remote_enemy_id] = nil
|
|
|
|
end
|
|
|
|
else
|
|
|
|
unsynced_enemys[remote_enemy_id] = nil
|
|
|
|
end
|
|
|
|
local vx = 0
|
|
|
|
local vy = 0
|
2024-10-16 06:45:23 -04:00
|
|
|
if ffi.typeof(en_data) ~= EnemyDataNoMotion then
|
2024-10-12 10:20:54 -04:00
|
|
|
vx, vy = en_data.vx, en_data.vy
|
|
|
|
end
|
2024-11-15 20:45:57 -05:00
|
|
|
local phys_infos = enemy_info_raw[3]
|
|
|
|
local phys_infos_2 = enemy_info_raw[4]
|
|
|
|
local gid = enemy_info_raw[5]
|
|
|
|
local effects = enemy_info_raw[6]
|
|
|
|
local animation = enemy_info_raw[7]
|
2024-10-12 10:20:54 -04:00
|
|
|
local has_died = filename == nil
|
|
|
|
|
2024-11-14 08:46:31 -05:00
|
|
|
local frame_now = GameGetFrameNum()
|
2024-10-12 10:20:54 -04:00
|
|
|
|
|
|
|
--[[if confirmed_kills[remote_enemy_id] then
|
|
|
|
goto continue
|
|
|
|
end]]
|
|
|
|
|
|
|
|
if ctx.entity_by_remote_id[remote_enemy_id] ~= nil and not EntityGetIsAlive(ctx.entity_by_remote_id[remote_enemy_id].id) then
|
|
|
|
ctx.entity_by_remote_id[remote_enemy_id] = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
if ctx.entity_by_remote_id[remote_enemy_id] == nil then
|
2024-11-14 08:46:31 -05:00
|
|
|
if filename == nil or filename == "" or not ModDoesFileExist(filename) then
|
2024-10-12 10:20:54 -04:00
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
times_spawned_last_minute[remote_enemy_id] = (times_spawned_last_minute[remote_enemy_id] or 0) + 1
|
|
|
|
if times_spawned_last_minute[remote_enemy_id] > 5 then
|
|
|
|
if times_spawned_last_minute[remote_enemy_id] == 6 then
|
|
|
|
print("Entity has been spawned again more than 5 times in last minute, skipping "..filename)
|
|
|
|
end
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
local enemy_id
|
2024-11-14 23:56:34 +03:00
|
|
|
enemy_id = EntityLoad(filename, x, y)
|
2024-10-12 10:20:54 -04:00
|
|
|
spawned_by_us[enemy_id] = true
|
|
|
|
EntityAddTag(enemy_id, "ew_replicated")
|
|
|
|
EntityAddTag(enemy_id, "polymorphable_NOT")
|
|
|
|
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
|
|
|
local script = ComponentGetValue2(com, "script_damage_received")
|
2024-11-16 16:31:35 -05:00
|
|
|
if (script ~= nil
|
|
|
|
and (script == "data/scripts/animals/leader_damage.lua"
|
|
|
|
or script == "data/scripts/animals/giantshooter_death.lua"
|
|
|
|
or script == "data/scripts/animals/blob_damage.lua"))
|
|
|
|
or ComponentGetValue2(com, "script_source_file") == "data/scripts/props/suspended_container_physics_objects.lua" then
|
2024-10-12 10:20:54 -04:00
|
|
|
EntityRemoveComponent(enemy_id, com)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
EntityAddComponent2(enemy_id, "LuaComponent", {_tags="ew_immortal", script_damage_about_to_be_received = "mods/quant.ew/files/resource/cbs/immortal.lua"})
|
|
|
|
local damage_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
|
|
|
if damage_component and damage_component ~= 0 then
|
|
|
|
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
|
|
|
end
|
2024-10-15 17:55:56 -04:00
|
|
|
for _, name in ipairs({"AnimalAIComponent", "PhysicsAIComponent", "CameraBoundComponent", "AdvancedFishAIComponent", "AIAttackComponent"}) do
|
2024-10-12 10:20:54 -04:00
|
|
|
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, name)
|
|
|
|
if ai_component ~= 0 then
|
|
|
|
EntityRemoveComponent(enemy_id, ai_component)
|
|
|
|
end
|
|
|
|
end
|
2024-11-14 08:46:31 -05:00
|
|
|
ctx.entity_by_remote_id[remote_enemy_id] = {id = enemy_id, frame = frame_now}
|
2024-10-12 10:20:54 -04:00
|
|
|
|
|
|
|
for _, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBody2Component") or {}) do
|
|
|
|
if phys_component ~= nil and phys_component ~= 0 then
|
|
|
|
ComponentSetValue2(phys_component, "destroy_body_if_entity_destroyed", true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Make sure stuff doesn't decide to explode on clients by itself.
|
|
|
|
local expl_component = EntityGetFirstComponent(enemy_id, "ExplodeOnDamageComponent")
|
|
|
|
if expl_component ~= nil and expl_component ~= 0 then
|
|
|
|
ComponentSetValue2(expl_component, "explode_on_damage_percent", 0)
|
|
|
|
ComponentSetValue2(expl_component, "physics_body_modified_death_probability", 0)
|
|
|
|
ComponentSetValue2(expl_component, "explode_on_death_percent", 0)
|
|
|
|
end
|
|
|
|
local pick_up = EntityGetFirstComponentIncludingDisabled(enemy_id, "ItemPickUpperComponent")
|
|
|
|
if pick_up ~= nil then
|
|
|
|
EntityRemoveComponent(enemy_id, pick_up)
|
|
|
|
end
|
|
|
|
for _, sprite in pairs(EntityGetComponent(enemy_id, "SpriteComponent", "character") or {}) do
|
|
|
|
ComponentAddTag(sprite, "ew_sprite")
|
|
|
|
ComponentRemoveTag(sprite, "character")
|
|
|
|
end
|
2024-11-14 23:56:34 +03:00
|
|
|
|
2024-11-16 10:29:27 -05:00
|
|
|
local ghost = EntityGetFirstComponentIncludingDisabled(enemy_id, "GhostComponent")
|
|
|
|
if ghost ~= nil then
|
|
|
|
ComponentSetValue2(ghost, "die_if_no_home", false)
|
|
|
|
end
|
2024-11-16 16:31:35 -05:00
|
|
|
util.make_ephemerial(enemy_id)
|
2024-10-12 10:20:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
local enemy_data_new = ctx.entity_by_remote_id[remote_enemy_id]
|
2024-11-14 08:46:31 -05:00
|
|
|
enemy_data_new.frame = frame_now
|
2024-10-12 10:20:54 -04:00
|
|
|
local enemy_id = enemy_data_new.id
|
|
|
|
|
|
|
|
for i, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBodyComponent") or {}) do
|
|
|
|
local phys_info = phys_infos[i]
|
|
|
|
if phys_component ~= nil and phys_component ~= 0 and phys_info ~= nil then
|
|
|
|
deserialize_phys_component(phys_component, phys_info)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for i, phys_component in ipairs(EntityGetComponent(enemy_id, "PhysicsBody2Component") or {}) do
|
|
|
|
local phys_info = phys_infos_2[i]
|
|
|
|
if phys_component ~= nil and phys_component ~= 0 and phys_info ~= nil then
|
|
|
|
-- A physics body doesn't exist otherwise, causing a crash
|
|
|
|
local initialized = ComponentGetValue2(phys_component, "mInitialized")
|
|
|
|
if initialized then
|
|
|
|
deserialize_phys_component(phys_component, phys_info)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not has_died then
|
|
|
|
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "CharacterDataComponent")
|
|
|
|
if character_data ~= nil then
|
|
|
|
ComponentSetValue2(character_data, "mVelocity", vx, vy)
|
|
|
|
end
|
|
|
|
local velocity_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
|
|
|
if velocity_data ~= nil then
|
|
|
|
ComponentSetValue2(velocity_data, "mVelocity", vx, vy)
|
|
|
|
end
|
2024-10-16 06:45:23 -04:00
|
|
|
if ffi.typeof(en_data) == EnemyDataFish then
|
|
|
|
EntitySetTransform(enemy_id, x, y, en_data.r / 255 * FULL_TURN)
|
|
|
|
else
|
|
|
|
EntitySetTransform(enemy_id, x, y)
|
|
|
|
end
|
|
|
|
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
2024-11-03 07:28:17 -05:00
|
|
|
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
2024-10-16 06:45:23 -04:00
|
|
|
if worm ~= nil and ffi.typeof(en_data) == EnemyDataWorm then
|
|
|
|
local tx, ty = en_data.tx, en_data.ty
|
2024-11-03 07:28:17 -05:00
|
|
|
ComponentSetValue2(worm, "mTargetVec", tx, ty)
|
2024-10-16 06:45:23 -04:00
|
|
|
end
|
2024-10-26 19:32:01 -04:00
|
|
|
if ffi.typeof(en_data) == EnemyDataKolmi and en_data.enabled then
|
|
|
|
local lua_components = EntityGetComponentIncludingDisabled(enemy_id, "LuaComponent") or {}
|
|
|
|
for _, c in ipairs(lua_components) do
|
|
|
|
EntityRemoveComponent(enemy_id, c)
|
|
|
|
end
|
|
|
|
EntitySetComponentsWithTagEnabled(enemy_id, "enabled_at_start", false)
|
|
|
|
EntitySetComponentsWithTagEnabled(enemy_id, "disabled_at_start", true)
|
|
|
|
end
|
2024-11-02 18:37:16 -04:00
|
|
|
|
|
|
|
local indexed = {}
|
2024-10-29 07:13:27 -04:00
|
|
|
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
2024-11-02 18:37:16 -04:00
|
|
|
local script = ComponentGetValue2(com, "script_death")
|
|
|
|
local has = false
|
|
|
|
for _, inx in ipairs(death_triggers) do
|
|
|
|
local script2 = constants.interned_index_to_filename[inx] or inx
|
|
|
|
if script == script2 then
|
|
|
|
has = true
|
|
|
|
indexed[script] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not has then
|
2024-10-29 07:13:27 -04:00
|
|
|
ComponentSetValue2(com, "script_death", "")
|
|
|
|
end
|
|
|
|
end
|
2024-11-02 18:37:16 -04:00
|
|
|
for _, inx in ipairs(death_triggers) do
|
|
|
|
local script = constants.interned_index_to_filename[inx] or inx
|
|
|
|
if indexed[script] == nil then
|
|
|
|
EntityAddComponent(enemy_id, "LuaComponent", { script_death = script,
|
|
|
|
execute_every_n_frame = "-1"})
|
|
|
|
end
|
|
|
|
end
|
2024-11-10 09:20:34 -05:00
|
|
|
if ffi.typeof(en_data) == EnemyDataMom then
|
|
|
|
local orbs = en_data.orbs
|
2024-11-16 23:07:10 -05:00
|
|
|
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
2024-11-10 09:20:34 -05:00
|
|
|
local var = EntityGetFirstComponentIncludingDisabled(child, "VariableStorageComponent")
|
|
|
|
local damage_component = EntityGetFirstComponentIncludingDisabled(child, "DamageModelComponent")
|
|
|
|
if EntityHasTag(child, "touchmagic_immunity") and var ~= nil then
|
|
|
|
local n = ComponentGetValue2(var, "value_int")
|
|
|
|
if orbs[n] then
|
|
|
|
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
|
|
|
else
|
|
|
|
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", false)
|
|
|
|
EntityKill(child)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2024-11-16 09:53:14 -05:00
|
|
|
effect_sync.apply_effects(effects, enemy_id, true)
|
|
|
|
if stains ~= nil then
|
|
|
|
stain_sync.sync_stains(stains, enemy_id)
|
|
|
|
end
|
2024-10-29 07:13:27 -04:00
|
|
|
end
|
2024-10-12 10:20:54 -04:00
|
|
|
|
|
|
|
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
|
|
|
local item
|
|
|
|
if inv ~= nil then
|
|
|
|
item = ComponentGetValue2(inv, "mActualActiveItem")
|
|
|
|
end
|
2024-11-03 18:04:14 -05:00
|
|
|
if gid ~= nil and (item == nil or item == 0 or not EntityGetIsAlive(item)) then
|
|
|
|
local wand = item_sync.find_by_gid(gid)
|
|
|
|
if wand ~= nil then
|
2024-10-12 10:20:54 -04:00
|
|
|
EntityAddTag(wand, "ew_client_item")
|
|
|
|
local found = false
|
|
|
|
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
|
|
|
if EntityGetName(child) == "inventory_quick" then
|
2024-11-03 19:16:38 -05:00
|
|
|
if EntityGetParent(wand) ~= nil then
|
|
|
|
EntityRemoveFromParent(wand)
|
|
|
|
end
|
2024-10-12 10:20:54 -04:00
|
|
|
EntityAddChild(child, wand)
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not found then
|
|
|
|
local inv_quick = EntityCreateNew("inventory_quick")
|
|
|
|
EntityAddChild(enemy_id, inv_quick)
|
|
|
|
EntityAddChild(inv_quick, wand)
|
2024-11-03 18:04:14 -05:00
|
|
|
end
|
|
|
|
if EntityGetFirstComponent(enemy_id, "Inventory2Component") == nil then
|
2024-10-12 10:20:54 -04:00
|
|
|
EntityAddComponent2(enemy_id, "Inventory2Component")
|
|
|
|
end
|
|
|
|
EntitySetComponentsWithTagEnabled(wand, "enabled_in_world", false)
|
|
|
|
EntitySetComponentsWithTagEnabled(wand, "enabled_in_hand", true)
|
|
|
|
EntitySetComponentsWithTagEnabled(wand, "enabled_in_inventory", false)
|
|
|
|
np.SetActiveHeldEntity(enemy_id, wand, false, false)
|
|
|
|
else
|
2024-11-03 18:04:14 -05:00
|
|
|
item_sync.rpc.request_send_again(gid)
|
2024-10-12 10:20:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, sprite in pairs(EntityGetComponent(enemy_id, "SpriteComponent", "ew_sprite") or {}) do
|
2024-11-16 10:29:27 -05:00
|
|
|
if animation ~= nil then
|
|
|
|
ComponentSetValue2(sprite, "rect_animation", animation)
|
|
|
|
ComponentSetValue2(sprite, "next_rect_animation", animation)
|
|
|
|
end
|
2024-10-12 10:20:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
::continue::
|
|
|
|
end
|
|
|
|
|
2024-05-30 12:41:56 +03:00
|
|
|
rpc.opts_reliable()
|
|
|
|
function rpc.handle_death_data(death_data)
|
2024-06-02 13:49:11 +03:00
|
|
|
for _, remote_data in ipairs(death_data) do
|
|
|
|
local remote_id = remote_data[1]
|
2024-08-29 09:19:04 -04:00
|
|
|
--[[if confirmed_kills[remote_id] then
|
2024-06-17 20:57:49 +03:00
|
|
|
GamePrint("Remote id has been killed already..?")
|
|
|
|
goto continue
|
|
|
|
end
|
2024-08-29 09:19:04 -04:00
|
|
|
confirmed_kills[remote_id] = true]]
|
2024-06-02 13:49:11 +03:00
|
|
|
local responsible_entity = 0
|
|
|
|
local peer_data = player_fns.peer_get_player_data(remote_data[2], true)
|
|
|
|
if peer_data ~= nil then
|
|
|
|
responsible_entity = peer_data.entity
|
|
|
|
elseif ctx.entity_by_remote_id[remote_data[2]] ~= nil then
|
|
|
|
responsible_entity = ctx.entity_by_remote_id[remote_data[2]]
|
|
|
|
end
|
|
|
|
|
2024-10-12 10:20:54 -04:00
|
|
|
if unsynced_enemys[remote_id] ~= nil then
|
|
|
|
sync_enemy(unsynced_enemys[remote_id], true)
|
|
|
|
end
|
2024-05-19 12:03:03 +03:00
|
|
|
local enemy_data = ctx.entity_by_remote_id[remote_id]
|
2024-06-14 17:07:52 +03:00
|
|
|
if enemy_data ~= nil and EntityGetIsAlive(enemy_data.id) then
|
2024-05-19 12:03:03 +03:00
|
|
|
local enemy_id = enemy_data.id
|
2024-05-30 11:57:02 +03:00
|
|
|
local immortal = EntityGetFirstComponentIncludingDisabled(enemy_id, "LuaComponent", "ew_immortal")
|
2024-06-09 19:55:07 +03:00
|
|
|
if immortal ~= 0 then
|
2024-05-30 11:57:02 +03:00
|
|
|
EntityRemoveComponent(enemy_id, immortal)
|
|
|
|
end
|
|
|
|
local protection_component_id = GameGetGameEffect(enemy_id, "PROTECTION_ALL")
|
2024-06-01 17:10:16 +03:00
|
|
|
if protection_component_id ~= 0 then
|
2024-05-30 11:57:02 +03:00
|
|
|
EntitySetComponentIsEnabled(enemy_id, protection_component_id, false)
|
|
|
|
end
|
2024-05-21 10:48:40 +03:00
|
|
|
|
2024-06-17 20:57:49 +03:00
|
|
|
local damage_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
|
|
|
if damage_component and damage_component ~= 0 then
|
|
|
|
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", false)
|
|
|
|
end
|
2024-08-18 13:48:46 -04:00
|
|
|
|
2024-08-16 12:28:35 +03:00
|
|
|
-- Enable explosion back
|
|
|
|
local expl_component = EntityGetFirstComponent(enemy_id, "ExplodeOnDamageComponent")
|
|
|
|
if expl_component ~= nil and expl_component ~= 0 then
|
|
|
|
ComponentSetValue2(expl_component, "explode_on_death_percent", 1)
|
|
|
|
end
|
2024-06-17 20:57:49 +03:00
|
|
|
|
2024-05-19 12:03:03 +03:00
|
|
|
local current_hp = util.get_ent_health(enemy_id)
|
|
|
|
local dmg = current_hp
|
|
|
|
if dmg > 0 then
|
2024-06-02 13:49:11 +03:00
|
|
|
EntityInflictDamage(enemy_id, dmg+0.1, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity)
|
2024-05-19 12:03:03 +03:00
|
|
|
end
|
2024-08-16 12:28:35 +03:00
|
|
|
|
2024-06-02 13:49:11 +03:00
|
|
|
EntityInflictDamage(enemy_id, 1000000000, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity) -- Just to be sure
|
2024-11-15 20:45:57 -05:00
|
|
|
EntityKill(enemy_id)
|
2024-05-19 12:03:03 +03:00
|
|
|
end
|
2024-06-17 20:57:49 +03:00
|
|
|
::continue::
|
2024-05-19 12:03:03 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-30 12:41:56 +03:00
|
|
|
function rpc.handle_enemy_data(enemy_data)
|
2024-11-13 18:54:28 -05:00
|
|
|
frame = GameGetFrameNum()
|
2024-05-08 20:33:41 +03:00
|
|
|
for _, enemy_info_raw in ipairs(enemy_data) do
|
2024-10-12 10:20:54 -04:00
|
|
|
sync_enemy(enemy_info_raw, false)
|
2024-08-17 00:55:05 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function rpc.handle_enemy_health(enemy_health_data)
|
|
|
|
for _, en_data in ipairs(enemy_health_data) do
|
|
|
|
local remote_enemy_id = en_data.enemy_id
|
|
|
|
local hp = en_data.hp
|
|
|
|
local max_hp = en_data.max_hp
|
|
|
|
|
|
|
|
if ctx.entity_by_remote_id[remote_enemy_id] == nil or not EntityGetIsAlive(ctx.entity_by_remote_id[remote_enemy_id].id) then
|
|
|
|
goto continue
|
|
|
|
end
|
|
|
|
local enemy_data = ctx.entity_by_remote_id[remote_enemy_id]
|
|
|
|
local enemy_id = enemy_data.id
|
|
|
|
|
2024-05-12 18:05:25 +03:00
|
|
|
local current_hp = util.get_ent_health(enemy_id)
|
|
|
|
local dmg = current_hp-hp
|
|
|
|
if dmg > 0 then
|
2024-06-13 15:56:33 +03:00
|
|
|
-- Make sure the enemy doesn't die from the next EntityInflictDamage.
|
2024-10-14 07:50:33 -04:00
|
|
|
if EntityGetName(enemy_id) ~= "$animal_boss_sky" then
|
|
|
|
util.set_ent_health(enemy_id, {dmg*2, dmg*2})
|
|
|
|
else
|
|
|
|
util.set_ent_health(enemy_id, {hp + dmg, max_hp})
|
|
|
|
end
|
2024-06-13 15:56:33 +03:00
|
|
|
-- Deal damage, so that game displays damage numbers.
|
2024-06-02 14:00:55 +03:00
|
|
|
EntityInflictDamage(enemy_id, dmg, "DAMAGE_CURSE", "", "NONE", 0, 0, GameGetWorldStateEntity())
|
2024-05-12 18:05:25 +03:00
|
|
|
end
|
2024-05-08 20:33:41 +03:00
|
|
|
util.set_ent_health(enemy_id, {hp, max_hp})
|
2024-05-19 12:03:03 +03:00
|
|
|
::continue::
|
2024-05-08 20:33:41 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-17 17:16:00 +03:00
|
|
|
function enemy_sync.on_projectile_fired(shooter_id, projectile_id, initial_rng, position_x, position_y, target_x, target_y, send_message,
|
|
|
|
unknown1, multicast_index, unknown3)
|
|
|
|
local not_a_player = not EntityHasTag(shooter_id, "ew_no_enemy_sync") and not EntityHasTag(shooter_id, "player_unit") and not EntityHasTag(shooter_id, "ew_client")
|
|
|
|
if not_a_player and ctx.is_host then
|
|
|
|
local projectileComponent = EntityGetFirstComponentIncludingDisabled(projectile_id, "ProjectileComponent")
|
2024-08-21 17:46:01 -04:00
|
|
|
if projectileComponent ~= nil then
|
|
|
|
local entity_that_shot = ComponentGetValue2(projectileComponent, "mEntityThatShot")
|
|
|
|
if entity_that_shot == 0 then
|
2024-10-21 14:14:35 +03:00
|
|
|
rpc.replicate_projectile(util.serialize_entity(projectile_id), position_x, position_y, target_x, target_y, shooter_id, initial_rng)
|
2024-08-21 17:46:01 -04:00
|
|
|
end
|
2024-06-17 17:16:00 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
rpc.opts_reliable()
|
|
|
|
function rpc.replicate_projectile(seri_ent, position_x, position_y, target_x, target_y, remote_source_ent, rng)
|
2024-06-18 14:25:19 +03:00
|
|
|
if rng ~= nil then
|
|
|
|
np.SetProjectileSpreadRNG(rng)
|
|
|
|
end
|
|
|
|
if ctx.entity_by_remote_id[remote_source_ent] == nil then
|
|
|
|
return
|
|
|
|
end
|
2024-06-17 17:16:00 +03:00
|
|
|
local source_ent = ctx.entity_by_remote_id[remote_source_ent].id
|
2024-10-21 14:14:35 +03:00
|
|
|
local ent = util.deserialize_entity(seri_ent)
|
2024-06-17 17:16:00 +03:00
|
|
|
GameShootProjectile(source_ent, position_x, position_y, target_x, target_y, ent)
|
|
|
|
end
|
|
|
|
|
2024-08-15 09:16:29 -04:00
|
|
|
return enemy_sync
|