refactor a bunch of things to get ready for rust world sync

This commit is contained in:
bgkillas 2025-07-13 21:06:05 -04:00
parent 4ff165b815
commit 067570672d
32 changed files with 540 additions and 3126 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")