mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
refactor a bunch of things to get ready for rust world sync
This commit is contained in:
parent
4ff165b815
commit
067570672d
32 changed files with 540 additions and 3126 deletions
|
@ -187,9 +187,7 @@ end
|
|||
|
||||
local DEST_PROXY = 1
|
||||
local DEST_BROADCAST = 2
|
||||
local DEST_PROXY_BIN = 3
|
||||
local DEST_FLAGS = 0
|
||||
|
||||
local MOD_RELIABLE = 4 -- 0b101
|
||||
|
||||
function net.send_internal(msg, dest, reliable)
|
||||
|
@ -228,10 +226,6 @@ function net.proxy_send(key, value)
|
|||
net.send_internal(key .. " " .. value, DEST_PROXY)
|
||||
end
|
||||
|
||||
function net.proxy_bin_send(key, value)
|
||||
ewext.netmanager_send(string.char(DEST_PROXY_BIN, key) .. value)
|
||||
end
|
||||
|
||||
function net.proxy_notify_game_over()
|
||||
net.proxy_send("game_over", 1)
|
||||
end
|
||||
|
|
|
@ -1,914 +0,0 @@
|
|||
local item_sync = dofile_once("mods/quant.ew/files/system/item_sync.lua")
|
||||
local effect_sync = dofile_once("mods/quant.ew/files/system/game_effect_sync/game_effect_sync.lua")
|
||||
local stain_sync = dofile_once("mods/quant.ew/files/system/effect_data_sync/effect_data_sync.lua")
|
||||
|
||||
local ffi = require("ffi")
|
||||
local rpc = net.new_rpc_namespace()
|
||||
|
||||
local EnemyData = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
})
|
||||
|
||||
-- Variant of EnemyData for when we don't have any motion (or no VelocityComponent).
|
||||
local EnemyDataNoMotion = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y" },
|
||||
})
|
||||
|
||||
local EnemyDataWorm = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy", "tx", "ty" },
|
||||
})
|
||||
|
||||
local EnemyDataKolmi = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
bool = { "enabled" },
|
||||
vecfloat = { "legs" },
|
||||
})
|
||||
|
||||
local EnemyDataMom = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
vecbool = { "orbs" },
|
||||
})
|
||||
|
||||
local EnemyDataFish = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "x", "y", "vx", "vy" },
|
||||
u8 = { "r" },
|
||||
})
|
||||
|
||||
local HpData = util.make_type({
|
||||
u32 = { "enemy_id" },
|
||||
f32 = { "hp", "max_hp" },
|
||||
})
|
||||
|
||||
local should_wait = {}
|
||||
|
||||
local first = true
|
||||
|
||||
local FULL_TURN = math.pi * 2
|
||||
|
||||
local frame = 0
|
||||
|
||||
local enemy_sync = {}
|
||||
|
||||
local unsynced_enemys = {}
|
||||
|
||||
local dead_entities = {}
|
||||
--this basically never happens, doesn't seem that useful anymore. Perhaps should be removed to conserve memory.
|
||||
--local confirmed_kills = {}
|
||||
|
||||
local spawned_by_us = {}
|
||||
|
||||
-- HACK
|
||||
local times_spawned_last_minute = {}
|
||||
|
||||
local DISTANCE_LIMIT = 128 * 6
|
||||
|
||||
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
|
||||
|
||||
util.add_cross_call("ew_es_death_notify", function(enemy_id, responsible_id)
|
||||
local player_data = player_fns.get_player_data_by_local_entity_id(responsible_id)
|
||||
local responsible
|
||||
if player_data ~= nil then
|
||||
responsible = player_data.peer_id
|
||||
else
|
||||
responsible = responsible_id
|
||||
end
|
||||
local damage = EntityGetFirstComponentIncludingDisabled(enemy_id, "DamageModelComponent")
|
||||
table.insert(dead_entities, { enemy_id, responsible, ComponentGetValue2(damage, "wait_for_kill_flag_on_death") })
|
||||
end)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
local function get_sync_entities(return_all)
|
||||
local entities = EntityGetWithTag("enemy") or {}
|
||||
table_extend(entities, EntityGetWithTag("ew_enemy_sync_extra"))
|
||||
table_extend(entities, EntityGetWithTag("plague_rat"))
|
||||
table_extend(entities, EntityGetWithTag("seed_f"))
|
||||
table_extend(entities, EntityGetWithTag("seed_e"))
|
||||
table_extend(entities, EntityGetWithTag("seed_d"))
|
||||
table_extend(entities, EntityGetWithTag("seed_c"))
|
||||
table_extend(entities, EntityGetWithTag("perk_fungus_tiny"))
|
||||
table_extend(entities, EntityGetWithTag("helpless_animal"))
|
||||
table_extend_filtered(entities, EntityGetWithTag("hittable"), function(ent)
|
||||
local name = EntityGetName(ent)
|
||||
local file = EntityGetFilename(ent)
|
||||
return name == "$item_essence_stone"
|
||||
or name == "$animal_fish_giga"
|
||||
or file == "data/entities/buildings/spittrap_left.xml"
|
||||
or file == "data/entities/buildings/spittrap_right.xml"
|
||||
or file == "data/entities/buildings/thundertrap_left.xml"
|
||||
or file == "data/entities/buildings/thundertrap_right.xml"
|
||||
or file == "data/entities/buildings/arrowtrap_left.xml"
|
||||
or file == "data/entities/buildings/arrowtrap_right.xml"
|
||||
or file == "data/entities/buildings/firetrap_left.xml"
|
||||
or file == "data/entities/buildings/firetrap_right.xml"
|
||||
--data/entities/buildings/statue_trap_left.xml
|
||||
--data/entities/buildings/statue_trap_right.xml
|
||||
end)
|
||||
table_extend_filtered(entities, EntityGetWithTag("prop_physics"), function(ent)
|
||||
local f = EntityGetFilename(ent)
|
||||
if f ~= nil then
|
||||
return constants.phys_sync_allowed[f]
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
local entities2 = {}
|
||||
if return_all then
|
||||
table_extend_filtered(entities2, entities, function(ent)
|
||||
return not EntityHasTag(ent, "ew_no_enemy_sync")
|
||||
end)
|
||||
else
|
||||
table_extend_filtered(entities2, entities, function(ent)
|
||||
local x, y = EntityGetTransform(ent)
|
||||
local has_anyone = EntityHasTag(ent, "worm")
|
||||
or EntityGetFirstComponent(ent, "BossHealthBarComponent") ~= nil
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "ew_peer") ~= 0
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "polymorphed_player") ~= 0
|
||||
return has_anyone and not EntityHasTag(ent, "ew_no_enemy_sync")
|
||||
end)
|
||||
end
|
||||
|
||||
return entities2
|
||||
end
|
||||
|
||||
local was_held = {}
|
||||
|
||||
function enemy_sync.host_upload_entities()
|
||||
local entities = get_sync_entities()
|
||||
local enemy_data_list = {}
|
||||
for i, enemy_id in ipairs(entities) do
|
||||
if not world_exists_for(enemy_id) then
|
||||
goto continue
|
||||
end
|
||||
local filename = EntityGetFilename(enemy_id)
|
||||
filename = constants.interned_filename_to_index[filename] or filename
|
||||
|
||||
local x, y, rot = EntityGetTransform(enemy_id)
|
||||
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "CharacterDataComponent")
|
||||
local vx, vy = 0, 0
|
||||
if character_data ~= nil then
|
||||
vx, vy = ComponentGetValue2(character_data, "mVelocity")
|
||||
else
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
vx, vy = ComponentGetValue2(velocity, "mVelocity")
|
||||
end
|
||||
end
|
||||
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, "AnimalAIComponent")
|
||||
if ai_component ~= 0 and ai_component ~= nil then
|
||||
ComponentSetValue2(ai_component, "max_distance_to_cam_to_start_hunting", math.pow(2, 29))
|
||||
end
|
||||
|
||||
local phys_info = util.get_phys_info(enemy_id, true)
|
||||
if phys_info == nil then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local hp, max_hp, has_hp = util.get_ent_health(enemy_id)
|
||||
if has_hp then
|
||||
util.ensure_component_present(enemy_id, "LuaComponent", "ew_death_notify", {
|
||||
script_death = "mods/quant.ew/files/resource/cbs/death_notify.lua",
|
||||
})
|
||||
end
|
||||
|
||||
-- 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
|
||||
-- -- local x, y, r =
|
||||
-- end
|
||||
|
||||
local death_triggers = {}
|
||||
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
||||
local script = ComponentGetValue2(com, "script_death")
|
||||
if script ~= nil and script ~= "" then
|
||||
table.insert(death_triggers, constants.interned_filename_to_index[script] or script)
|
||||
end
|
||||
end
|
||||
local en_data
|
||||
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
||||
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
||||
if EntityHasTag(enemy_id, "boss_centipede") then
|
||||
local legs = {}
|
||||
for _, leg in ipairs(EntityGetAllChildren(enemy_id, "foot")) do
|
||||
local limb = EntityGetFirstComponentIncludingDisabled(leg, "IKLimbComponent")
|
||||
local lx, ly = ComponentGetValue2(limb, "end_position")
|
||||
table.insert(legs, lx)
|
||||
table.insert(legs, ly)
|
||||
end
|
||||
en_data = EnemyDataKolmi({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
enabled = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent", "disabled_at_start") ~= nil,
|
||||
legs = legs,
|
||||
})
|
||||
elseif EntityHasTag(enemy_id, "boss_wizard") then
|
||||
local orbs = { false, false, false, false, false, false, false, false }
|
||||
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
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,
|
||||
})
|
||||
elseif worm ~= nil then
|
||||
local tx, ty = ComponentGetValue2(worm, "mTargetVec")
|
||||
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({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
r = math.floor((rot % FULL_TURN) / FULL_TURN * 255),
|
||||
})
|
||||
else
|
||||
en_data = EnemyData({
|
||||
enemy_id = enemy_id,
|
||||
x = x,
|
||||
y = y,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
})
|
||||
end
|
||||
|
||||
local wand
|
||||
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
||||
if inv ~= nil then
|
||||
local item = ComponentGetValue2(inv, "mActualActiveItem")
|
||||
if item ~= nil and EntityGetIsAlive(item) then
|
||||
if not EntityHasTag(item, "ew_global_item") then
|
||||
item_sync.make_item_global(item)
|
||||
else
|
||||
wand = item_sync.get_global_item_id(item)
|
||||
if wand == nil then
|
||||
EntityRemoveTag(item, "ew_global_item")
|
||||
goto continue
|
||||
end
|
||||
if not item_sync.is_my_item(wand) then
|
||||
item_sync.take_authority(wand)
|
||||
end
|
||||
was_held[wand] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local effect_data = effect_sync.get_sync_data(enemy_id, true)
|
||||
|
||||
local has_laser
|
||||
local animations = {}
|
||||
|
||||
for _, sprite in ipairs(EntityGetComponent(enemy_id, "SpriteComponent") or {}) do
|
||||
local animation
|
||||
if sprite ~= nil then
|
||||
animation = ComponentGetValue2(sprite, "rect_animation")
|
||||
end
|
||||
table.insert(animations, animation)
|
||||
if ComponentHasTag(sprite, "laser_sight") then
|
||||
has_laser = true
|
||||
end
|
||||
end
|
||||
local laser
|
||||
if has_laser and EntityGetName(enemy_id) ~= "$animal_turret" then
|
||||
local ai = EntityGetFirstComponentIncludingDisabled(enemy_id, "AnimalAIComponent")
|
||||
if ai ~= nil then
|
||||
local target = ComponentGetValue2(ai, "mGreatestPrey")
|
||||
local peer = player_fns.get_player_data_by_local_entity_id(target)
|
||||
if peer ~= nil then
|
||||
laser = peer.peer_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dont_cull = EntityGetFirstComponent(enemy_id, "BossHealthBarComponent") ~= nil
|
||||
or worm ~= nil
|
||||
or EntityHasTag(enemy_id, "seed_f")
|
||||
or EntityHasTag(enemy_id, "seed_e")
|
||||
or EntityHasTag(enemy_id, "seed_d")
|
||||
or EntityHasTag(enemy_id, "seed_c")
|
||||
or EntityGetFilename(enemy_id) == "data/entities/buildings/essence_eater.xml"
|
||||
|
||||
local stains = stain_sync.get_stains(enemy_id)
|
||||
|
||||
table.insert(
|
||||
enemy_data_list,
|
||||
{ filename, en_data, phys_info, wand, effect_data, animations, dont_cull, death_triggers, stains, laser }
|
||||
)
|
||||
::continue::
|
||||
end
|
||||
|
||||
rpc.handle_enemy_data(enemy_data_list, first)
|
||||
first = false
|
||||
if #dead_entities > 0 then
|
||||
rpc.handle_death_data(dead_entities)
|
||||
end
|
||||
dead_entities = {}
|
||||
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
|
||||
|
||||
if #enemy_health_list > 0 then
|
||||
rpc.handle_enemy_health(enemy_health_list)
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.client_cleanup()
|
||||
local entities = get_sync_entities(true)
|
||||
for _, enemy_id in ipairs(entities) do
|
||||
if not EntityHasTag(enemy_id, "ew_replicated") then
|
||||
EntityKill(enemy_id)
|
||||
elseif not spawned_by_us[enemy_id] then
|
||||
EntityKill(enemy_id)
|
||||
end
|
||||
end
|
||||
for remote_id, enemy_data in pairs(ctx.entity_by_remote_id) do
|
||||
if frame > enemy_data.frame then
|
||||
EntityKill(enemy_data.id)
|
||||
ctx.entity_by_remote_id[remote_id] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.on_world_update_host()
|
||||
local rt = math.floor(tonumber(ModSettingGet("quant.ew.enemy_sync") or 2) or 2 + 0.5)
|
||||
local n = 0
|
||||
if rt == 3 then
|
||||
n = 2
|
||||
elseif rt == 2 then
|
||||
n = 1
|
||||
end
|
||||
if rt == 1 or GameGetFrameNum() % rt == n then
|
||||
enemy_sync.host_upload_entities()
|
||||
end
|
||||
if GameGetFrameNum() % 10 == 5 then
|
||||
host_upload_health()
|
||||
end
|
||||
for wand, _ in pairs(was_held) do
|
||||
if EntityGetRootEntity(wand) == wand then
|
||||
was_held[wand] = nil
|
||||
if item_sync.is_my_item(item_sync.get_global_item_id(wand)) then
|
||||
item_sync.make_item_global(wand)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function enemy_sync.on_world_update_client()
|
||||
if GameGetFrameNum() % 12 == 1 then
|
||||
enemy_sync.client_cleanup()
|
||||
end
|
||||
if GameGetFrameNum() % (60 * 60) == 1 then
|
||||
times_spawned_last_minute = {}
|
||||
end
|
||||
end
|
||||
|
||||
local kolmi_spawn
|
||||
|
||||
local function sync_enemy(enemy_info_raw, force_no_cull, host_fps)
|
||||
local filename = enemy_info_raw[1]
|
||||
filename = constants.interned_index_to_filename[filename] or filename
|
||||
|
||||
local en_data = enemy_info_raw[2]
|
||||
local dont_cull = enemy_info_raw[7]
|
||||
local death_triggers = enemy_info_raw[8]
|
||||
local stains = enemy_info_raw[9]
|
||||
local has_laser = enemy_info_raw[10]
|
||||
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)
|
||||
if my_x == nil then
|
||||
goto continue
|
||||
end
|
||||
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
|
||||
EntityKill(ctx.entity_by_remote_id[remote_enemy_id].id)
|
||||
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
|
||||
if ffi.typeof(en_data) ~= EnemyDataNoMotion then
|
||||
vx, vy = en_data.vx, en_data.vy
|
||||
end
|
||||
local phys_infos = enemy_info_raw[3]
|
||||
local gid = enemy_info_raw[4]
|
||||
local effects = enemy_info_raw[5]
|
||||
local animation = enemy_info_raw[6]
|
||||
local has_died = filename == nil
|
||||
|
||||
local frame_now = GameGetFrameNum()
|
||||
|
||||
--[[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
|
||||
if filename == nil or filename == "" or not ModDoesFileExist(filename) then
|
||||
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
|
||||
enemy_id = EntityLoad(filename, x, y)
|
||||
if enemy_id == nil then
|
||||
print("entity is nil " .. tostring(filename))
|
||||
goto continue
|
||||
end
|
||||
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")
|
||||
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
|
||||
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
|
||||
for _, name in ipairs({
|
||||
"AnimalAIComponent",
|
||||
"PhysicsAIComponent",
|
||||
"CameraBoundComponent",
|
||||
"AdvancedFishAIComponent",
|
||||
"AIAttackComponent",
|
||||
}) do
|
||||
local ai_component = EntityGetFirstComponentIncludingDisabled(enemy_id, name)
|
||||
if ai_component ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, ai_component)
|
||||
end
|
||||
end
|
||||
ctx.entity_by_remote_id[remote_enemy_id] = { id = enemy_id, frame = frame_now }
|
||||
|
||||
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
|
||||
ComponentRemoveTag(sprite, "character")
|
||||
end
|
||||
|
||||
local ghost = EntityGetFirstComponentIncludingDisabled(enemy_id, "GhostComponent")
|
||||
if ghost ~= nil then
|
||||
ComponentSetValue2(ghost, "die_if_no_home", false)
|
||||
end
|
||||
if not EntityHasTag(enemy_id, "effectable_prop") then
|
||||
util.make_ephemerial(enemy_id)
|
||||
end
|
||||
end
|
||||
|
||||
local enemy_data_new = ctx.entity_by_remote_id[remote_enemy_id]
|
||||
enemy_data_new.frame = frame_now
|
||||
local enemy_id = enemy_data_new.id
|
||||
|
||||
if not has_died then
|
||||
local laser = EntityGetFirstComponentIncludingDisabled(enemy_id, "LaserEmitterComponent", "ew_laser")
|
||||
if has_laser then
|
||||
if laser == nil then
|
||||
laser = EntityAddComponent2(enemy_id, "LaserEmitterComponent", { _tags = "ew_laser" })
|
||||
ComponentObjectSetValue2(laser, "laser", "max_cell_durability_to_destroy", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "damage_to_cells", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "max_length", 1024)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_radius", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_chance", 80)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_fade", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "hit_particle_chance", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "audio_enabled", false)
|
||||
ComponentObjectSetValue2(laser, "laser", "damage_to_entities", 0)
|
||||
ComponentObjectSetValue2(laser, "laser", "beam_particle_type", 225)
|
||||
end
|
||||
local target = ctx.players[has_laser].entity
|
||||
local lx, ly = EntityGetTransform(target)
|
||||
if lx ~= nil then
|
||||
local did_hit, _, _ = RaytracePlatforms(x, y, lx, ly)
|
||||
ComponentSetValue2(laser, "is_emitting", not did_hit)
|
||||
if not did_hit then
|
||||
local dy = ly - y
|
||||
local dx = lx - x
|
||||
local theta = math.atan2(dy, dx)
|
||||
ComponentSetValue2(laser, "laser_angle_add_rad", theta)
|
||||
ComponentObjectSetValue2(laser, "laser", "max_length", math.sqrt(dx * dx + dy * dy))
|
||||
end
|
||||
end
|
||||
elseif laser ~= nil then
|
||||
ComponentSetValue2(laser, "is_emitting", false)
|
||||
end
|
||||
if not util.set_phys_info(enemy_id, phys_infos, host_fps) or enemy_id == kolmi_spawn then
|
||||
local m = host_fps / ctx.my_player.fps
|
||||
vx, vy = vx * m, vy * m
|
||||
local character_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "CharacterDataComponent")
|
||||
if character_data ~= nil then
|
||||
ComponentSetValue2(character_data, "mVelocity", vx, vy)
|
||||
else
|
||||
local velocity_data = EntityGetFirstComponentIncludingDisabled(enemy_id, "VelocityComponent")
|
||||
if velocity_data ~= nil then
|
||||
ComponentSetValue2(velocity_data, "mVelocity", vx, vy)
|
||||
end
|
||||
end
|
||||
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
|
||||
end
|
||||
local worm = EntityGetFirstComponentIncludingDisabled(enemy_id, "WormAIComponent")
|
||||
or EntityGetFirstComponentIncludingDisabled(enemy_id, "BossDragonComponent")
|
||||
if worm ~= nil and ffi.typeof(en_data) == EnemyDataWorm then
|
||||
local tx, ty = en_data.tx, en_data.ty
|
||||
ComponentSetValue2(worm, "mTargetVec", tx, ty)
|
||||
end
|
||||
if ffi.typeof(en_data) == EnemyDataKolmi and en_data.enabled then
|
||||
if kolmi_spawn ~= enemy_id then
|
||||
for _, c in ipairs(EntityGetComponentIncludingDisabled(enemy_id, "LuaComponent") or {}) do
|
||||
EntityRemoveComponent(enemy_id, c)
|
||||
end
|
||||
kolmi_spawn = enemy_id
|
||||
end
|
||||
EntitySetComponentsWithTagEnabled(enemy_id, "enabled_at_start", false)
|
||||
EntitySetComponentsWithTagEnabled(enemy_id, "disabled_at_start", true)
|
||||
for i, leg in ipairs(EntityGetAllChildren(enemy_id, "foot")) do
|
||||
local limb = EntityGetFirstComponentIncludingDisabled(leg, "IKLimbComponent")
|
||||
ComponentSetValue2(limb, "end_position", en_data.legs[2 * i - 2], en_data.legs[2 * i - 1])
|
||||
end
|
||||
end
|
||||
|
||||
local indexed = {}
|
||||
for _, com in ipairs(EntityGetComponent(enemy_id, "LuaComponent") or {}) do
|
||||
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
|
||||
ComponentSetValue2(com, "script_death", "")
|
||||
end
|
||||
end
|
||||
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
|
||||
if ffi.typeof(en_data) == EnemyDataMom then
|
||||
local orbs = en_data.orbs
|
||||
for _, child in ipairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
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 - 1] 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
|
||||
effect_sync.apply_effects(effects, enemy_id, true)
|
||||
if stains ~= nil then
|
||||
stain_sync.sync_stains(stains, enemy_id)
|
||||
end
|
||||
end
|
||||
|
||||
local inv = EntityGetFirstComponentIncludingDisabled(enemy_id, "Inventory2Component")
|
||||
local item
|
||||
if inv ~= nil then
|
||||
item = ComponentGetValue2(inv, "mActualActiveItem")
|
||||
end
|
||||
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 and EntityGetIsAlive(wand) then
|
||||
EntityAddTag(wand, "ew_client_item")
|
||||
local inventory
|
||||
for _, child in pairs(EntityGetAllChildren(enemy_id) or {}) do
|
||||
if EntityGetName(child) == "inventory_quick" then
|
||||
inventory = child
|
||||
end
|
||||
end
|
||||
if inventory == nil then
|
||||
inventory = EntityCreateNew("inventory_quick")
|
||||
EntityAddChild(enemy_id, inventory)
|
||||
end
|
||||
if EntityGetParent(wand) ~= inventory then
|
||||
if EntityGetParent(wand) ~= 0 then
|
||||
EntityRemoveFromParent(wand)
|
||||
end
|
||||
EntityAddChild(inventory, wand)
|
||||
end
|
||||
np.SetActiveHeldEntity(enemy_id, wand, false, false)
|
||||
elseif should_wait[gid] == nil or should_wait[gid] < GameGetFrameNum() then
|
||||
item_sync.rpc.request_send_again(gid)
|
||||
should_wait[gid] = GameGetFrameNum() + 15
|
||||
end
|
||||
end
|
||||
|
||||
for i, sprite in pairs(EntityGetComponent(enemy_id, "SpriteComponent") or {}) do
|
||||
if animation[i] ~= nil then
|
||||
ComponentSetValue2(sprite, "rect_animation", animation[i])
|
||||
ComponentSetValue2(sprite, "next_rect_animation", animation[i])
|
||||
end
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.handle_death_data(death_data)
|
||||
for _, remote_data in ipairs(death_data) do
|
||||
local remote_id = remote_data[1]
|
||||
--[[if confirmed_kills[remote_id] then
|
||||
GamePrint("Remote id has been killed already..?")
|
||||
goto continue
|
||||
end
|
||||
confirmed_kills[remote_id] = true]]
|
||||
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
|
||||
|
||||
if unsynced_enemys[remote_id] ~= nil then
|
||||
sync_enemy(unsynced_enemys[remote_id], true, 60)
|
||||
end
|
||||
local enemy_data = ctx.entity_by_remote_id[remote_id]
|
||||
if enemy_data ~= nil and EntityGetIsAlive(enemy_data.id) then
|
||||
local enemy_id = enemy_data.id
|
||||
local immortal = EntityGetFirstComponentIncludingDisabled(enemy_id, "LuaComponent", "ew_immortal")
|
||||
if immortal ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, immortal)
|
||||
end
|
||||
local protection_component_id = GameGetGameEffect(enemy_id, "PROTECTION_ALL")
|
||||
if protection_component_id ~= 0 then
|
||||
EntitySetComponentIsEnabled(enemy_id, protection_component_id, false)
|
||||
end
|
||||
|
||||
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)
|
||||
ComponentSetValue2(damage_component, "ui_report_damage", false)
|
||||
ComponentSetValue2(damage_component, "hp", 2 ^ -38)
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp
|
||||
if dmg > 0 then
|
||||
EntityInflictDamage(enemy_id, dmg + 0.1, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity)
|
||||
end
|
||||
|
||||
EntityInflictDamage(enemy_id, 1000000000, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity) -- Just to be sure
|
||||
if not remote_data[3] then
|
||||
EntityKill(enemy_id)
|
||||
else
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
ComponentSetValue2(damage_component, "kill_now", true)
|
||||
end
|
||||
ctx.entity_by_remote_id[remote_id] = nil
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.handle_enemy_data(enemy_data, is_first)
|
||||
if is_first then
|
||||
for _, n in pairs(ctx.entity_by_remote_id) do
|
||||
EntityKill(n.id)
|
||||
end
|
||||
ctx.entity_by_remote_id = {}
|
||||
end
|
||||
frame = GameGetFrameNum()
|
||||
for _, enemy_info_raw in ipairs(enemy_data) do
|
||||
sync_enemy(enemy_info_raw, false, ctx.rpc_player_data.fps)
|
||||
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
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp - hp
|
||||
if dmg > 0 then
|
||||
-- Make sure the enemy doesn't die from the next EntityInflictDamage.
|
||||
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
|
||||
-- Deal damage, so that game displays damage numbers.
|
||||
EntityInflictDamage(enemy_id, dmg, "DAMAGE_CURSE", "", "NONE", 0, 0, GameGetWorldStateEntity())
|
||||
end
|
||||
util.set_ent_health(enemy_id, { hp, max_hp })
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
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")
|
||||
if projectileComponent ~= nil then
|
||||
local entity_that_shot = ComponentGetValue2(projectileComponent, "mEntityThatShot")
|
||||
if entity_that_shot == 0 then
|
||||
rpc.replicate_projectile(
|
||||
util.serialize_entity(projectile_id),
|
||||
position_x,
|
||||
position_y,
|
||||
target_x,
|
||||
target_y,
|
||||
shooter_id,
|
||||
initial_rng
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.replicate_projectile(seri_ent, position_x, position_y, target_x, target_y, remote_source_ent, rng)
|
||||
if rng ~= nil then
|
||||
np.SetProjectileSpreadRNG(rng)
|
||||
end
|
||||
if ctx.entity_by_remote_id[remote_source_ent] == nil then
|
||||
return
|
||||
end
|
||||
local source_ent = ctx.entity_by_remote_id[remote_source_ent].id
|
||||
local ent = util.deserialize_entity(seri_ent)
|
||||
GameShootProjectile(source_ent, position_x, position_y, target_x, target_y, ent)
|
||||
end
|
||||
|
||||
return enemy_sync
|
|
@ -25,13 +25,8 @@ end
|
|||
|
||||
function module.on_world_initialized()
|
||||
initial_world_state_entity = GameGetWorldStateEntity()
|
||||
ewext.on_world_initialized()
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
||||
chunk_map = tonumber(ffi.cast("intptr_t", chunk_map))
|
||||
local material_list = tonumber(ffi.cast("intptr_t", world_ffi.get_material_ptr(0)))
|
||||
ewext.init_particle_world_state(grid_world, chunk_map, material_list)
|
||||
ewext.on_world_initialized(ctx.proxy_opt.world_num)
|
||||
ewext.init_particle_world_state()
|
||||
ewext.module_on_world_init()
|
||||
log = ModSettingGet("quant.ew.log_performance") or false
|
||||
ewext.set_log(log)
|
||||
|
|
|
@ -1,865 +0,0 @@
|
|||
-- Synchronizes item pickup and item drop
|
||||
ModLuaFileAppend("data/scripts/items/utility_box.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
ModLuaFileAppend("data/scripts/items/chest_random.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
ModLuaFileAppend("data/scripts/items/chest_random_super.lua", "mods/quant.ew/files/resource/cbs/chest_sync.lua")
|
||||
|
||||
dofile_once("data/scripts/lib/coroutines.lua")
|
||||
|
||||
local rpc = net.new_rpc_namespace()
|
||||
|
||||
local item_sync = {}
|
||||
|
||||
local pending_remove = {}
|
||||
local pickup_handlers = {}
|
||||
|
||||
local dead_entities = {}
|
||||
|
||||
local frame = {}
|
||||
|
||||
local gid_last_frame_updated = {}
|
||||
|
||||
local wait_on_send = {}
|
||||
|
||||
local wait_for_gid = {}
|
||||
|
||||
function rpc.open_chest(gid)
|
||||
if wait_for_gid[gid] == nil or wait_for_gid[gid] < 10000 then
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 36000
|
||||
wait_on_send[gid] = GameGetFrameNum() + 36000
|
||||
local ent = item_sync.find_by_gid(gid)
|
||||
if ent ~= nil then
|
||||
local file
|
||||
local name = EntityGetFilename(ent)
|
||||
if name == "data/entities/items/pickup/utility_box.xml" then
|
||||
file = "data/scripts/items/utility_box.lua"
|
||||
elseif name == "data/entities/items/pickup/chest_random_super.xml" then
|
||||
file = "data/scripts/items/chest_random_super.lua"
|
||||
elseif name == "data/entities/items/pickup/chest_random.xml" then
|
||||
file = "data/scripts/items/chest_random.lua"
|
||||
end
|
||||
if file ~= nil then
|
||||
EntityAddComponent2(ent, "LuaComponent", {
|
||||
script_source_file = file,
|
||||
execute_on_added = true,
|
||||
call_init_function = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
util.add_cross_call("ew_chest_opened", function(chest_id)
|
||||
local gid = item_sync.get_global_item_id(chest_id)
|
||||
if gid ~= nil then
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 36000
|
||||
wait_on_send[gid] = GameGetFrameNum() + 36000
|
||||
rpc.open_chest(gid)
|
||||
end
|
||||
end)
|
||||
|
||||
util.add_cross_call("ew_item_death_notify", function(enemy_id, responsible_id)
|
||||
local player_data = player_fns.get_player_data_by_local_entity_id(responsible_id)
|
||||
local responsible
|
||||
if player_data ~= nil then
|
||||
responsible = player_data.peer_id
|
||||
else
|
||||
responsible = responsible_id
|
||||
end
|
||||
local gid = item_sync.get_global_item_id(enemy_id)
|
||||
if gid ~= nil then
|
||||
table.insert(dead_entities, { gid, responsible })
|
||||
end
|
||||
end)
|
||||
|
||||
function item_sync.ensure_notify_component(ent)
|
||||
local notify = EntityGetFirstComponentIncludingDisabled(ent, "LuaComponent", "ew_notify_component")
|
||||
if notify == nil then
|
||||
EntityAddComponent2(ent, "LuaComponent", {
|
||||
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_notify_component,ew_remove_on_send",
|
||||
script_throw_item = "mods/quant.ew/files/resource/cbs/item_notify.lua",
|
||||
script_item_picked_up = "mods/quant.ew/files/resource/cbs/item_notify.lua",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function mark_in_inventory(my_player)
|
||||
local items = inventory_helper.get_all_inventory_items(my_player)
|
||||
for _, ent in pairs(items) do
|
||||
if not EntityHasTag(ent, "polymorphed_player") then
|
||||
item_sync.ensure_notify_component(ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function allocate_global_id()
|
||||
local current = tonumber(GlobalsGetValue("ew_global_item_id", "1"))
|
||||
GlobalsSetValue("ew_global_item_id", tostring(current + 1))
|
||||
return ctx.my_id .. ":" .. current
|
||||
end
|
||||
|
||||
-- Try to guess if the item is in world.
|
||||
local function is_item_on_ground(item)
|
||||
return EntityGetRootEntity(item) == item
|
||||
end
|
||||
|
||||
function item_sync.get_global_item_id(item)
|
||||
local gid = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
if gid == nil then
|
||||
return nil
|
||||
end
|
||||
local ret = ComponentGetValue2(gid, "value_string")
|
||||
return ret
|
||||
end
|
||||
|
||||
local function is_wand(ent)
|
||||
if ent == nil or ent == 0 then
|
||||
return false
|
||||
end
|
||||
local ability = EntityGetFirstComponentIncludingDisabled(ent, "AbilityComponent")
|
||||
if ability == nil then
|
||||
return false
|
||||
end
|
||||
return ComponentGetValue2(ability, "use_gun_script") == true
|
||||
end
|
||||
|
||||
local function is_safe_to_remove()
|
||||
return not ctx.is_wand_pickup
|
||||
end
|
||||
|
||||
function item_sync.remove_item_with_id(gid)
|
||||
local item_ent_id = item_sync.find_by_gid(gid)
|
||||
if is_safe_to_remove() or not is_wand(item_ent_id) then
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
else
|
||||
table.insert(pending_remove, gid)
|
||||
EntitySetTransform(item_ent_id, 0, 0)
|
||||
util.make_ephemerial(item_ent_id)
|
||||
end
|
||||
end
|
||||
|
||||
local find_by_gid_cache = {}
|
||||
function item_sync.find_by_gid(gid)
|
||||
if find_by_gid_cache[gid] ~= nil then
|
||||
if
|
||||
EntityGetIsAlive(find_by_gid_cache[gid])
|
||||
and EntityHasTag(find_by_gid_cache[gid], "ew_global_item")
|
||||
and is_item_on_ground(find_by_gid_cache[gid])
|
||||
then
|
||||
return find_by_gid_cache[gid]
|
||||
else
|
||||
find_by_gid_cache[gid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--print("find_by_gid: searching")
|
||||
|
||||
local candidate
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local i_gid = item_sync.get_global_item_id(item)
|
||||
if i_gid ~= nil then
|
||||
find_by_gid_cache[i_gid] = item
|
||||
if i_gid == gid then
|
||||
if is_item_on_ground(item) then
|
||||
return item
|
||||
else
|
||||
candidate = item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return candidate
|
||||
end
|
||||
|
||||
function item_sync.remove_item_with_id_now(gid)
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item ~= nil then
|
||||
find_by_gid_cache[gid] = nil
|
||||
for _, audio in ipairs(EntityGetComponent(item, "AudioComponent") or {}) do
|
||||
if string.sub(ComponentGetValue2(audio, "event_root"), 1, 10) == "collision/" then
|
||||
EntitySetComponentIsEnabled(item, audio, false)
|
||||
end
|
||||
end
|
||||
EntityKill(item)
|
||||
return item
|
||||
end
|
||||
end
|
||||
|
||||
function item_sync.host_localize_item(gid, peer_id)
|
||||
if ctx.item_prevent_localize[gid] then
|
||||
print("Item localize for " .. gid .. " prevented")
|
||||
return
|
||||
end
|
||||
ctx.item_prevent_localize[gid] = true
|
||||
|
||||
if table.contains(pending_remove, gid) then
|
||||
print("Item localize prevented, already taken")
|
||||
return
|
||||
end
|
||||
|
||||
local item_ent_id = item_sync.find_by_gid(gid)
|
||||
if item_ent_id ~= nil then
|
||||
for _, handler in ipairs(pickup_handlers) do
|
||||
handler(item_ent_id)
|
||||
end
|
||||
end
|
||||
if peer_id ~= ctx.my_id then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
end
|
||||
rpc.item_localize(peer_id, gid)
|
||||
if peer_id == ctx.my_id then
|
||||
item_sync.take_authority(gid)
|
||||
else
|
||||
rpc.hand_authority_over_to(peer_id, gid)
|
||||
end
|
||||
end
|
||||
|
||||
local function make_global(item, give_authority_to)
|
||||
if not EntityGetIsAlive(item) then
|
||||
print("Thrown item vanished before we could send it")
|
||||
return
|
||||
end
|
||||
item_sync.ensure_notify_component(item)
|
||||
local gid_component =
|
||||
EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
local gid
|
||||
if gid_component == nil then
|
||||
gid = allocate_global_id()
|
||||
if give_authority_to ~= nil then
|
||||
gid = give_authority_to .. ":" .. gid
|
||||
end
|
||||
EntityAddComponent2(item, "VariableStorageComponent", {
|
||||
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_global_item_id",
|
||||
value_string = gid,
|
||||
})
|
||||
else
|
||||
gid = ComponentGetValue2(gid_component, "value_string")
|
||||
end
|
||||
--local vel = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
--if vel then
|
||||
-- local vx, vy = ComponentGetValue2(vel, "mVelocity")
|
||||
--end
|
||||
|
||||
local item_data = inventory_helper.serialize_single_item(item)
|
||||
item_data.gid = gid
|
||||
|
||||
local _, _, has_hp = util.get_ent_health(item)
|
||||
if has_hp then
|
||||
util.ensure_component_present(item, "LuaComponent", "ew_item_death_notify", {
|
||||
script_death = "mods/quant.ew/files/resource/cbs/item_death_notify.lua",
|
||||
})
|
||||
end
|
||||
|
||||
ctx.item_prevent_localize[gid] = false
|
||||
rpc.item_globalize(item_data)
|
||||
if wait_on_send[gid] ~= nil then
|
||||
wait_on_send[gid] = GameGetFrameNum() + 30
|
||||
end
|
||||
end
|
||||
|
||||
function item_sync.make_item_global(item, instant, give_authority_to)
|
||||
EntityAddTag(item, "ew_global_item")
|
||||
if instant then
|
||||
make_global(item, give_authority_to)
|
||||
else
|
||||
async(function()
|
||||
wait(1) -- Wait 1 frame so that game sets proper velocity.
|
||||
make_global(item, give_authority_to)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_client_items_from_world()
|
||||
if GameGetFrameNum() % 5 ~= 3 then
|
||||
return
|
||||
end
|
||||
for _, item in ipairs(EntityGetWithTag("ew_client_item")) do
|
||||
if is_item_on_ground(item) then
|
||||
item_sync.remove_item_with_id(item_sync.get_global_item_id(item))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function is_peers_item(gid, peer)
|
||||
if gid == nil then
|
||||
return false
|
||||
end
|
||||
return string.sub(gid, 1, 16) == peer
|
||||
end
|
||||
|
||||
function item_sync.is_my_item(gid)
|
||||
if gid == nil then
|
||||
return false
|
||||
end
|
||||
return string.sub(gid, 1, 16) == ctx.my_id
|
||||
end
|
||||
|
||||
function item_sync.take_authority(gid)
|
||||
if gid ~= nil and not item_sync.is_my_item(gid) then
|
||||
local new_id = allocate_global_id()
|
||||
rpc.give_authority_to(gid, new_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_everywhere()
|
||||
rpc.opts_reliable()
|
||||
function rpc.give_authority_to(gid, new_id)
|
||||
local item
|
||||
local to_remove = {}
|
||||
for _, ent in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local i_gid = item_sync.get_global_item_id(ent)
|
||||
if i_gid == gid then
|
||||
if item == nil then
|
||||
item = ent
|
||||
else
|
||||
table.insert(to_remove, gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
find_by_gid_cache[gid] = nil
|
||||
if table.contains(pending_remove, gid) then
|
||||
for i, id in ipairs(pending_remove) do
|
||||
if id == gid then
|
||||
table.remove(pending_remove, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(pending_remove, new_id)
|
||||
end
|
||||
for _, g in ipairs(to_remove) do
|
||||
item_sync.remove_item_with_id(g)
|
||||
end
|
||||
if item ~= nil then
|
||||
find_by_gid_cache[new_id] = item
|
||||
local var = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
ComponentSetValue2(var, "value_string", new_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.hand_authority_over_to(peer_id, gid)
|
||||
if peer_id == ctx.my_id then
|
||||
if item_sync.find_by_gid(gid) ~= nil then
|
||||
item_sync.take_authority(gid)
|
||||
elseif wait_for_gid[gid] == nil then
|
||||
rpc.request_send_again(gid)
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 300
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.handle_death_data(death_data)
|
||||
for _, remote_data in ipairs(death_data) do
|
||||
local remote_id = remote_data[1]
|
||||
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
|
||||
|
||||
local enemy_id = item_sync.find_by_gid(remote_id)
|
||||
if enemy_id ~= nil and EntityGetIsAlive(enemy_id) then
|
||||
local immortal = EntityGetFirstComponentIncludingDisabled(enemy_id, "LuaComponent", "ew_immortal")
|
||||
if immortal ~= 0 then
|
||||
EntityRemoveComponent(enemy_id, immortal)
|
||||
end
|
||||
local protection_component_id = GameGetGameEffect(enemy_id, "PROTECTION_ALL")
|
||||
if protection_component_id ~= 0 then
|
||||
EntitySetComponentIsEnabled(enemy_id, protection_component_id, false)
|
||||
end
|
||||
|
||||
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)
|
||||
ComponentSetValue2(damage_component, "ui_report_damage", false)
|
||||
ComponentSetValue2(damage_component, "hp", 2 ^ -38)
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
local current_hp = util.get_ent_health(enemy_id)
|
||||
local dmg = current_hp
|
||||
if dmg > 0 then
|
||||
EntityInflictDamage(enemy_id, dmg + 0.1, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity)
|
||||
end
|
||||
|
||||
EntityInflictDamage(enemy_id, 1000000000, "DAMAGE_CURSE", "", "NONE", 0, 0, responsible_entity) -- Just to be sure
|
||||
EntityKill(enemy_id)
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
local DISTANCE_LIMIT = 128 * 4
|
||||
|
||||
local ignore = {}
|
||||
|
||||
local function send_item_positions(all)
|
||||
local position_data = {}
|
||||
local cx, cy = GameGetCameraPos()
|
||||
local cap = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item")) do
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
-- Only send info about items created by us.
|
||||
local tg = EntityHasTag(item, "ew_no_spawn")
|
||||
if gid ~= nil and item_sync.is_my_item(gid) and (is_item_on_ground(item) or tg) then
|
||||
local x, y = EntityGetTransform(item)
|
||||
local dx, dy = x - cx, y - cy
|
||||
if
|
||||
not tg
|
||||
and (ignore[gid] == nil or ignore[gid] < GameGetFrameNum())
|
||||
and dx * dx + dy * dy > 4 * DISTANCE_LIMIT * DISTANCE_LIMIT
|
||||
then
|
||||
local ent = EntityGetClosestWithTag(x, y, "ew_peer")
|
||||
local nx, ny
|
||||
local ndx, ndy
|
||||
if ent ~= 0 then
|
||||
nx, ny = EntityGetTransform(ent)
|
||||
ndx, ndy = x - nx, y - ny
|
||||
end
|
||||
if ent == 0 or ndx * ndx + ndy * ndy > DISTANCE_LIMIT * DISTANCE_LIMIT then
|
||||
ent = EntityGetClosestWithTag(x, y, "polymorphed_player")
|
||||
if ent ~= 0 then
|
||||
nx, ny = EntityGetTransform(ent)
|
||||
ndx, ndy = x - nx, y - ny
|
||||
end
|
||||
if ent == 0 or ndx * ndx + ndy * ndy > DISTANCE_LIMIT * DISTANCE_LIMIT then
|
||||
ignore[gid] = GameGetFrameNum() + 60
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
local data = player_fns.get_player_data_by_local_entity_id(ent)
|
||||
if data ~= nil then
|
||||
local peer = data.peer_id
|
||||
rpc.hand_authority_over_to(peer, gid)
|
||||
ignore[gid] = nil
|
||||
else
|
||||
ignore[gid] = GameGetFrameNum() + 60
|
||||
end
|
||||
else
|
||||
local phys_info = util.get_phys_info(item, true)
|
||||
if
|
||||
tg
|
||||
or (
|
||||
(phys_info[1][1] ~= nil or phys_info[2][1] ~= nil or all)
|
||||
and (
|
||||
#EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "ew_peer") ~= 0
|
||||
or #EntityGetInRadiusWithTag(x, y, DISTANCE_LIMIT, "polymorphed_player") ~= 0
|
||||
)
|
||||
)
|
||||
then
|
||||
local costcom = EntityGetFirstComponentIncludingDisabled(item, "ItemCostComponent")
|
||||
local cost = 0
|
||||
if costcom ~= nil then
|
||||
cost = ComponentGetValue2(costcom, "cost")
|
||||
local vel = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if math.abs(cx - x) < DISTANCE_LIMIT and math.abs(cy - y) < DISTANCE_LIMIT then
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_stealable"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(costcom, "stealable", true)
|
||||
ComponentSetValue2(vel, "gravity_y", 400)
|
||||
elseif
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_float"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(vel, "gravity_y", 400)
|
||||
end
|
||||
else
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_stealable"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(costcom, "stealable", false)
|
||||
ComponentSetValue2(vel, "gravity_y", 0)
|
||||
elseif
|
||||
EntityGetFirstComponentIncludingDisabled(
|
||||
item,
|
||||
"VariableStorageComponent",
|
||||
"ew_try_float"
|
||||
) ~= nil
|
||||
then
|
||||
ComponentSetValue2(vel, "gravity_y", 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
position_data[gid] = { x, y, phys_info, cost }
|
||||
if EntityHasTag(item, "egg_item") then
|
||||
if
|
||||
EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_egg") ~= nil
|
||||
then
|
||||
position_data[gid][5] = true
|
||||
end
|
||||
elseif tg then
|
||||
local f = EntityGetFilename(item)
|
||||
if cap[f] == nil then
|
||||
cap[f] = tonumber(ModSettingGet("quant.ew.rocks") or 16) or 16
|
||||
end
|
||||
if cap[f] == 0 then
|
||||
position_data[gid] = nil
|
||||
goto continue
|
||||
end
|
||||
cap[f] = cap[f] - 1
|
||||
position_data[gid][5] = false
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
local vx, vy = ComponentGetValue2(velocity, "mVelocity")
|
||||
position_data[gid][6] = { vx, vy }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
rpc.update_positions(position_data, all)
|
||||
if #dead_entities > 0 then
|
||||
rpc.handle_death_data(dead_entities)
|
||||
end
|
||||
dead_entities = {}
|
||||
end
|
||||
|
||||
util.add_cross_call("ew_thrown", function(thrown_item)
|
||||
if
|
||||
thrown_item ~= nil
|
||||
and (item_sync.get_global_item_id(thrown_item) == nil or item_sync.is_my_item(
|
||||
item_sync.get_global_item_id(thrown_item)
|
||||
))
|
||||
and EntityGetFirstComponentIncludingDisabled(thrown_item, "VariableStorageComponent", "ew_egg") == nil
|
||||
then
|
||||
item_sync.make_item_global(thrown_item)
|
||||
end
|
||||
end)
|
||||
|
||||
util.add_cross_call("ew_picked", function(picked_item)
|
||||
if picked_item ~= nil and EntityHasTag(picked_item, "ew_global_item") then
|
||||
local gid = item_sync.get_global_item_id(picked_item)
|
||||
if gid ~= nil then
|
||||
if ctx.is_host then
|
||||
item_sync.host_localize_item(gid, ctx.my_id)
|
||||
else
|
||||
rpc.item_localize_req(gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function item_sync.on_world_update()
|
||||
-- TODO check that we not removing item we are going to pick now, instead of checking if picker gui is open.
|
||||
if is_safe_to_remove() then
|
||||
if #pending_remove > 0 then
|
||||
local gid = table.remove(pending_remove)
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
end
|
||||
end
|
||||
if GameGetFrameNum() % 120 == 35 then
|
||||
for _, ent in ipairs(EntityGetWithTag("mimic_potion")) do
|
||||
if not EntityHasTag(ent, "polymorphed_player") and is_item_on_ground(ent) then
|
||||
if not EntityHasTag(ent, "ew_global_item") then
|
||||
if ctx.is_host then
|
||||
item_sync.make_item_global(ent)
|
||||
else
|
||||
EntityKill(ent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, wand in ipairs(EntityGetWithTag("wand")) do
|
||||
local com = EntityGetFirstComponentIncludingDisabled(wand, "ItemComponent")
|
||||
if com ~= nil then
|
||||
ComponentSetValue2(com, "item_pickup_radius", 256)
|
||||
end
|
||||
end
|
||||
end
|
||||
local rt = math.floor(tonumber(ModSettingGet("quant.ew.item_sync") or 4) or 4 + 0.5)
|
||||
local n = 0
|
||||
if rt == 5 then
|
||||
n = 3
|
||||
elseif rt == 3 then
|
||||
n = 1
|
||||
elseif rt == 4 then
|
||||
n = 2
|
||||
end
|
||||
if GameGetFrameNum() % 60 == 3 then
|
||||
send_item_positions(true)
|
||||
elseif rt == 1 or GameGetFrameNum() % rt == n then
|
||||
send_item_positions(false)
|
||||
end
|
||||
if GameGetFrameNum() % 30 == 23 then
|
||||
for gid, num in pairs(wait_for_gid) do
|
||||
if num < GameGetFrameNum() then
|
||||
wait_for_gid[gid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if GameGetFrameNum() % 5 == 4 then
|
||||
mark_in_inventory(ctx.my_player)
|
||||
end
|
||||
|
||||
remove_client_items_from_world()
|
||||
end
|
||||
|
||||
function item_sync.on_should_send_updates()
|
||||
if not ctx.is_host then
|
||||
return
|
||||
end
|
||||
local item_list = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
if is_item_on_ground(item) and not EntityHasTag(item, "mimic_potion") then
|
||||
local item_data = inventory_helper.serialize_single_item(item)
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
if gid ~= nil then
|
||||
item_data.gid = gid
|
||||
table.insert(item_list, item_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
rpc.initial_items(item_list)
|
||||
end
|
||||
|
||||
function item_sync.on_draw_debug_window(imgui)
|
||||
local mx, my = DEBUG_GetMouseWorld()
|
||||
local ent = EntityGetClosestWithTag(mx, my, "ew_global_item")
|
||||
if ent ~= nil and ent ~= 0 then
|
||||
if imgui.CollapsingHeader("Item gid") then
|
||||
local x, y = EntityGetTransform(ent)
|
||||
GameCreateSpriteForXFrames("mods/quant.ew/files/resource/debug/marker.png", x, y, true, 0, 0, 1, true)
|
||||
local gid = item_sync.get_global_item_id(ent)
|
||||
imgui.Text("GID: " .. tostring(gid))
|
||||
local prevented = ctx.item_prevent_localize[gid]
|
||||
if prevented then
|
||||
imgui.Text("Localize prevented")
|
||||
else
|
||||
imgui.Text("Localize allowed")
|
||||
end
|
||||
local on_ground, reason = is_item_on_ground(ent)
|
||||
if on_ground then
|
||||
imgui.Text("On ground: " .. reason)
|
||||
else
|
||||
imgui.Text("Not on ground: " .. reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_stuff_to_globalized_item(item, gid)
|
||||
EntityAddTag(item, "ew_global_item")
|
||||
item_sync.ensure_notify_component(item)
|
||||
local gid_c = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
|
||||
if gid_c == nil then
|
||||
EntityAddComponent2(item, "VariableStorageComponent", {
|
||||
_tags = "ew_global_item_id",
|
||||
value_string = gid,
|
||||
})
|
||||
else
|
||||
ComponentSetValue2(gid_c, "value_string", gid)
|
||||
end
|
||||
ctx.item_prevent_localize[gid] = false
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.initial_items(item_list)
|
||||
-- Only run once ever, as it tends to duplicate items otherwise
|
||||
if GameHasFlagRun("ew_initial_items") then
|
||||
return
|
||||
end
|
||||
GameAddFlagRun("ew_initial_items")
|
||||
for _, item_data in ipairs(item_list) do
|
||||
local item = item_sync.find_by_gid(item_data.gid)
|
||||
if item == nil then
|
||||
local item_new = inventory_helper.deserialize_single_item(item_data)
|
||||
add_stuff_to_globalized_item(item_new, item_data.gid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_globalize(item_data)
|
||||
if wait_for_gid[item_data.gid] ~= nil then
|
||||
if wait_for_gid[item_data.gid] > GameGetFrameNum() + 10000 then
|
||||
return
|
||||
end
|
||||
wait_for_gid[item_data.gid] = GameGetFrameNum() + 30
|
||||
end
|
||||
local a = item_sync.find_by_gid(item_data.gid)
|
||||
if is_safe_to_remove() or not is_wand(a) then
|
||||
local k = item_sync.remove_item_with_id_now(item_data.gid)
|
||||
local n = item_sync.find_by_gid(item_data.gid)
|
||||
if n ~= nil and k ~= n then
|
||||
return
|
||||
end
|
||||
else
|
||||
local n = item_sync.find_by_gid(item_data.gid)
|
||||
if n ~= nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
local item = inventory_helper.deserialize_single_item(item_data)
|
||||
add_stuff_to_globalized_item(item, item_data.gid)
|
||||
for _, com in ipairs(EntityGetComponent(item, "VariableStorageComponent") or {}) do
|
||||
if ComponentGetValue2(com, "name") == "throw_time" then
|
||||
ComponentSetValue2(com, "value_int", GameGetFrameNum())
|
||||
end
|
||||
end
|
||||
local damage_component = EntityGetFirstComponentIncludingDisabled(item, "DamageModelComponent")
|
||||
if damage_component and damage_component ~= 0 then
|
||||
ComponentSetValue2(damage_component, "wait_for_kill_flag_on_death", true)
|
||||
EntityAddComponent2(item, "LuaComponent", {
|
||||
_tags = "ew_immortal",
|
||||
script_damage_about_to_be_received = "mods/quant.ew/files/resource/cbs/immortal.lua",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_localize(l_peer_id, item_id)
|
||||
local item_ent_id = item_sync.find_by_gid(item_id)
|
||||
if item_ent_id ~= nil then
|
||||
for _, handler in ipairs(pickup_handlers) do
|
||||
handler(item_ent_id)
|
||||
end
|
||||
end
|
||||
if l_peer_id ~= ctx.my_id then
|
||||
item_sync.remove_item_with_id(item_id)
|
||||
end
|
||||
end
|
||||
|
||||
rpc.opts_reliable()
|
||||
function rpc.item_localize_req(gid)
|
||||
if not ctx.is_host then
|
||||
return
|
||||
end
|
||||
item_sync.host_localize_item(gid, ctx.rpc_peer_id)
|
||||
end
|
||||
|
||||
local function cleanup(peer)
|
||||
for gid, num in pairs(gid_last_frame_updated[peer]) do
|
||||
if frame[peer] > num then
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if is_item_on_ground(item) then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
gid_last_frame_updated[peer][gid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local is_duplicate = {}
|
||||
for _, item in ipairs(EntityGetWithTag("ew_global_item") or {}) do
|
||||
local gid = item_sync.get_global_item_id(item)
|
||||
if gid ~= nil and is_peers_item(gid, peer) then
|
||||
if is_duplicate[gid] then
|
||||
item_sync.remove_item_with_id(gid)
|
||||
else
|
||||
is_duplicate[gid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.kill_egg(gid)
|
||||
item_sync.remove_item_with_id_now(gid)
|
||||
end
|
||||
|
||||
function rpc.update_positions(position_data, all)
|
||||
if frame[ctx.rpc_peer_id] == nil or all then
|
||||
frame[ctx.rpc_peer_id] = GameGetFrameNum()
|
||||
if gid_last_frame_updated[ctx.rpc_peer_id] == nil then
|
||||
gid_last_frame_updated[ctx.rpc_peer_id] = {}
|
||||
end
|
||||
end
|
||||
local cx, cy = GameGetCameraPos()
|
||||
for gid, el in pairs(position_data) do
|
||||
if table.contains(pending_remove, gid) then
|
||||
goto continue
|
||||
end
|
||||
local x, y = el[1], el[2]
|
||||
local name = EntityGetFilename(item)
|
||||
local is_chest = name == "data/entities/items/pickup/utility_box.xml"
|
||||
or name == "data/entities/items/pickup/chest_random_super.xml"
|
||||
or name == "data/entities/items/pickup/chest_random.xml"
|
||||
if is_chest or el[5] ~= nil or (math.abs(x - cx) < DISTANCE_LIMIT and math.abs(y - cy) < DISTANCE_LIMIT) then
|
||||
if el[5] == nil then
|
||||
gid_last_frame_updated[ctx.rpc_peer_id][gid] = frame[ctx.rpc_peer_id]
|
||||
end
|
||||
local phys_info = el[3]
|
||||
local price = el[4]
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item ~= nil then
|
||||
if not util.set_phys_info(item, phys_info, ctx.rpc_player_data.fps) then
|
||||
EntitySetTransform(item, x, y)
|
||||
if el[6] ~= nil then
|
||||
local vx, vy = el[6][1], el[6][2]
|
||||
local velocity = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
|
||||
if velocity ~= nil then
|
||||
ComponentSetValue2(velocity, "mVelocity", vx, vy)
|
||||
end
|
||||
end
|
||||
end
|
||||
local costcom = EntityGetFirstComponentIncludingDisabled(item, "ItemCostComponent")
|
||||
if costcom ~= nil then
|
||||
if price == 0 then
|
||||
EntitySetComponentsWithTagEnabled(item, "shop_cost", false)
|
||||
ComponentSetValue2(costcom, "cost", 0)
|
||||
else
|
||||
EntitySetComponentsWithTagEnabled(item, "shop_cost", true)
|
||||
ComponentSetValue2(costcom, "cost", price)
|
||||
end
|
||||
end
|
||||
elseif wait_for_gid[gid] == nil then
|
||||
if el[5] == true then
|
||||
rpc.kill_egg(gid)
|
||||
elseif el[5] ~= false then
|
||||
util.log("Requesting again " .. gid)
|
||||
rpc.request_send_again(gid)
|
||||
wait_for_gid[gid] = GameGetFrameNum() + 300
|
||||
end
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
if all then
|
||||
cleanup(ctx.rpc_peer_id)
|
||||
end
|
||||
end
|
||||
|
||||
function rpc.request_send_again(gid)
|
||||
if gid ~= nil and not item_sync.is_my_item(gid) then
|
||||
return
|
||||
end
|
||||
local item = item_sync.find_by_gid(gid)
|
||||
if item == nil then
|
||||
util.log("Requested to send item again, but this item wasn't found: " .. gid)
|
||||
return
|
||||
end
|
||||
if wait_on_send[gid] == nil or wait_on_send[gid] < GameGetFrameNum() then
|
||||
wait_on_send[gid] = GameGetFrameNum() + 240
|
||||
item_sync.make_item_global(item)
|
||||
end
|
||||
end
|
||||
|
||||
ctx.cap.item_sync = {
|
||||
globalize = item_sync.make_item_global,
|
||||
register_pickup_handler = function(handler)
|
||||
table.insert(pickup_handlers, handler)
|
||||
end,
|
||||
}
|
||||
|
||||
item_sync.rpc = rpc
|
||||
|
||||
return item_sync
|
|
@ -1,318 +0,0 @@
|
|||
--- World read / write functionality.
|
||||
---@module 'noitapatcher.nsew.world'
|
||||
local world = {}
|
||||
|
||||
local ffi = require("ffi")
|
||||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
|
||||
print("get_cell: " .. tostring(world_ffi.get_cell))
|
||||
|
||||
local C = ffi.C
|
||||
|
||||
ffi.cdef([[
|
||||
|
||||
enum ENCODE_CONST {
|
||||
// Maximum amount of runs 128*128 pixels can result in, plus one just in case.
|
||||
PIXEL_RUN_MAX = 16385,
|
||||
|
||||
LIQUID_FLAG_STATIC = 1,
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) EncodedAreaHeader {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
|
||||
uint16_t pixel_run_count;
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) PixelRun {
|
||||
uint16_t length;
|
||||
int16_t material;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) EncodedArea {
|
||||
struct EncodedAreaHeader header;
|
||||
struct PixelRun pixel_runs[PIXEL_RUN_MAX];
|
||||
};
|
||||
|
||||
]])
|
||||
|
||||
world.last_material_id = 0
|
||||
|
||||
world.EncodedAreaHeader = ffi.typeof("struct EncodedAreaHeader")
|
||||
world.PixelRun = ffi.typeof("struct PixelRun")
|
||||
world.EncodedArea = ffi.typeof("struct EncodedArea")
|
||||
|
||||
local pliquid_cell = ffi.typeof("struct CLiquidCell*")
|
||||
|
||||
--- Total bytes taken up by the encoded area
|
||||
-- @tparam EncodedArea encoded_area
|
||||
-- @treturn int total number of bytes that encodes the area
|
||||
-- @usage
|
||||
-- local data = ffi.string(area, world.encoded_size(area))
|
||||
-- peer:send(data)
|
||||
function world.encoded_size(encoded_area)
|
||||
return (ffi.sizeof(world.EncodedAreaHeader) + encoded_area.header.pixel_run_count * ffi.sizeof(world.PixelRun))
|
||||
end
|
||||
|
||||
--[[
|
||||
--- Encode the given rectangle of the world
|
||||
-- The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not
|
||||
-- exceed 256 in width or height.
|
||||
-- @param chunk_map
|
||||
-- @tparam int start_x coordinate
|
||||
-- @tparam int start_y coordinate
|
||||
-- @tparam int end_x coordinate
|
||||
-- @tparam int end_y coordinate
|
||||
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
||||
-- @return returns an EncodedArea or nil if the area could not be encoded
|
||||
-- @see decode
|
||||
function world.encode_area(chunk_map, start_x, start_y, end_x, end_y, encoded_area)
|
||||
start_x = ffi.cast('int32_t', start_x)
|
||||
start_y = ffi.cast('int32_t', start_y)
|
||||
end_x = ffi.cast('int32_t', end_x)
|
||||
end_y = ffi.cast('int32_t', end_y)
|
||||
|
||||
encoded_area = encoded_area or world.EncodedArea()
|
||||
|
||||
local width = end_x - start_x
|
||||
local height = end_y - start_y
|
||||
|
||||
if width <= 0 or height <= 0 then
|
||||
print("Invalid world part, negative dimension")
|
||||
return nil
|
||||
end
|
||||
|
||||
if width > 256 or height > 256 then
|
||||
print("Invalid world part, dimension greater than 256")
|
||||
return nil
|
||||
end
|
||||
|
||||
encoded_area.header.x = start_x
|
||||
encoded_area.header.y = start_y
|
||||
encoded_area.header.width = width - 1
|
||||
encoded_area.header.height = height - 1
|
||||
|
||||
local run_count = 1
|
||||
|
||||
local current_run = encoded_area.pixel_runs[0]
|
||||
local run_length = 0
|
||||
local current_material = 0
|
||||
local current_flags = 0
|
||||
|
||||
local y = start_y
|
||||
while y < end_y do
|
||||
local x = start_x
|
||||
while x < end_x do
|
||||
local material_number = 0
|
||||
local flags = 0
|
||||
|
||||
local ppixel = world_ffi.get_cell(chunk_map, x, y)
|
||||
local pixel = ppixel[0]
|
||||
if pixel ~= nil then
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
|
||||
if cell_type ~= C.CELL_TYPE_SOLID then
|
||||
local material_ptr = pixel.vtable.get_material(pixel)
|
||||
material_number = world_ffi.get_material_id(material_ptr)
|
||||
end
|
||||
|
||||
if cell_type == C.CELL_TYPE_LIQUID then
|
||||
local liquid_cell = ffi.cast(pliquid_cell, pixel)
|
||||
if liquid_cell.is_static then
|
||||
flags = bit.bor(flags, C.LIQUID_FLAG_STATIC)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if x == start_x and y == start_y then
|
||||
-- Initial run
|
||||
current_material = material_number
|
||||
current_flags = flags
|
||||
elseif current_material ~= material_number or current_flags ~= flags then
|
||||
-- Next run
|
||||
current_run.length = run_length - 1
|
||||
current_run.material = current_material
|
||||
current_run.flags = current_flags
|
||||
|
||||
if run_count == C.PIXEL_RUN_MAX then
|
||||
print("Area too complicated to encode")
|
||||
return nil
|
||||
end
|
||||
|
||||
current_run = encoded_area.pixel_runs[run_count]
|
||||
run_count = run_count + 1
|
||||
|
||||
run_length = 0
|
||||
current_material = material_number
|
||||
current_flags = flags
|
||||
end
|
||||
|
||||
run_length = run_length + 1
|
||||
|
||||
x = x + 1
|
||||
end
|
||||
y = y + 1
|
||||
end
|
||||
|
||||
current_run.length = run_length - 1
|
||||
current_run.material = current_material
|
||||
current_run.flags = current_flags
|
||||
|
||||
encoded_area.header.pixel_run_count = run_count
|
||||
|
||||
return encoded_area
|
||||
end
|
||||
]]
|
||||
|
||||
--- Encode the given rectangle of the world
|
||||
-- The rectangle defined by {`start_x`, `start_y`, `end_x`, `end_y`} must not
|
||||
-- exceed 256 in width or height.
|
||||
-- @tparam int start_x coordinate
|
||||
-- @tparam int start_y coordinate
|
||||
-- @tparam int end_x coordinate
|
||||
-- @tparam int end_y coordinate
|
||||
-- @tparam EncodedArea encoded_area memory to use, if nil this function allocates its own memory
|
||||
-- @return returns an EncodedArea or nil if the area could not be encoded
|
||||
-- @see decode
|
||||
function world.encode_area(start_x_ini, start_y_ini, end_x_ini, end_y_ini, encoded_area)
|
||||
local start_x = ffi.cast("int32_t", start_x_ini)
|
||||
local start_y = ffi.cast("int32_t", start_y_ini)
|
||||
local end_x = ffi.cast("int32_t", end_x_ini)
|
||||
local end_y = ffi.cast("int32_t", end_y_ini)
|
||||
|
||||
encoded_area = encoded_area or world.EncodedArea()
|
||||
|
||||
local width = end_x - start_x
|
||||
local height = end_y - start_y
|
||||
|
||||
if width <= 0 or height <= 0 then
|
||||
print("Invalid world part, negative dimension")
|
||||
return nil
|
||||
end
|
||||
|
||||
if width > 128 or height > 128 then
|
||||
print("Invalid world part, dimension greater than 128")
|
||||
return nil
|
||||
end
|
||||
|
||||
encoded_area.header.x = start_x
|
||||
encoded_area.header.y = start_y
|
||||
encoded_area.header.width = width - 1
|
||||
encoded_area.header.height = height - 1
|
||||
|
||||
encoded_area.header.pixel_run_count = ewext.encode_area(
|
||||
start_x_ini,
|
||||
start_y_ini,
|
||||
end_x_ini,
|
||||
end_y_ini,
|
||||
tonumber(ffi.cast("intptr_t", encoded_area.pixel_runs))
|
||||
)
|
||||
return encoded_area
|
||||
end
|
||||
|
||||
--local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
|
||||
|
||||
--- Load an encoded area back into the world.
|
||||
-- @param grid_world
|
||||
-- @tparam EncodedAreaHeader header header of the encoded area
|
||||
-- @param received pointer or ffi array of PixelRun from the encoded area
|
||||
-- @see encode_area
|
||||
function world.decode(grid_world, header, pixel_runs)
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
|
||||
local top_left_x = header.x
|
||||
local top_left_y = header.y
|
||||
local width = header.width + 1
|
||||
local height = header.height + 1
|
||||
local bottom_right_x = top_left_x + width
|
||||
local bottom_right_y = top_left_y + height
|
||||
|
||||
local current_run_ix = 0
|
||||
local current_run = pixel_runs[current_run_ix]
|
||||
local new_material = current_run.material
|
||||
local flags = current_run.flags
|
||||
local left = current_run.length + 1
|
||||
|
||||
local y = top_left_y
|
||||
while y < bottom_right_y do
|
||||
local x = top_left_x
|
||||
while x < bottom_right_x do
|
||||
if world_ffi.chunk_loaded(chunk_map, x, y) then
|
||||
local ppixel = world_ffi.get_cell(chunk_map, x, y)
|
||||
|
||||
local current_material = 0
|
||||
|
||||
if new_material == -1 then
|
||||
goto next_pixel
|
||||
end
|
||||
|
||||
if ppixel[0] ~= nil then
|
||||
local pixel = ppixel[0]
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
if cell_type == C.CELL_TYPE_SOLID then
|
||||
goto next_pixel
|
||||
end
|
||||
current_material = world_ffi.get_material_id(pixel.vtable.get_material(pixel))
|
||||
|
||||
if new_material ~= current_material then
|
||||
world_ffi.remove_cell(grid_world, pixel, x, y, false)
|
||||
end
|
||||
end
|
||||
|
||||
if current_material ~= new_material and new_material ~= 0 then
|
||||
if new_material > world.last_material_id then
|
||||
goto next_pixel
|
||||
end
|
||||
local mat_ptr = world_ffi.get_material_ptr(new_material)
|
||||
if mat_ptr == nil then
|
||||
goto next_pixel
|
||||
end
|
||||
local pixel = world_ffi.construct_cell(grid_world, x, y, mat_ptr, nil)
|
||||
if pixel == nil then
|
||||
-- TODO: This can happen when the material texture has a
|
||||
-- transparent pixel at the given coordinate. There's
|
||||
-- probably a better way to deal with this, but for now
|
||||
-- we skip positions like this.
|
||||
goto next_pixel
|
||||
end
|
||||
|
||||
local cell_type = pixel.vtable.get_cell_type(pixel)
|
||||
|
||||
if cell_type == C.CELL_TYPE_LIQUID then
|
||||
local liquid_cell = ffi.cast(pliquid_cell, pixel)
|
||||
liquid_cell.is_static = bit.band(flags, C.CELL_TYPE_LIQUID) == C.LIQUID_FLAG_STATIC
|
||||
end
|
||||
|
||||
ppixel[0] = pixel
|
||||
end
|
||||
end
|
||||
|
||||
::next_pixel::
|
||||
|
||||
left = left - 1
|
||||
if left <= 0 then
|
||||
current_run_ix = current_run_ix + 1
|
||||
if current_run_ix >= header.pixel_run_count then
|
||||
-- No more runs, done
|
||||
assert(x == bottom_right_x - 1)
|
||||
assert(y == bottom_right_y - 1)
|
||||
return
|
||||
end
|
||||
|
||||
current_run = pixel_runs[current_run_ix]
|
||||
new_material = current_run.material
|
||||
flags = current_run.flags
|
||||
left = current_run.length + 1
|
||||
end
|
||||
|
||||
x = x + 1
|
||||
end
|
||||
y = y + 1
|
||||
end
|
||||
end
|
||||
|
||||
return world
|
|
@ -1,226 +0,0 @@
|
|||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
local world = dofile_once("mods/quant.ew/files/system/world_sync/world.lua")
|
||||
local rect = require("noitapatcher.nsew.rect")
|
||||
local ffi = require("ffi")
|
||||
|
||||
-- local rpc = net.new_rpc_namespace()
|
||||
|
||||
--local rect_optimiser = rect.Optimiser_new()
|
||||
local encoded_area = world.EncodedArea()
|
||||
|
||||
local world_sync = {}
|
||||
|
||||
local KEY_WORLD_FRAME = 0
|
||||
local KEY_WORLD_END = 1
|
||||
|
||||
local CHUNK_SIZE = 128
|
||||
|
||||
local iter_fast = 0
|
||||
|
||||
local iter_slow = 0
|
||||
|
||||
local iter_slow_2 = 0
|
||||
|
||||
--[[local function do_benchmark()
|
||||
local world_ffi = require("noitapatcher.nsew.world_ffi")
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
local start = GameGetRealWorldTimeSinceStarted()
|
||||
local iters = 10000
|
||||
for i=1, iters do
|
||||
world.encode_area(chunk_map, 0, 0, 128, 128, encode_area)
|
||||
-- world_ffi.get_cell(chunk_map, 0, 0)
|
||||
end
|
||||
local end_time = GameGetRealWorldTimeSinceStarted()
|
||||
local elapsed = (end_time - start) * 1000 * 1000 * 1000 / (iters * 128 * 128)
|
||||
print("Benchmark:", elapsed, "ns/pixel")
|
||||
end]]
|
||||
|
||||
function world_sync.on_world_initialized()
|
||||
local c = 0
|
||||
while true do
|
||||
local name = CellFactory_GetName(c)
|
||||
if name == "unknown" then
|
||||
break
|
||||
end
|
||||
c = c + 1
|
||||
end
|
||||
c = c - 1
|
||||
print("Last material id: " .. c)
|
||||
world.last_material_id = c
|
||||
-- do_benchmark()
|
||||
end
|
||||
|
||||
local function send_chunks(cx, cy)
|
||||
local chx, chy = cx * CHUNK_SIZE, cy * CHUNK_SIZE
|
||||
local crect = rect.Rectangle(chx, chy, chx + CHUNK_SIZE, chy + CHUNK_SIZE)
|
||||
if DoesWorldExistAt(crect.left, crect.top, crect.right, crect.bottom) then
|
||||
local area = world.encode_area(crect.left, crect.top, crect.right, crect.bottom, encoded_area)
|
||||
if area ~= nil then
|
||||
--if ctx.proxy_opt.debug then
|
||||
-- GameCreateSpriteForXFrames("mods/quant.ew/files/resource/debug/box_128x128.png", crect.left+64, crect.top + 64, true, 0, 0, 11, true)
|
||||
--end
|
||||
local str = ffi.string(area, world.encoded_size(area))
|
||||
net.proxy_bin_send(KEY_WORLD_FRAME, str)
|
||||
end
|
||||
end
|
||||
end
|
||||
local int = 4 -- ctx.proxy_opt.world_sync_interval
|
||||
|
||||
local function get_all_chunks(ocx, ocy, pos_data, priority, give_0)
|
||||
--local grid_world = world_ffi.get_grid_world()
|
||||
--local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||
--local thread_impl = grid_world.mThreadImpl
|
||||
if GameGetFrameNum() % int == 0 then
|
||||
send_chunks(ocx, ocy)
|
||||
local pri = priority
|
||||
if give_0 then
|
||||
pri = 0
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(pri) .. pos_data)
|
||||
elseif GameGetFrameNum() % int == 2 then
|
||||
if iter_fast == 0 then
|
||||
send_chunks(ocx + 1, ocy)
|
||||
send_chunks(ocx + 1, ocy + 1)
|
||||
elseif iter_fast == 1 then
|
||||
send_chunks(ocx, ocy + 1)
|
||||
send_chunks(ocx - 1, ocy + 1)
|
||||
elseif iter_fast == 2 then
|
||||
send_chunks(ocx - 1, ocy)
|
||||
send_chunks(ocx - 1, ocy - 1)
|
||||
else
|
||||
send_chunks(ocx, ocy - 1)
|
||||
send_chunks(ocx + 1, ocy - 1)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 1, 16)) .. pos_data)
|
||||
iter_fast = iter_fast + 1
|
||||
if iter_fast == 4 then
|
||||
iter_fast = 0
|
||||
end
|
||||
elseif GameGetFrameNum() % (int * 4) == 3 then
|
||||
if iter_slow == 0 then
|
||||
send_chunks(ocx + 2, ocy - 1)
|
||||
send_chunks(ocx + 2, ocy)
|
||||
send_chunks(ocx + 2, ocy + 1)
|
||||
send_chunks(ocx + 2, ocy + 2)
|
||||
elseif iter_slow == 1 then
|
||||
send_chunks(ocx + 1, ocy + 2)
|
||||
send_chunks(ocx, ocy + 2)
|
||||
send_chunks(ocx - 1, ocy + 2)
|
||||
send_chunks(ocx - 2, ocy + 2)
|
||||
elseif iter_slow == 2 then
|
||||
send_chunks(ocx - 2, ocy + 1)
|
||||
send_chunks(ocx - 2, ocy)
|
||||
send_chunks(ocx - 2, ocy - 1)
|
||||
send_chunks(ocx - 2, ocy - 2)
|
||||
else
|
||||
send_chunks(ocx - 1, ocy - 2)
|
||||
send_chunks(ocx, ocy - 2)
|
||||
send_chunks(ocx + 1, ocy - 2)
|
||||
send_chunks(ocx + 2, ocy - 2)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 2, 16)) .. pos_data)
|
||||
iter_slow = iter_slow + 1
|
||||
if iter_slow == 4 then
|
||||
iter_slow = 0
|
||||
end
|
||||
elseif (priority == 0 and not GameHasFlagRun("ending_game_completed")) and GameGetFrameNum() % (int * 3) == 1 then
|
||||
if iter_slow_2 == 0 then
|
||||
send_chunks(ocx + 3, ocy)
|
||||
send_chunks(ocx + 3, ocy + 1)
|
||||
send_chunks(ocx + 3, ocy + 2)
|
||||
send_chunks(ocx + 3, ocy + 3)
|
||||
elseif iter_slow_2 == 1 then
|
||||
send_chunks(ocx + 2, ocy + 3)
|
||||
send_chunks(ocx + 1, ocy + 3)
|
||||
send_chunks(ocx, ocy + 3)
|
||||
send_chunks(ocx - 1, ocy + 3)
|
||||
elseif iter_slow_2 == 2 then
|
||||
send_chunks(ocx - 2, ocy + 3)
|
||||
send_chunks(ocx - 3, ocy + 3)
|
||||
send_chunks(ocx - 3, ocy + 2)
|
||||
send_chunks(ocx - 3, ocy + 1)
|
||||
elseif iter_slow_2 == 3 then
|
||||
send_chunks(ocx - 3, ocy)
|
||||
send_chunks(ocx - 3, ocy - 1)
|
||||
send_chunks(ocx - 3, ocy - 2)
|
||||
send_chunks(ocx - 3, ocy - 3)
|
||||
elseif iter_slow_2 == 4 then
|
||||
send_chunks(ocx - 2, ocy - 3)
|
||||
send_chunks(ocx - 1, ocy - 3)
|
||||
send_chunks(ocx, ocy - 3)
|
||||
send_chunks(ocx + 1, ocy - 3)
|
||||
else
|
||||
send_chunks(ocx + 2, ocy - 3)
|
||||
send_chunks(ocx + 3, ocy - 3)
|
||||
send_chunks(ocx + 3, ocy - 2)
|
||||
send_chunks(ocx + 3, ocy - 1)
|
||||
end
|
||||
net.proxy_bin_send(KEY_WORLD_END, string.char(math.min(priority + 2, 16)) .. pos_data)
|
||||
iter_slow_2 = iter_slow_2 + 1
|
||||
if iter_slow_2 == 6 then
|
||||
iter_slow_2 = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local wait
|
||||
|
||||
function world_sync.on_world_update()
|
||||
if ctx.run_ended or (wait ~= nil and wait > GameGetFrameNum()) then
|
||||
return
|
||||
end
|
||||
wait = nil
|
||||
int = math.floor(tonumber(ModSettingGet("quant.ew.world_sync") or 4) or 4 + 0.5)
|
||||
local cx, cy = GameGetCameraPos()
|
||||
cx, cy = math.floor(cx / CHUNK_SIZE), math.floor(cy / CHUNK_SIZE)
|
||||
local player_data = ctx.my_player
|
||||
if not EntityGetIsAlive(player_data.entity) then
|
||||
return
|
||||
end
|
||||
local px, py = EntityGetTransform(player_data.entity)
|
||||
-- Original Chunk x/y
|
||||
local ocx, ocy = math.floor(px / CHUNK_SIZE), math.floor(py / CHUNK_SIZE)
|
||||
local n = 0
|
||||
if EntityHasTag(ctx.my_player.entity, "ew_notplayer") or GameHasFlagRun("ending_game_completed") then
|
||||
n = 1
|
||||
end
|
||||
local pos_data
|
||||
if GameGetFrameNum() % int ~= 0 and GameGetFrameNum() % (int * 4) == 3 then
|
||||
pos_data = ocx .. ":" .. ocy .. ":" .. cx .. ":" .. cy .. ":" .. n .. ":" .. ctx.proxy_opt.world_num
|
||||
else
|
||||
pos_data = ctx.proxy_opt.world_num
|
||||
end
|
||||
if math.abs(cx - ocx) > 2 or math.abs(cy - ocy) > 2 then
|
||||
if ctx.spectating_over_peer_id ~= nil and ctx.spectating_over_peer_id ~= ctx.my_id then
|
||||
if GameGetFrameNum() % 3 ~= 2 then
|
||||
get_all_chunks(cx, cy, pos_data, 16, false)
|
||||
else
|
||||
get_all_chunks(ocx, ocy, pos_data, 16, true)
|
||||
end
|
||||
else
|
||||
wait = GameGetFrameNum() + 30
|
||||
end
|
||||
else
|
||||
local pri = 0
|
||||
if EntityHasTag(ctx.my_player.entity, "ew_notplayer") then
|
||||
pri = 16
|
||||
end
|
||||
get_all_chunks(ocx, ocy, pos_data, pri, true)
|
||||
end
|
||||
end
|
||||
|
||||
local PixelRun_const_ptr = ffi.typeof("struct PixelRun const*")
|
||||
|
||||
function world_sync.handle_world_data(datum)
|
||||
local grid_world = world_ffi.get_grid_world()
|
||||
local header = ffi.cast("struct EncodedAreaHeader const*", ffi.cast("char const*", datum))
|
||||
local runs = ffi.cast(PixelRun_const_ptr, ffi.cast("const char*", datum) + ffi.sizeof(world.EncodedAreaHeader))
|
||||
world.decode(grid_world, header, runs)
|
||||
end
|
||||
|
||||
net.net_handling.proxy[0] = function(_, value)
|
||||
world_sync.handle_world_data(value)
|
||||
end
|
||||
|
||||
return world_sync
|
|
@ -91,7 +91,7 @@ local function load_modules()
|
|||
ctx.dofile_and_add_hooks("mods/quant.ew/files/system/weather_sync.lua")
|
||||
ctx.load_system("polymorph")
|
||||
|
||||
ctx.load_system("world_sync")
|
||||
-- ctx.load_system("world_sync")
|
||||
|
||||
-- ctx.load_system("spawn_hooks")
|
||||
ctx.dofile_and_add_hooks("mods/quant.ew/files/system/proxy_info.lua")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue