From 68669d390a1ac472f43e86eadb8d5f13e682caae Mon Sep 17 00:00:00 2001 From: DaddelZeit <141176220+DaddelZeit@users.noreply.github.com> Date: Fri, 3 Apr 2026 21:32:03 +0200 Subject: [PATCH 1/5] Use string.buffer for inter-VM communication (#183) * Move from jsonEncode/jsonDecode to string.buffer, fix string.format syntax * Remove spammy electrics print * Fix missing "time_past" field when vehicle comes back into active view * Remove unnecessary Lua queue --- .../lua/ge/extensions/kisstransform.lua | 15 ++++++---- .../lua/ge/extensions/vehiclemanager.lua | 29 +++++++++++-------- .../extensions/kiss_mp/kiss_couplers.lua | 11 +++++-- .../extensions/kiss_mp/kiss_electrics.lua | 12 +++++--- .../extensions/kiss_mp/kiss_gearbox.lua | 6 ++-- .../vehicle/extensions/kiss_mp/kiss_input.lua | 6 ++-- .../extensions/kiss_mp/kiss_transforms.lua | 7 +++-- .../extensions/kiss_mp/kiss_vehicle.lua | 11 +++++-- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kisstransform.lua b/KISSMultiplayer/lua/ge/extensions/kisstransform.lua index e611f114..acd7cda4 100644 --- a/KISSMultiplayer/lua/ge/extensions/kisstransform.lua +++ b/KISSMultiplayer/lua/ge/extensions/kisstransform.lua @@ -1,5 +1,7 @@ local M = {} +local string_buffer = require("string.buffer") + local generation = 0 local timer = 0 @@ -42,8 +44,9 @@ local function update(dt) vehicle:setActive(1) M.inactive[id] = false end - vehicle:queueLuaCommand("kiss_transforms.set_target_transform(" .. string.format("%q", jsonEncode(transform)) .. ")") - vehicle:queueLuaCommand("kiss_transforms.update("..dt..")") + vehicle:queueLuaCommand(string.format( + "kiss_transforms.update(%f)", + dt)) end end end @@ -60,14 +63,16 @@ local function update_vehicle_transform(data) M.received_transforms[id] = transform local vehicle = be:getObjectByID(id) + transform.time_past = clamp(vehiclemanager.get_current_time() - transform.sent_at, 0, 0.1) * 0.9 + 0.001 if vehicle and (not M.inactive[id]) then - transform.time_past = clamp(vehiclemanager.get_current_time() - transform.sent_at, 0, 0.1) * 0.9 + 0.001 - vehicle:queueLuaCommand("kiss_transforms.set_target_transform(" .. string.format("%q", jsonEncode(transform)) .. ")") + vehicle:queueLuaCommand(string.format( + "kiss_transforms.set_target_transform(%q)", + string_buffer.encode(transform))) end end local function push_transform(id, t) - M.local_transforms[id] = jsonDecode(t) + M.local_transforms[id] = string_buffer.decode(t) end M.send_transform_updates = send_transform_updates diff --git a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua index 05c5a2e7..dd87a14e 100644 --- a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua +++ b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua @@ -1,6 +1,6 @@ local M = {} -local messagepack = require("lua/common/libs/Lua-MessagePack/MessagePack") +local string_buffer = require("string.buffer") local timer = 0 local generation = 0 @@ -149,11 +149,11 @@ local function send_vehicle_config(vehicle_id) end end -local function send_vehicle_config_inner(id, parts_config, data) +local function send_vehicle_config_inner(id, parts_config_json, buffer_data) + local data = string_buffer.decode(buffer_data) for k, v in pairs(M.id_map) do if v == id and not M.ownership[id] then return end end - local data = jsonDecode(data) local vehicle = be:getObjectByID(id) local metal_data = vehicle:getMetallicPaintData() local color = vehicle.color @@ -163,7 +163,7 @@ local function send_vehicle_config_inner(id, parts_config, data) local position = vec3(data.position) local rotation = quat(data.rotation) local vehicle_data = {} - vehicle_data.parts_config = parts_config + vehicle_data.parts_config = parts_config_json vehicle_data.in_game_id = id vehicle_data.color = color_to_table(color, metal_data[1]) vehicle_data.palete_0 = color_to_table(palete_0, metal_data[2]) @@ -326,8 +326,11 @@ local function update_vehicle(data) kisstransform.update_vehicle_transform(data) if not kisstransform.inactive[id] then - vehicle:queueLuaCommand("kiss_input.apply(" .. string.format("%q", jsonEncode(data.electrics)) .. ")") - vehicle:queueLuaCommand("kiss_gearbox.apply(" .. string.format("%q", jsonEncode(data.gearbox)) .. ")") + vehicle:queueLuaCommand(string.format( + [[kiss_input.apply(%q) + kiss_gearbox.apply(%q)]], + string_buffer.encode(data.electrics), + string_buffer.encode(data.gearbox))) end end @@ -414,13 +417,15 @@ local function electrics_diff_update(data) if id and not M.ownership[id] then local vehicle = be:getObjectByID(id) if not vehicle then return end - local data = jsonEncode(data[2].diff) - vehicle:queueLuaCommand("kiss_electrics.apply_diff(" .. string.format("%q", data) .. ")") + local data = data[2].diff + vehicle:queueLuaCommand(string.format( + "kiss_electrics.apply_diff(%q)", + string_buffer.encode(data))) end end -local function attach_coupler_inner(data) - local data = jsonDecode(data) +local function attach_coupler_inner(buffer_data) + local data = string_buffer.decode(buffer_data) data.obj_a = M.server_ids[data.obj_a] data.obj_b = M.server_ids[data.obj_b] network.send_data( @@ -431,8 +436,8 @@ local function attach_coupler_inner(data) ) end -local function detach_coupler_inner(data) - local data = jsonDecode(data) +local function detach_coupler_inner(buffer_data) + local data = string_buffer.decode(buffer_data) data.obj_a = M.server_ids[data.obj_a] data.obj_b = M.server_ids[data.obj_b] network.send_data( diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua index f4de5af5..441c8f62 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua @@ -1,4 +1,7 @@ local M = {} + +local string_buffer = require("string.buffer") + local ownership = false local ignore_attachment = false local ignore_detachment = false @@ -33,7 +36,9 @@ local function onCouplerAttached(node_id, obj2_id, obj2_node_id) node_a_id = node_id, node_b_id = obj2_node_id } - obj:queueGameEngineLua("vehiclemanager.attach_coupler_inner(\'"..jsonEncode(data).."\')") + obj:queueGameEngineLua(string.format( + "vehiclemanager.attach_coupler_inner(%q)", + string_buffer.encode(data))) end local function onCouplerDetached(node_id, obj2_id, obj2_node_id) @@ -49,7 +54,9 @@ local function onCouplerDetached(node_id, obj2_id, obj2_node_id) node_a_id = node_id, node_b_id = obj2_node_id } - obj:queueGameEngineLua("vehiclemanager.detach_coupler_inner(\'"..jsonEncode(data).."\')") + obj:queueGameEngineLua(string.format( + "vehiclemanager.detach_coupler_inner(%q)", + string_buffer.encode(data))) end local function kissUpdateOwnership(owned) diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua index 53e422f0..bb55d947 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua @@ -1,4 +1,7 @@ local M = {} + +local string_buffer = require("string.buffer") + local prev_electrics = {} local prev_signal_electrics = {} local last_engine_state = true @@ -143,8 +146,9 @@ local function send() ElectricsUndefinedUpdate = {obj:getID(), data} } if diff_count > 0 then - print("=== ELECTRICS BEING SENT ===\n" .. jsonEncode(data)) - obj:queueGameEngineLua("network.send_data(\'"..jsonEncode(data).."\', true)") + obj:queueGameEngineLua(string.format( + "network.send_data(%q, true)", + jsonEncode(data))) end end @@ -189,8 +193,8 @@ local function update_advanced_coupler_state(coupler_control_controller, value) end end -local function apply_diff(data) - local diff = jsonDecode(data) +local function apply_diff(buffer_data) + local diff = string_buffer.decode(buffer_data) apply_diff_signals(diff) for k, v in pairs(diff) do electrics.values[k] = v diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua index e075ce57..31802de8 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua @@ -1,5 +1,7 @@ local M = {} +local string_buffer = require("string.buffer") + local mainController = nil local gearbox = nil @@ -62,8 +64,8 @@ local function get_gearbox_data() return data end -local function apply(data) - local data = jsonDecode(data) +local function apply(buffer_data) + local data = string_buffer.decode(buffer_data) set_gear_indices(data.gear_indices) end diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_input.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_input.lua index 083df2db..4908d5c0 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_input.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_input.lua @@ -1,7 +1,9 @@ local M = {} -local function apply(data) - local data = jsonDecode(data) +local string_buffer = require("string.buffer") + +local function apply(buffer_data) + local data = string_buffer.decode(buffer_data) input.event("throttle", data.throttle_input, 1) input.event("brake", data.brake_input, 2) input.event("parkingbrake", data.parkingbrake, 2) diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua index 2b69cecd..207b0786 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua @@ -1,4 +1,7 @@ local M = {} + +local string_buffer = require("string.buffer") + local cooldown_timer = 2 M.received_transform = { @@ -115,8 +118,8 @@ local function update(dt) end end -local function set_target_transform(raw) - local transform = jsonDecode(raw) +local function set_target_transform(buffer_data) + local transform = string_buffer.decode(buffer_data) local time_dif = clamp((transform.sent_at - M.received_transform.sent_at), 0.01, 0.1) M.received_transform.acceleration = (vec3(transform.velocity) - M.received_transform.velocity) / time_dif diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua index 3c35d7b9..3a728f75 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua @@ -1,4 +1,7 @@ local M = {} + +local string_buffer = require("string.buffer") + local parts_config = v.config local nodes = {} local ref_nodes = {} @@ -95,7 +98,9 @@ local function update_transform_info() vel_roll = obj:getRollAngularVelocity(), vel_yaw = obj:getYawAngularVelocity(), } - obj:queueGameEngineLua("kisstransform.push_transform("..obj:getID()..", " .. string.format("%q", jsonEncode(transform)) .. ")") + obj:queueGameEngineLua(string.format( + "kisstransform.push_transform(%d, %q)", + obj:getID(), string_buffer.encode(transform))) end local function apply_linear_velocity(x, y, z) @@ -141,7 +146,9 @@ local function send_vehicle_config() position = {p.x, p.y, p.z}, rotation = {r.x, r.y, r.z, r.w}, } - obj:queueGameEngineLua("vehiclemanager.send_vehicle_config_inner("..obj:getID()..", " .. string.format("%q", jsonEncode(config)) .. ", " .. string.format("%q", jsonEncode(data)) .. ")") + obj:queueGameEngineLua(string.format( + "vehiclemanager.send_vehicle_config_inner(%d, %q, %q)", + obj:getID(), jsonEncode(config), string_buffer.encode(data))) end M.update_transform_info = update_transform_info From 54d1b6c8b456394d52a979339a89cec88b0cb764 Mon Sep 17 00:00:00 2001 From: florinm03 <116092053+florinm03@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:29:38 +0200 Subject: [PATCH 2/5] Mods download improvements (#178) * show download speed * fix(download): stabilize speed/progress metrics and improve packet processing * add ETA * keep track of already downloaded mods * queue for one mod at a time * remove unused and tidied code; delete incomplete mod download; added catches for more cases --------- Co-authored-by: Vlad118 --- .../lua/ge/extensions/kissmp/ui/download.lua | 47 ++++- KISSMultiplayer/lua/ge/extensions/network.lua | 189 ++++++++++++------ 2 files changed, 167 insertions(+), 69 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua index d5872448..bb2332ca 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/download.lua @@ -5,6 +5,18 @@ local function bytes_to_mb(bytes) return (bytes / 1024) / 1024 end +local function format_eta(seconds) + if seconds < 0 then seconds = 0 end + local hours = math.floor(seconds / 3600) + local minutes = math.floor((seconds % 3600) / 60) + local secs = math.floor(seconds % 60) + + if hours > 0 then + return string.format("%02d:%02d:%02d", hours, minutes, secs) + end + return string.format("%02d:%02d", minutes, secs) +end + local function draw(gui) if not kissui.show_download then return end @@ -37,19 +49,41 @@ local function draw(gui) imgui.ProgressBar(download_status.progress, imgui.ImVec2(split_width, 0)) local mod = kissmods.mods[download_status.name] - total_size = total_size + mod.size - downloaded_size = downloaded_size + (mod.size * download_status.progress) + local mod_size = (mod and mod.size) or 0 + total_size = total_size + mod_size + downloaded_size = downloaded_size + (mod_size * download_status.progress) end end imgui.EndChild() - total_size = bytes_to_mb(total_size) - downloaded_size = bytes_to_mb(downloaded_size) - local progress = downloaded_size / total_size + local total_size_bytes = network.download_total_bytes or 0 + local downloaded_size_bytes = network.downloaded_bytes or 0 + if total_size_bytes <= 0 then + total_size_bytes = total_size + downloaded_size_bytes = downloaded_size + end + + total_size = bytes_to_mb(total_size_bytes) + downloaded_size = bytes_to_mb(downloaded_size_bytes) local progress_text = tostring(math.floor(downloaded_size)) .. "MB / " .. tostring(math.floor(total_size)) .. "MB" + local elapsed = 0 + if (network.download_start_time or 0) > 0 then + elapsed = socket.gettime() - network.download_start_time + end + if elapsed <= 0 then elapsed = 0.001 end + local progress_speed = downloaded_size / elapsed + local speed_text = tostring(math.floor(progress_speed)) .. "MB/s" + + local eta_text = "--:--" + if progress_speed > 0 and downloaded_size < total_size then + local eta_seconds = (total_size - downloaded_size) / progress_speed + eta_text = format_eta(eta_seconds) + end + content_width = imgui.GetWindowContentRegionWidth() - split_width = content_width * 0.495 + split_width = content_width * 0.450 + progress_text = progress_text .. " (" .. speed_text .. ", ETA " .. eta_text .. ")" local text_size = imgui.CalcTextSize(progress_text) local extra_size = split_width - text_size.x @@ -60,7 +94,6 @@ local function draw(gui) end imgui.SameLine() if imgui.Button("Cancel###cancel_download", imgui.ImVec2(split_width, -1)) then - network.cancel_download() kissui.show_download = false network.disconnect() end diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index 905f3690..052e12ca 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -8,8 +8,10 @@ M.downloads = {} M.downloading = false M.downloads_status = {} M.downloads_received = {} - -local current_download = nil +M.download_start_time = 0 +M.download_total_bytes = 0 +M.downloaded_bytes = 0 +M.download_queue = {} local socket = require("socket") local messagepack = require("lua/common/libs/Lua-MessagePack/MessagePack") @@ -25,12 +27,10 @@ M.connection = { heartbeat_time = 1, timer = 0, tickrate = 33, - mods_left = 0, ping = 0, time_offset = 0 } -local FILE_TRANSFER_CHUNK_SIZE = 16384; local CHUNK_SIZE = 65000 -- Safe size under 65536 limit local message_handlers = {} @@ -67,6 +67,28 @@ local function bytesToU32(str) ) end +local function cancel_download() +for name, file_handle in pairs(M.downloads) do + file_handle:close() + + -- Delete partial file + local file_path = "/kissmp_mods/" .. name + if FS:fileExists(file_path) then + FS:removeFile(file_path) + end + end + + -- Clear the tables so dead file handles aren't reused + M.downloads = {} + M.downloads_status = {} + M.downloads_received = {} + M.downloading = false + M.download_start_time = 0 + M.download_total_bytes = 0 + M.downloaded_bytes = 0 + M.download_queue = {} +end + local function disconnect(data) local text = "Disconnected!" if data then @@ -74,7 +96,13 @@ local function disconnect(data) end kissui.chat.add_message(text) M.connection.connected = false - M.connection.tcp:close() + if M.connection.tcp then + M.connection.tcp:close() + M.connection.tcp = nil + end + + cancel_download() + M.players = {} kissplayers.players = {} kissplayers.player_transforms = {} @@ -96,24 +124,6 @@ local function handle_disconnected(data) disconnect(data) end -local function handle_file_transfer(data) - kissui.show_download = true - -- local file_len = ffi.cast("uint32_t*", ffi.new("char[?]", 5, data:sub(1, 4)))[0] - local file_len = bytesToU32(data:sub(1, 4)) - local file_name = data:sub(5, #data) - local chunks = math.floor(file_len / FILE_TRANSFER_CHUNK_SIZE) - - current_download = { - file_len = file_len, - file_name = file_name, - chunks = chunks, - last_chunk = file_len - chunks * FILE_TRANSFER_CHUNK_SIZE, - current_chunk = 0, - file = kissmods.open_file(file_name) - } - M.downloading = true -end - local function handle_player_info(player_info) M.players[player_info.id] = player_info end @@ -282,6 +292,10 @@ local function connect(addr, player_name, is_public) disconnect() end M.players = {} + M.download_start_time = 0 + M.download_queue = {} + M.download_total_bytes = 0 + M.downloaded_bytes = 0 print("Connecting...") addr = sanitize_addr(addr) @@ -348,16 +362,26 @@ local function connect(addr, player_name, is_public) local missing_mods = {} local mod_names = {} + local available_mods = {} + local total_missing_bytes = 0 for _, mod in pairs(kissmods.mods) do table.insert(mod_names, mod.name) if mod.status ~= "ok" then table.insert(missing_mods, mod.name) M.downloads_status[mod.name] = {name = mod.name, progress = 0} + total_missing_bytes = total_missing_bytes + (mod.size or 0) + else + table.insert(available_mods, mod.name) end end - M.connection.mods_left = #missing_mods + + M.download_total_bytes = total_missing_bytes + M.downloaded_bytes = 0 kissmods.deactivate_all_mods() + if #available_mods > 0 then + kissmods.mount_mods(available_mods) + end for k, v in pairs(missing_mods) do print(k.." "..v) end @@ -368,7 +392,11 @@ local function connect(addr, player_name, is_public) disconnect() return else - send_data({ RequestMods = missing_mods }, true) + M.download_queue = missing_mods + local next_mod = table.remove(M.download_queue, 1) + if next_mod then + send_data({ RequestMods = { next_mod } }, true) + end end end vehiclemanager.loading_map = true @@ -390,6 +418,7 @@ local function send_messagepack(data_type, reliable, data) end local function on_finished_download() + M.download_start_time = 0 vehiclemanager.loading_map = true change_map(M.connection.server_info.map) end @@ -404,22 +433,6 @@ local function send_ping() ) end -local function cancel_download() - --[[if not current_download then return end - io.close(current_download.file) - current_download = nil - M.downloading = false]]-- - for k, v in pairs(M.downloads) do - M.downloads[k]:close() - end - - -- Clear the tables so dead file handles aren't reused - M.downloads = {} - M.downloads_status = {} - M.downloads_received = {} - M.downloading = false -end - local function onUpdate(dt) if not M.connection.connected then return end if M.connection.timer < M.connection.heartbeat_time then @@ -429,24 +442,37 @@ local function onUpdate(dt) send_ping() end - while true do + local packets_processed = 0 + local max_packets_per_update = 64 + + while packets_processed < max_packets_per_update do local msg_type = M.connection.tcp:receive(1) if not msg_type then break end - --print("msg_t"..string.byte(msg_type)) + packets_processed = packets_processed + 1 + M.connection.tcp:settimeout(5.0) - -- JSON data + if string.byte(msg_type) == 1 then - local data = M.connection.tcp:receive(4) - if not data then break end - local len = bytesToU32(data) + local len_b = M.connection.tcp:receive(4) + if not len_b then + M.connection.tcp:settimeout(0.0) + break + end + + local len = bytesToU32(len_b) local data, _, _ = M.connection.tcp:receive(len) M.connection.tcp:settimeout(0.0) + if not data then break end + local data_decoded = jsonDecode(data) - for k, v in pairs(data_decoded) do - if message_handlers[k] then - message_handlers[k](v) + if data_decoded then + for k, v in pairs(data_decoded) do + if message_handlers[k] then + message_handlers[k](v) + end end end + elseif string.byte(msg_type) == 0 then -- Binary data if M.is_server_public then kissui.chat.add_message("Connection rejected: Server tried to download a mod.", kissui.COLOR_RED) @@ -454,26 +480,47 @@ local function onUpdate(dt) return end + if M.download_start_time == 0 then + M.download_start_time = socket.gettime() + end + local name_b = M.connection.tcp:receive(4) - if not name_b then break end + if not name_b then + M.connection.tcp:settimeout(0.0) + break + end M.downloading = true kissui.show_download = true + local len_n = bytesToU32(name_b) local name, _, _ = M.connection.tcp:receive(len_n) local chunk_n_b = M.connection.tcp:receive(4) local chunk_a_b = M.connection.tcp:receive(4) local read_size_b = M.connection.tcp:receive(4) + + if not name or not chunk_n_b or not chunk_a_b or not read_size_b then + M.connection.tcp:settimeout(0.0) + break + end + local chunk_n = bytesToU32(chunk_n_b) local chunk_a = bytesToU32(chunk_a_b) local read_size = bytesToU32(read_size_b) local file_length = chunk_a local file_data, _, _ = M.connection.tcp:receive(read_size) + M.connection.tcp:settimeout(0.0) + if not file_data then break end + if not M.downloads_received[name] then M.downloads_received[name] = 0 end M.downloads_received[name] = M.downloads_received[name] + read_size + M.downloaded_bytes = M.downloaded_bytes + read_size + if M.download_total_bytes > 0 and M.downloaded_bytes > M.download_total_bytes then + M.downloaded_bytes = M.download_total_bytes + end M.downloads_status[name] = { name = name, @@ -489,32 +536,50 @@ local function onUpdate(dt) if file and file_data then file:write(file_data) + else + kissui.chat.add_message("Error: Could not write file to disk. Check permissions or disk space.", kissui.COLOR_RED) + disconnect("File write error") + return end if M.downloads_received[name] >= file_length then - kissmods.mount_mod(name) - if M.downloads[name] then M.downloads[name]:close() M.downloads[name] = nil end + + kissmods.mount_mod(name) M.downloads_status[name] = nil M.downloads_received[name] = nil - M.connection.mods_left = M.connection.mods_left - 1 - end - - if M.connection.mods_left <= 0 then - M.downloading = false - kissui.show_download = false - on_finished_download() + + if #M.download_queue > 0 then + local next_mod = table.remove(M.download_queue, 1) + if next_mod then + send_data({ RequestMods = { next_mod } }, true) + end + else + M.downloading = false + kissui.show_download = false + M.downloaded_bytes = M.download_total_bytes + on_finished_download() + end end - M.connection.tcp:settimeout(0.0) - break + elseif string.byte(msg_type) == 2 then local len_b = M.connection.tcp:receive(4) + if not len_b then + M.connection.tcp:settimeout(0.0) + break + end + local len = bytesToU32(len_b) local reason, _, _ = M.connection.tcp:receive(len) + M.connection.tcp:settimeout(0.0) disconnect(reason) + break + + else + M.connection.tcp:settimeout(0.0) end end end From efa91c150db415df094e21d253d764a29f24e0a7 Mon Sep 17 00:00:00 2001 From: DaddelZeit <141176220+DaddelZeit@users.noreply.github.com> Date: Sun, 5 Apr 2026 02:54:30 +0200 Subject: [PATCH 3/5] Lua Code Modernization (#186) * Use jsonWriteFile/jsonReadFile instead of opening and reading/writing manually * Move to zero garbage alternatives for be: calls * Move to setExtensionUnloadMode * Use vec3 instead of deprecated Point3F * Update to zero garbage object handling (1) * Rewrite kissplayers.lua, Update to zero garbage object handling (2) * Update to zero garbage object handling (3) * Move to log() function away from print(), remove pointless prints * Fix unused locals, whitespaces, indentations * Update to zero garbage object handling (4) * Avoid re-instancing UI colors * Use global objectId instead of obj:getID() * Remove more unused locals * Remove incorrect vec3() constructor parameters * Fix missed conversion to squaredDistance (kiss_transforms) Co-authored-by: Vlad118 <96501158+Vlad118@users.noreply.github.com> * Fix send_vehicle_meta_updates iterating over all vehicles and not only player owned ones * Remove unused kiss_nodes, kissutils and associated code --------- Co-authored-by: Vlad118 <96501158+Vlad118@users.noreply.github.com> --- .../lua/ge/extensions/kissconfig.lua | 11 +- .../lua/ge/extensions/kissmods.lua | 13 +- .../lua/ge/extensions/kissmp/ui/chat.lua | 8 +- .../lua/ge/extensions/kissmp/ui/names.lua | 32 +-- .../kissmp/ui/tabs/create_server.lua | 18 +- .../extensions/kissmp/ui/tabs/favorites.lua | 12 +- .../lua/ge/extensions/kissplayers.lua | 156 ++++++++----- .../lua/ge/extensions/kisstransform.lua | 28 ++- KISSMultiplayer/lua/ge/extensions/kissui.lua | 7 - .../lua/ge/extensions/kissutils.lua | 28 --- .../lua/ge/extensions/kissvoicechat.lua | 24 +- KISSMultiplayer/lua/ge/extensions/network.lua | 65 ++---- .../lua/ge/extensions/vehiclemanager.lua | 211 +++++++++++------- .../extensions/kiss_mp/kiss_couplers.lua | 8 +- .../extensions/kiss_mp/kiss_electrics.lua | 25 +-- .../extensions/kiss_mp/kiss_gearbox.lua | 15 +- .../vehicle/extensions/kiss_mp/kiss_nodes.lua | 34 --- .../extensions/kiss_mp/kiss_transforms.lua | 133 +++++++---- .../extensions/kiss_mp/kiss_vehicle.lua | 57 ++--- KISSMultiplayer/scripts/kiss_mp/modScript.lua | 38 +--- 20 files changed, 473 insertions(+), 450 deletions(-) delete mode 100644 KISSMultiplayer/lua/ge/extensions/kissutils.lua delete mode 100644 KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_nodes.lua diff --git a/KISSMultiplayer/lua/ge/extensions/kissconfig.lua b/KISSMultiplayer/lua/ge/extensions/kissconfig.lua index ca65bab5..0cb91e64 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissconfig.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissconfig.lua @@ -26,21 +26,17 @@ local function save_config() view_distance = kissui.view_distance[0], base_secret_v2 = secret } - local file = io.open("./settings/kissmp_config.json", "w") - file:write(jsonEncode(result)) - io.close(file) + jsonWriteFile("/settings/kissmp_config.json", result, true) end local function load_config() - local file = io.open("./settings/kissmp_config.json", "r") - if not file then + if not FS:fileExists("/settings/kissmp_config.json") then if Steam and Steam.isWorking and Steam.accountLoggedIn then kissui.player_name = imgui.ArrayChar(32, Steam.playerName) end return end - local content = file:read("*a") - local config = jsonDecode(content or "") + local config = jsonReadFile("/settings/kissmp_config.json") if not config then return end if config.name ~= nil then @@ -67,7 +63,6 @@ local function load_config() if config.base_secret_v2 ~= nil then network.base_secret = config.base_secret_v2 end - io.close(file) end local function init() diff --git a/KISSMultiplayer/lua/ge/extensions/kissmods.lua b/KISSMultiplayer/lua/ge/extensions/kissmods.lua index 6acbbe03..5ff1b7b7 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmods.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmods.lua @@ -12,12 +12,6 @@ local function is_special_mod(mod_path) return false end -local function get_mod_name(name) - local name = string.lower(name) - name = name:gsub('.zip$', '') - return "kissmp_mods"..name -end - local function deactivate_mod(name) local filename = "/kissmp_mods/"..name if FS:isMounted(filename) then @@ -31,7 +25,7 @@ local function is_app_mod(path) if string.sub(path, -4) ~= ".zip" then pattern = "([^/]+)$" end - + path = string.match(path, pattern) local mod = core_modmanager.getModDB(path) if not mod then return false end @@ -62,7 +56,7 @@ local function mount_mod(name) if FS:fileExists("/kissmp_mods/"..name) then FS:mount("/kissmp_mods/"..name) else - files = FS:findFiles("/mods/", name, 1000) + local files = FS:findFiles("/mods/", name, 1000) if files[1] then FS:mount(files[1]) else @@ -89,7 +83,7 @@ local function update_status(mod) for _, v in pairs(search_results2) do table.insert(search_results, v) end - + if not search_results[1] then mod.status = "missing" else @@ -127,7 +121,6 @@ local function open_file(name) FS:directoryCreate("/kissmp_mods/") end local path = "/kissmp_mods/"..name - print(path) local file = io.open(path, "wb") return file end diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/chat.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/chat.lua index 3056f743..ec2ca56c 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/chat.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/chat.lua @@ -58,6 +58,7 @@ local function draw_player_list() imgui.EndGroup() end +local message_color = imgui.ImVec4(0,0,0,0) local function draw() if not kissui.gui.isWindowVisible("Chat") then return end imgui.PushStyleVar2(imgui.StyleVar_WindowMinSize, imgui.ImVec2(100, 100)) @@ -90,12 +91,13 @@ local function draw() for _, message in pairs(M.chat) do imgui.PushTextWrapPos(0) if message.user_name ~= nil then - local color = imgui.ImVec4(message.user_color[1], message.user_color[2], message.user_color[3], message.user_color[4]) - imgui.TextColored(color, "%s", (message.user_name:sub(1, 16))..":") + message_color.x, message_color.y, message_color.z, message_color.w = message.user_color[1], message.user_color[2], message.user_color[3], message.user_color[4] + imgui.TextColored(message_color, "%s", (message.user_name:sub(1, 16))..":") imgui.SameLine() end if message.has_color then - imgui.TextColored(imgui.ImVec4(message.color.r or 1, message.color.g or 1, message.color.b or 1, message.color.a or 1), "%s", message.text) + message_color.x, message_color.y, message_color.z, message_color.w = message.color.r or 1, message.color.g or 1, message.color.b or 1, message.color.a or 1 + imgui.TextColored(message_color, "%s", message.text) else imgui.Text("%s", message.text) end diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/names.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/names.lua index 63cdc401..92435970 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/names.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/names.lua @@ -1,31 +1,37 @@ local M = {} +local camera_pos = vec3() +local vehicle_position = vec3() + +local text_color = ColorF(1, 1, 1, 1) +local background_color = ColorI(0, 0, 0, 255) + local function draw() + camera_pos:set(core_camera.getPositionXYZ()) for id, player in pairs(network.players) do if id ~= network.connection.client_id and player.current_vehicle then local vehicle_id = vehiclemanager.id_map[player.current_vehicle] or -1 - local vehicle = be:getObjectByID(vehicle_id) - local vehicle_position = vec3() - if (not vehicle) or (kisstransform.inactive[vehicle_id]) then + local vehicle = getObjectByID(vehicle_id) + if not vehicle or kisstransform.inactive[vehicle_id] then if kissplayers.players[player.current_vehicle] then - vehicle_position = vec3(kissplayers.players[player.current_vehicle]:getPosition()) + vehicle_position:set(kissplayers.players[player.current_vehicle]:getPositionXYZ()) elseif kisstransform.raw_transforms[player.current_vehicle] then - vehicle_position = vec3(kisstransform.raw_transforms[player.current_vehicle].position) + local position = kisstransform.raw_transforms[player.current_vehicle].position + vehicle_position:set(position[1], position[2], position[3]) end else - vehicle_position = vec3(vehicle:getPosition()) + vehicle_position:set(vehicle:getPositionXYZ()) end - - local local_position = getCameraPosition() - local distance = vehicle_position:distance(vec3(local_position)) or 0 + + local distance = vehicle_position:distance(camera_pos) or 0 vehicle_position.z = vehicle_position.z + 1.6 debugDrawer:drawTextAdvanced( - Point3F(vehicle_position.x, vehicle_position.y, vehicle_position.z), - String(player.name.." ("..tostring(math.floor(distance)).."m)"), - ColorF(1, 1, 1, 1), + vehicle_position, + player.name.." ("..tostring(math.floor(distance)).."m)", + text_color, true, false, - ColorI(0, 0, 0, 255), + background_color, false, false ) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/create_server.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/create_server.lua index 1831f7e1..f4e58072 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/create_server.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/create_server.lua @@ -50,7 +50,7 @@ end local function find_map_real_path(map_path) local patterns = {"info.json", "*.mis"} local found_file = map_path - + for _,pattern in pairs(patterns) do local files = FS:findFiles(map_path, pattern, 1) if #files > 0 then @@ -58,7 +58,6 @@ local function find_map_real_path(map_path) break end end - print(found_file) return FS:virtual2Native(found_file) end @@ -74,19 +73,16 @@ local function change_map(map_info, title) -- local map_path = map_info.misFilePath - print(map_path) M.map = map_path M.map_name = title or map_info.levelName local native = find_map_real_path(map_path) - print(native) local _, zip_end = string.find(native, ".zip") local _, is_mod = string.find(native, "mods") if zip_end and is_mod then local mod_file = string.sub(native, 1, zip_end) - print(mod_file) local virtual = to_non_lowered(FS:native2Virtual(mod_file)) - + pre_forced_mods_state[virtual] = (M.mods[virtual] ~= nil) M.mods[virtual] = FS:virtual2Native(virtual) forced_mods[virtual] = true @@ -95,23 +91,23 @@ end local function checkbox(id, checked, allow_click) if allow_click == nil then allow_click = allow_click or true end - + if not allow_click then imgui.PushStyleVar1(imgui.StyleVar_Alpha, 0.70) end local return_value = imgui.Checkbox(id, checked) if not allow_click then imgui.PopStyleVar() end - + if allow_click then return return_value else return false end end local function draw() imgui.Text("Server name:") imgui.InputText("##host_server_name", M.server_name) - + imgui.Text("Max players:") if imgui.InputInt("###host_max_players", M.max_players) then M.max_players[0] = math.max(1, math.min(255, M.max_players[0])) end - + imgui.Text("Map:") if imgui.BeginCombo("###host_map", M.map_name) then for k, v in pairs(core_levels.getList()) do @@ -138,7 +134,7 @@ local function draw() if not kissmods.is_special_mod(v) then local forced = forced_mods[v] or false local checked = imgui.BoolPtr(M.mods[v] ~= nil or forced) - + if checkbox(v.."###host_mod"..k, checked, not forced) then if checked[0] and not M.mods[v] then M.mods[v] = FS:virtual2Native(v) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/favorites.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/favorites.lua index 385078bc..c3349032 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/favorites.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/favorites.lua @@ -26,18 +26,14 @@ local function spairs(t, order) end local function save_favorites() - local file = io.open("./settings/kissmp_favorites.json", "w") - file:write(jsonEncode(M.favorite_servers)) - io.close(file) + jsonWriteFile("/settings/kissmp_favorites.json", M.favorite_servers, true) end local function load_favorites(m) local kissui = kissui or m - local file = io.open("./settings/kissmp_favorites.json", "r") - if file then - local content = file:read("*a") - M.favorite_servers = jsonDecode(content) or {} - io.close(file) + local jsonData = jsonReadFile("/settings/kissmp_favorites.json") + if jsonData then + M.favorite_servers = jsonData end end diff --git a/KISSMultiplayer/lua/ge/extensions/kissplayers.lua b/KISSMultiplayer/lua/ge/extensions/kissplayers.lua index b95f66eb..cb6fb10f 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissplayers.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissplayers.lua @@ -69,16 +69,20 @@ local function spawn_player(data) local player = createObject('TSStatic') player:setField("shapeName", 0, "/art/shapes/kissmp_playermodels/base_nb.dae") player:setField("dynamic", 0, "true") - player.scale = Point3F(1, 1, 1) + player.scale = vec3(1, 1, 1) player:registerObject("player"..data.owner) + + local r, g, b, a = get_player_color(data.owner) + player:setField('instanceColor', 0, string.format("%g %g %g %g", r, g, b, a)) player:setPosRot( data.position[1], data.position[2], data.position[3], data.rotation[1], data.rotation[2], data.rotation[3], data.rotation[4] ) - local r, g, b, a = get_player_color(data.owner) - player:setField('instanceColor', 0, string.format("%g %g %g %g", r, g, b, a)) - vehiclemanager.id_map[data.server_id] = player:getID() - vehiclemanager.server_ids[player:getID()] = data.server_id + + local player_mesh_id = player:getID() + vehiclemanager.id_map[data.server_id] = player_mesh_id + vehiclemanager.server_ids[player_mesh_id] = data.server_id + M.players[data.server_id] = player M.player_transforms[data.server_id] = { position = vec3(data.position), @@ -89,74 +93,112 @@ local function spawn_player(data) } end -local function update_players(dt) +local original_position = vec3() +local temp_vec = vec3() +local final_pos = vec3() +local function update_unicycle_replacements(dt) for id, data in pairs(M.player_transforms) do local player = M.players[id] if player and data then data.time_past = data.time_past + dt - local old_position = data.position - data.position = lerp(data.position, data.target_position + data.velocity * data.time_past, clamp(dt * M.lerp_factor, 0, 1)) - local local_velocity = data.position - old_position - local p = data.position + local_velocity * dt - --player.position = m + original_position:set(data.position) + + temp_vec:set(data.velocity) + temp_vec:setScaled(data.time_past) + temp_vec:setAdd(data.target_position) + + data.position:setLerp(original_position, temp_vec, clamp(dt * M.lerp_factor, 0, 1)) + + temp_vec:setSub2(data.position, original_position) -- local_velocity + + -- local_velocity * dt + data.position + final_pos:set(temp_vec) + final_pos:setScaled(dt) + final_pos:setAdd(original_position) + + local x, y, z = final_pos:xyz() player:setPosRot( - p.x, p.y, p.z, + x, y, z, data.rotation[1], data.rotation[2], data.rotation[3], data.rotation[4] ) end end - for id, player_data in pairs(network.players) do - local vehicle = be:getObjectByID(vehiclemanager.id_map[player_data.current_vehicle or -1] or -1) - if vehicle and (not blacklist[vehicle:getJBeamFilename()]) then - local cam_node, _ = core_camera.getDriverData(vehicle) - if cam_node and kisstransform.local_transforms[vehicle:getID()] then - local p = vec3(vehicle:getNodePosition(cam_node)) + vec3(vehicle:getPosition()) - local r = kisstransform.local_transforms[vehicle:getID()].rotation - local hide = be:getPlayerVehicle(0) and (be:getPlayerVehicle(0):getID() == vehicle:getID()) and (vec3(getCameraPosition()):distance(p) < 2.5) - hide = hide or (not kissui.show_drivers[0]) or kisstransform.inactive[vehicle:getID()] - if (not M.players_in_cars[id]) and (not hide) then - local player = createObject('TSStatic') - player:setField("shapeName", 0, "/art/shapes/kissmp_playermodels/base_nb_head.dae") - player:setField("dynamic", 0, "true") - player.scale = Point3F(1, 1, 1) - local r, g, b, a = get_player_color(id) - player:setField('instanceColor', 0, string.format("%g %g %g %g", r, g, b, a)) - player:registerObject("player_head"..id) - M.players_in_cars[id] = player - M.player_heads_attachments[id] = vehicle:getID() - end - if hide and M.players_in_cars[id] then - M.players_in_cars[id]:delete() - M.players_in_cars[id] = nil - M.player_heads_attachments[id] = nil - end - p = p + vec3(vehicle:getVelocity()) * dt - local player = M.players_in_cars[id] - if player then - player:setPosRot( - p.x, p.y, p.z, - r[1], r[2], r[3], r[4] - ) - end - end - else - if M.players_in_cars[id] then - M.players_in_cars[id]:delete() - M.players_in_cars[id] = nil - M.player_heads_attachments[id] = nil - end +end + +local function spawn_player_head(id, veh_id) + local player = createObject('TSStatic') + player:setField("shapeName", 0, "/art/shapes/kissmp_playermodels/base_nb_head.dae") + player:setField("dynamic", 0, "true") + player.scale = vec3(1, 1, 1) + local r, g, b, a = get_player_color(id) + player:setField('instanceColor', 0, string.format("%g %g %g %g", r, g, b, a)) + player:registerObject("player_head"..id) + + M.players_in_cars[id] = player + M.player_heads_attachments[id] = veh_id +end + +local function delete_player_head(id) + M.players_in_cars[id]:delete() + M.players_in_cars[id] = nil + M.player_heads_attachments[id] = nil +end + +local camera_pos = vec3() +local distance_threshold = 2.5 * 2.5 +local driver_cam_pos = vec3() +local vehicle_vel = vec3() +local function update_player_head(dt, player_id, vehicle) + local cam_node, _ = core_camera.getDriverData(vehicle) + local veh_id = vehicle:getID() + local transform = kisstransform.local_transforms[veh_id] + + if cam_node and transform then + driver_cam_pos:set(vehicle:getNodeAbsPositionXYZ(cam_node)) + local r = transform.rotation + + local hide = not kissui.show_drivers[0] or kisstransform.inactive[veh_id] + hide = hide or vehicle == getPlayerVehicle(0) and camera_pos:squaredDistance(driver_cam_pos) < distance_threshold + if not hide and not M.players_in_cars[player_id] then + spawn_player_head(player_id, veh_id) + end + if hide and M.players_in_cars[player_id] then + delete_player_head(player_id) + end + + vehicle_vel:set(vehicle:getVelocityXYZ()) + vehicle_vel:setScaled(dt) + driver_cam_pos:setAdd(vehicle_vel) + local player = M.players_in_cars[player_id] + if player then + local x, y, z = driver_cam_pos:xyz() + player:setPosRot( + x, y, z, + r[1], r[2], r[3], r[4] + ) end end - for id, v in pairs(M.players_in_cars) do - if not be:getObjectByID(M.player_heads_attachments[id] or -1) then - v:delete() - M.players_in_cars[id] = nil - M.player_heads_attachments[id] = nil +end + +local function update_players(_, dt_sim) + update_unicycle_replacements(dt_sim) + + camera_pos:set(core_camera.getPositionXYZ()) + for player_id, player_data in pairs(network.players) do + local vehicleId = vehiclemanager.id_map[player_data.current_vehicle or -1] or -1 + local vehicle = getObjectByID(vehicleId) + + if vehicle and not blacklist[vehicle:getJBeamFilename()] then + update_player_head(dt_sim, player_id, vehicle) + elseif M.players_in_cars[player_id] then + delete_player_head(player_id) end end end M.spawn_player = spawn_player +M.delete_player_head = delete_player_head + M.get_player_color = get_player_color M.onUpdate = update_players diff --git a/KISSMultiplayer/lua/ge/extensions/kisstransform.lua b/KISSMultiplayer/lua/ge/extensions/kisstransform.lua index acd7cda4..d904192e 100644 --- a/KISSMultiplayer/lua/ge/extensions/kisstransform.lua +++ b/KISSMultiplayer/lua/ge/extensions/kisstransform.lua @@ -2,9 +2,6 @@ local M = {} local string_buffer = require("string.buffer") -local generation = 0 -local timer = 0 - M.raw_transforms = {} M.received_transforms = {} M.local_transforms = {} @@ -17,25 +14,28 @@ M.velocity_error_limit = 10 M.hidden = {} +local transform_pos = vec3() +local camera_pos = vec3() local function update(dt) if not network.connection.connected then return end - -- Get rotation/angular velocity from vehicle lua - for i = 0, be:getObjectCount() do - local vehicle = be:getObject(i) - if vehicle and (not M.inactive[vehicle:getID()]) then - vehicle:queueLuaCommand("kiss_vehicle.update_transform_info()") + -- Get rotation/angular velocity from vehicle lua + for vid, v in vehiclesIterator() do + if not M.inactive[vid] then + v:queueLuaCommand("kiss_vehicle.update_transform_info()") end end -- Don't apply velocity while paused. If we do, velocity gets stored up and released when the game resumes. local apply_velocity = not bullettime.getPause() + camera_pos:set(core_camera.getPositionXYZ()) + local view_distance = kissui.enable_view_distance[0] and kissui.view_distance[0] * kissui.view_distance[0] or nil for id, transform in pairs(M.received_transforms) do --apply_transform(dt, id, transform, apply_velocity) - local vehicle = be:getObjectByID(id) - local p = vec3(transform.position) + local vehicle = getObjectByID(id) + transform_pos:set(transform.position[1], transform.position[2], transform.position[3]) if vehicle and apply_velocity and (not vehiclemanager.ownership[id]) then - if ((p:distance(vec3(getCameraPosition())) > kissui.view_distance[0])) and kissui.enable_view_distance[0] then - if (not M.inactive[id]) then + if view_distance and (transform_pos:squaredDistance(camera_pos) > view_distance) then + if not M.inactive[id] then vehicle:setActive(0) M.inactive[id] = true end @@ -62,7 +62,7 @@ local function update_vehicle_transform(data) M.raw_positions[transform.owner or -1] = transform.position M.received_transforms[id] = transform - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) transform.time_past = clamp(vehiclemanager.get_current_time() - transform.sent_at, 0, 0.1) * 0.9 + 0.001 if vehicle and (not M.inactive[id]) then vehicle:queueLuaCommand(string.format( @@ -75,8 +75,6 @@ local function push_transform(id, t) M.local_transforms[id] = string_buffer.decode(t) end -M.send_transform_updates = send_transform_updates -M.send_vehicle_transform = send_vehicle_transform M.update_vehicle_transform = update_vehicle_transform M.push_transform = push_transform M.onUpdate = update diff --git a/KISSMultiplayer/lua/ge/extensions/kissui.lua b/KISSMultiplayer/lua/ge/extensions/kissui.lua index e46d140d..5d9a0606 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissui.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissui.lua @@ -1,7 +1,4 @@ local M = {} -local http = require("socket.http") - -local bor = bit.bor local main_window = require("kissmp.ui.main") M.chat = require("kissmp.ui.chat") @@ -80,10 +77,6 @@ local function open_ui() show_ui() end -local function bytes_to_mb(bytes) - return (bytes / 1024) / 1024 -end - local function draw_incorrect_install() if imgui.Begin("Incorrect install detected") then imgui.Text("Incorrect KissMP install. Please, check if mod path is correct") diff --git a/KISSMultiplayer/lua/ge/extensions/kissutils.lua b/KISSMultiplayer/lua/ge/extensions/kissutils.lua deleted file mode 100644 index ad2ba983..00000000 --- a/KISSMultiplayer/lua/ge/extensions/kissutils.lua +++ /dev/null @@ -1,28 +0,0 @@ -local M = {} - -M.hooks = { - internal = {} -} - -M.hooks.clear = function() - M.hooks.internal = {} -end - -M.hooks.register = function(hook_name, subname, fn) - if not M.hooks.internal[hook_name] then M.hooks.internal[hook_name] = {} end - M.hooks.internal[hook_name][sub_name] = fn -end - -M.hooks.call = function(hook_name, ...) - for k, v in pairs(M.hooks.internal[hook_name]) do - v(arg) - end -end - -local function onUpdate(dt) - M.hooks.call("onUpdate", dt) -end - ---M.onUpdate = onUpdate - -return M diff --git a/KISSMultiplayer/lua/ge/extensions/kissvoicechat.lua b/KISSMultiplayer/lua/ge/extensions/kissvoicechat.lua index c43f22f6..44a69082 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissvoicechat.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissvoicechat.lua @@ -2,15 +2,25 @@ local M = {} M.el = vec3(0.08, 0, 0) M.er = vec3(-0.08, 0, 0) +local position = vec3() +local pl, pr = vec3(), vec3() +local cam_rot = quat() + local function onUpdate() if not network.connection.connected then return end - local position = vec3(getCameraPosition() or vec3()) - local ear_left = M.el:rotated(quat(getCameraQuat())) - local ear_right = M.er:rotated(quat(getCameraQuat())) - local pl = position + ear_left - local pr = position + ear_right - --debugDrawer:drawSphere((pl + vec3(0, 2, 0):rotated(quat(getCameraQuat()))):toPoint3F(), 0.05, ColorF(0,1,0,0.8)) - --debugDrawer:drawSphere((pr + vec3(0, 2, 0):rotated(quat(getCameraQuat()))):toPoint3F(), 0.05, ColorF(0,0,1,0.8)) + position:set(core_camera.getPositionXYZ()) + cam_rot:set(core_camera.getQuatXYZW()) + + pl:set(M.el) + pl:setRotate(cam_rot) + pl:setAdd(position) + + pr:set(M.er) + pr:setRotate(cam_rot) + pr:setAdd(position) + + --debugDrawer:drawSphere((pl + vec3(0, 2, 0):rotated(quat(getCameraQuat()))), 0.05, ColorF(0,1,0,0.8)) + --debugDrawer:drawSphere((pr + vec3(0, 2, 0):rotated(quat(getCameraQuat()))), 0.05, ColorF(0,0,1,0.8)) network.send_data({ SpatialUpdate = {{pl.x, pl.y, pl.z}, {pr.x, pr.y, pr.z}} }) diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index 052e12ca..027089d6 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -114,7 +114,6 @@ local function disconnect(data) --vehiclemanager.delay_spawns = false --kissui.force_disable_nametags = false --Lua:requestReload() - --kissutils.hooks.clear() if getMissionFilename() ~= "" then returnToMainMenu() end @@ -140,10 +139,10 @@ local function check_lua(l) end local function handle_lua(data) - if M.is_server_public then - print("Blocked arbitrary Lua command from public server.") - return - end + if M.is_server_public then + log("W", "kissmp.network.handle_lua", "Blocked arbitrary GE Lua command from public server.") + return + end if check_lua(data) then Lua:queueLuaCommand(data) @@ -151,15 +150,15 @@ local function handle_lua(data) end local function handle_vehicle_lua(data) - if M.is_server_public then - print("Blocked arbitrary vehicle Lua command from public server.") - return + if M.is_server_public then + log("W", "kissmp.network.handle_vehicle_lua", "Blocked arbitrary vehicle Lua command from public server.") + return end local id = data[1] local lua = data[2] local id = vehiclemanager.id_map[id or -1] or 0 - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if vehicle and check_lua(lua) then vehicle:queueLuaCommand(lua) end @@ -178,6 +177,9 @@ end local function handle_player_disconnected(data) local id = data M.players[id] = nil + if kissplayers.players_in_cars[id] then + kissplayers.delete_player_head(id) + end end local function handle_chat(data) @@ -207,7 +209,7 @@ end local function send_data(raw_data, reliable) if type(raw_data) == "number" then - print("NOT IMPLEMENTED. PLEASE REPORT TO KISSMP DEVELOPERS. CODE: "..raw_data) + log("E", "kissmp.network.send_data", "Sending raw data is not implemented. Please report to KissMP developers. Code: "..raw_data) return end if not M.connection.connected then return -1 end @@ -220,14 +222,13 @@ local function send_data(raw_data, reliable) local data_size = #data -- Auto-chunk if data is too large if data_size > CHUNK_SIZE then - print("Large data detected: " .. data_size .. " bytes, sending in chunks") local num_chunks = math.ceil(data_size / CHUNK_SIZE) - + for i = 0, num_chunks - 1 do local start_pos = i * CHUNK_SIZE + 1 local end_pos = math.min((i + 1) * CHUNK_SIZE, data_size) local chunk = data:sub(start_pos, end_pos) - + local chunk_data = jsonEncode({ DataChunk = { chunk_index = i, @@ -239,21 +240,14 @@ local function send_data(raw_data, reliable) local len = ffi.string(ffi.new("uint32_t[?]", 1, {#chunk_data}), 4) M.connection.tcp:send(string.char(1)..len) M.connection.tcp:send(chunk_data) - - print("Sent chunk " .. (i + 1) .. "/" .. num_chunks) end - - print("All chunks sent successfully") + return 0 end - + -- Send normally local len = ffi.string(ffi.new("uint32_t[?]", 1, {data_size}), 4) - if reliable then - reliable = 1 - else - reliable = 0 - end + reliable = reliable and 1 or 0 M.connection.tcp:send(string.char(reliable)..len) M.connection.tcp:send(data) return 0 @@ -297,8 +291,8 @@ local function connect(addr, player_name, is_public) M.download_total_bytes = 0 M.downloaded_bytes = 0 - print("Connecting...") addr = sanitize_addr(addr) + log("I", "kissmp.network.connect", "Connecting to "..addr.."...") kissui.chat.add_message("Connecting to "..addr.."...") M.connection.tcp = socket.tcp() M.connection.tcp:settimeout(3.0) @@ -320,21 +314,20 @@ local function connect(addr, player_name, is_public) return end - -- Ignore message type + -- Ignore message type M.connection.tcp:receive(1) local len, _, _ = M.connection.tcp:receive(4) len = bytesToU32(len) local received, _, _ = M.connection.tcp:receive(len) - print(received) local server_info = jsonDecode(received).ServerInfo if not server_info then - print("Failed to fetch server info") + log("E", "kissmp.network.connect", "Failed to fetch server info. Aborting.") return end - print("Server name: "..server_info.name) - print("Player count: "..server_info.player_count) + log("I", "kissmp.network.connect", "Server name: "..server_info.name) + log("I", "kissmp.network.connect", "Player count: "..server_info.player_count) M.connection.tcp:settimeout(0.0) M.connection.connected = true @@ -383,7 +376,7 @@ local function connect(addr, player_name, is_public) kissmods.mount_mods(available_mods) end for k, v in pairs(missing_mods) do - print(k.." "..v) + log("I", "kissmp.network.connect", "Missing Mod "..k..": "..v) end if #missing_mods > 0 then -- Do not allow public servers to force mod downloads @@ -408,15 +401,6 @@ local function connect(addr, player_name, is_public) kissui.chat.add_message("Connected!") end -local function send_messagepack(data_type, reliable, data) - local data = data - if type(data) == "string" then - data = jsonDecode(data) - end - data = messagepack.pack(data) - send_data(data_type, reliable, data) -end - local function on_finished_download() M.download_start_time = 0 vehiclemanager.loading_map = true @@ -533,7 +517,7 @@ local function onUpdate(dt) file = kissmods.open_file(name) M.downloads[name] = file end - + if file and file_data then file:write(file_data) else @@ -594,7 +578,6 @@ M.disconnect = disconnect M.cancel_download = cancel_download M.send_data = send_data M.onUpdate = onUpdate -M.send_messagepack = send_messagepack M.onExtensionLoaded = onExtensionLoaded return M diff --git a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua index dd87a14e..e8780367 100644 --- a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua +++ b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua @@ -89,12 +89,11 @@ local function send_vehicle_update(obj) end local function send_vehicle_meta_updates() - for i = 0, be:getObjectCount() do - local vehicle = be:getObject(i) - if vehicle then + for id in pairs(vehiclemanager.ownership) do + local vehicle = getObjectByID(id) + if vehicle and not kisstransform.inactive[id] then local changed = false - local id = vehicle:getID() - + local metal_data = vehicle:getMetallicPaintData() local color = vehicle.color local palete_0 = vehicle.colorPalette0 @@ -105,17 +104,17 @@ local function send_vehicle_meta_updates() color_to_table(palete_0, metal_data[2]), color_to_table(palete_1, metal_data[3]) } - + if plates_buffer[id] then changed = changed or plates_buffer[id] ~= plate end plates_buffer[id] = plate - + if colors_buffer[id] then changed = changed or not colors_eq(colors, colors_buffer[id]) end colors_buffer[id] = colors - + if changed then local data = { VehicleMetaUpdate = { @@ -131,19 +130,19 @@ local function send_vehicle_meta_updates() end local function update_ownership_limits() - local owned_vehicle_count = 0 - for _, _ in pairs(M.ownership) do - owned_vehicle_count = owned_vehicle_count + 1 - end - if owned_vehicle_count >= network.connection.server_info.max_vehicles_per_client then - enable_spawning(false) - else - enable_spawning(true) - end + local owned_vehicle_count = 0 + for _, _ in pairs(M.ownership) do + owned_vehicle_count = owned_vehicle_count + 1 + end + if owned_vehicle_count >= network.connection.server_info.max_vehicles_per_client then + enable_spawning(false) + else + enable_spawning(true) + end end local function send_vehicle_config(vehicle_id) - local vehicle = be:getObjectByID(vehicle_id) + local vehicle = getObjectByID(vehicle_id) if vehicle then vehicle:queueLuaCommand("kiss_vehicle.send_vehicle_config()") end @@ -154,7 +153,8 @@ local function send_vehicle_config_inner(id, parts_config_json, buffer_data) for k, v in pairs(M.id_map) do if v == id and not M.ownership[id] then return end end - local vehicle = be:getObjectByID(id) + + local vehicle = getObjectByID(id) local metal_data = vehicle:getMetallicPaintData() local color = vehicle.color local palete_0 = vehicle.colorPalette0 @@ -182,39 +182,49 @@ local function send_vehicle_config_inner(id, parts_config_json, buffer_data) ) end +local camera_pos = vec3() +local transform_pos = vec3() + local function spawn_vehicle(data) local model_info = core_vehicles.getModel(data.name) if tableSize(model_info) == 0 then - print("Rejected modded vehicle spawn " .. data.name) + log("W", "kissmp.vehiclemanager.spawn_vehicle", "Rejected modded vehicle spawn "..data.name) return end local away = true - if kisstransform.raw_transforms[data.server_id] then - away = (vec3(kisstransform.raw_transforms[data.server_id].position):distance(vec3(getCameraPosition())) > kissui.view_distance[0]) - else - away = (vec3(data.position):distance(vec3(getCameraPosition())) > kissui.view_distance[0]) + local view_distance = kissui.enable_view_distance[0] and kissui.view_distance[0] * kissui.view_distance[0] or nil + if view_distance then + if kisstransform.raw_transforms[data.server_id] then + local position = kisstransform.raw_transforms[data.server_id].position + transform_pos:set(position[1], position[2], position[3]) + away = transform_pos:squaredDistance(camera_pos) > view_distance + else + local position = data.position + transform_pos:set(position[1], position[2], position[3]) + away = transform_pos:squaredDistance(camera_pos) > view_distance + end end + if M.loading_map or M.delay_spawns then - print("Buffering vehicle") + log("D", "kissmp.vehiclemanager.spawn_vehicle", "Buffering vehicle") M.vehicle_buffer[data.server_id] = data return - elseif away and kissui.enable_view_distance[0] then - print("Buffering vehicle") + elseif away and view_distance then + log("D", "kissmp.vehiclemanager.spawn_vehicle", "Buffering vehicle") M.vehicle_buffer[data.server_id] = data return end if data.owner == network.get_client_id() then - print("Vehicle belongs to local client, setting ownership") + log("I", "kissmp.vehiclemanager.spawn_vehicle", "Vehicle belongs to local client, setting ownership") M.id_map[data.server_id] = data.in_game_id M.ownership[data.in_game_id] = data.server_id M.server_ids[data.in_game_id] = data.server_id update_ownership_limits() - be:getObjectByID(data.in_game_id):queueLuaCommand("extensions.hook('kissUpdateOwnership', true)") + getObjectByID(data.in_game_id):queueLuaCommand("extensions.hook('kissUpdateOwnership', true)") return end if M.id_map[data.server_id] then return end - local current_vehicle = be:getPlayerVehicle(0) local parts_config = jsonDecode(data.parts_config) local c = data.color local plate = data.plate @@ -222,13 +232,12 @@ local function spawn_vehicle(data) local cp1 = data.palete_1 local name = data.name if name == "unicycle" then - print("Attempt to spawn player") kissplayers.spawn_player(data) return end - - print("Attempt to spawn vehicle "..name) - local options = { + + log("D", "kissmp.vehiclemanager.spawn_vehicle", "Attempt to spawn vehicle "..name) + local options = { vehicleName = "mp_veh", pos = vec3(data.position), rot = quat(data.rotation), @@ -239,7 +248,7 @@ local function spawn_vehicle(data) autoEnterVehicle = false } options = sanitizeVehicleSpawnOptions(name, options) - + local spawned = spawn.spawnVehicle(name, options.config, options.pos, options.rot, options) if not spawned then return end local p = data.position @@ -257,6 +266,7 @@ end local function onUpdate(dt) if not network.connection.connected then return end + camera_pos:set(core_camera.getPositionXYZ()) if (getMissionFilename():lower() ~= network.connection.server_info.map:lower()) and (getMissionPath():lower() ~= network.connection.server_info.map:lower()) and not M.loading_map then network.disconnect() end @@ -273,7 +283,7 @@ local function onUpdate(dt) else timer = timer - tick_time for i, v in pairs(vehiclemanager.ownership) do - local vehicle = be:getObjectByID(i) + local vehicle = getObjectByID(i) if vehicle and (not kisstransform.inactive[i]) then send_vehicle_update(vehicle) vehicle:queueLuaCommand("kiss_electrics.send()") @@ -283,17 +293,19 @@ local function onUpdate(dt) for k, v in pairs(M.id_map) do if not M.ownership[v] then - local vehicle = be:getObjectByID(v) + local vehicle = getObjectByID(v) if vehicle and (not kisstransform.inactive[v]) then vehicle:queueLuaCommand("kiss_vehicle.update_eligible_nodes()") end end end if not (M.loading_map or M.delay_spawns) then + local view_distance = kissui.enable_view_distance[0] and kissui.view_distance[0] * kissui.view_distance[0] or nil local to_remove = {} for k, vehicle in pairs(M.vehicle_buffer) do local t = kisstransform.raw_transforms[k] - if t and not ((vec3(t.position):distance(vec3(getCameraPosition())) > kissui.view_distance[0]) and kissui.enable_view_distance[0]) then + transform_pos:set(t.position[1], t.position[2], t.position[3]) + if t and not (view_distance and transform_pos:squaredDistance(camera_pos) > view_distance) then spawn_vehicle(vehicle) table.insert(to_remove, k) end @@ -306,22 +318,34 @@ end local function update_vehicle(data) kisstransform.raw_transforms[data.vehicle_id] = data.transform - -- If vehicle is a unicycle(Walking mode character), sync it differently + -- If vehicle is a unicycle(Walking mode character), sync it differently local character = kissplayers.players[data.vehicle_id] if character then - kissplayers.player_transforms[data.vehicle_id].target_position = vec3(data.transform.position) - kissplayers.player_transforms[data.vehicle_id].rotation = data.transform.rotation - kissplayers.player_transforms[data.vehicle_id].velocity = vec3(data.transform.velocity) - kissplayers.player_transforms[data.vehicle_id].time_past = clamp(get_current_time() - data.sent_at, 0, 0.3) + 0.0001 + local character_transforms = kissplayers.player_transforms[data.vehicle_id] + if not character_transforms then + character_transforms = { + target_position = vec3(), + rotation = {}, + velocity = vec3() + } + kissplayers.player_transforms[data.vehicle_id] = character_transforms + end + + local temp = data.transform.position + character_transforms.target_position:set(temp[1], temp[2], temp[3]) + character_transforms.rotation = data.transform.rotation + temp = data.transform.velocity + character_transforms.velocity:set(temp[1], temp[2], temp[3]) + character_transforms.time_past = clamp(get_current_time() - data.sent_at, 0, 0.3) + 0.0001 return end - + local id = M.id_map[data.vehicle_id] if not id then return end if M.ownership[id] then return end if data.generation <= (M.packet_gen_buffer[id] or -1) then return end M.packet_gen_buffer[id] = data.generation - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if not vehicle then return end kisstransform.update_vehicle_transform(data) @@ -343,7 +367,7 @@ local function remove_vehicle(data) return end local local_id = M.id_map[id] or -1 - local vehicle = be:getObjectByID(local_id) + local vehicle = getObjectByID(local_id) if vehicle then vehicle:setActive(1) vehicle:delete() @@ -360,11 +384,11 @@ end local function reset_vehicle(data) local id = data.vehicle_id id = M.id_map[id] or -1 - + local position = data.position local rotation = data.rotation - - local vehicle = be:getObjectByID(id) + + local vehicle = getObjectByID(id) if not vehicle then return end if vehicle then vehicle:reset() @@ -383,10 +407,10 @@ end local function update_vehicle_meta(data) local id = M.id_map[data.vehicle_id or -1] or -1 if M.ownership[id] then return end - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if not vehicle then return end local plate = data.plate - + local color = data.colors_table[1] local palete_0 = data.colors_table[2] local palete_1 = data.colors_table[3] @@ -403,7 +427,7 @@ local function update_vehicle_meta(data) -- Apply colors local vd = extensions.core_vehicle_manager.getVehicleData(id) if not vd or not vd.config or not vd.config.paints then return end - + for i=1,3 do local ct = color_tables[i] vd.config.paints[i] = table_to_paint(ct) @@ -415,7 +439,7 @@ end local function electrics_diff_update(data) local id = M.id_map[data[1] or -1] if id and not M.ownership[id] then - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if not vehicle then return end local data = data[2].diff vehicle:queueLuaCommand(string.format( @@ -448,20 +472,36 @@ local function detach_coupler_inner(buffer_data) ) end +local tempVec1 = vec3() +local tempVec2 = vec3() +local nodeAPos = vec3() +local nodeBPos = vec3() +local distanceThreshold = 15 * 15 local function attach_coupler(data) local obj_a = M.id_map[data.obj_a] local obj_b = M.id_map[data.obj_b] if obj_a and obj_b then if M.ownership[obj_a] then return end - local vehicle = be:getObjectByID(obj_a) - local vehicle_b = be:getObjectByID(obj_b) - if not vehicle then return end - if not vehicle_b then return end - if vec3(vehicle:getPosition()):distance(vec3(vehicle_b:getPosition())) > 15 then return end - local node_a_pos = vec3(vehicle:getPosition()) + vec3(vehicle:getNodePosition(data.node_a_id)) - local node_b_pos = vec3(vehicle_b:getPosition()) + vec3(vehicle_b:getNodePosition(data.node_b_id)) - local pos = vec3(vehicle_b:getPosition()) + (node_a_pos - node_b_pos) - vehicle_b:setPositionNoPhysicsReset(Point3F(pos.x, pos.y, pos.z)) + local vehicle = getObjectByID(obj_a) + local vehicle_b = getObjectByID(obj_b) + if not vehicle or not vehicle_b then return end + + tempVec1:set(vehicle:getPositionXYZ()) + tempVec2:set(vehicle_b:getPositionXYZ()) + if tempVec1:squaredDistance(tempVec2) > distanceThreshold then return end + + --[[ + local node_a_pos = vehicle:getNodeAbsPosition(data.node_a_id) + local node_b_pos = vehicle_b:getNodeAbsPosition(data.node_b_id) + local pos = vehicle_b:getPosition() + (node_a_pos - node_b_pos) + ]] + + nodeAPos:set(vehicle:getNodeAbsPositionXYZ(data.node_a_id)) + nodeBPos:set(vehicle_b:getNodeAbsPositionXYZ(data.node_b_id)) + tempVec2:setAdd(nodeAPos) + tempVec2:setSub(nodeBPos) + + vehicle_b:setPositionNoPhysicsReset(tempVec2) vehicle_b:queueLuaCommand("kiss_couplers.attach_coupler("..data.node_b_id..")") onCouplerAttached(obj_a, obj_b, data.node_a_id, data.node_b_id) end @@ -472,11 +512,14 @@ local function detach_coupler(data) local obj_b = M.id_map[data.obj_b] if obj_a and obj_b then if M.ownership[obj_a] then return end - local vehicle = be:getObjectByID(obj_a) - local vehicle_b = be:getObjectByID(obj_b) - if not vehicle then return end - if not vehicle_b then return end - if vehicle_ ~= vehicle_b and vec3(vehicle:getPosition()):distance(vec3(vehicle_b:getPosition())) > 15 then return end + local vehicle = getObjectByID(obj_a) + local vehicle_b = getObjectByID(obj_b) + if not vehicle or not vehicle_b then return end + + tempVec1:set(vehicle:getPositionXYZ()) + tempVec2:set(vehicle_b:getPositionXYZ()) + if tempVec1:squaredDistance(tempVec2) > distanceThreshold then return end + vehicle:queueLuaCommand("kiss_couplers.detach_coupler("..data.node_a_id..")") onCouplerDetached(obj_a, obj_b, data.node_a_id, data.node_b_id) onCouplerDetach(obj_a, data.node_a_id) @@ -486,15 +529,16 @@ end local function set_position(data) local id = M.id_map[data[1] or -1] or -1 - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if vehicle then - vehicle:setPositionNoPhysicsReset(Point3F(data[2][1], data[2][2], data[2][3])) + tempVec1:set(data[2][1], data[2][2], data[2][3]) + vehicle:setPositionNoPhysicsReset(tempVec1) end end local function set_position_rotation(data) local id = M.id_map[data[1] or -1] or -1 - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if vehicle then vehicle:setPosRot(data[2][1], data[2][2], data[2][3], data[3][1], data[3][2], data[3][3], data[3][4]) end @@ -502,7 +546,7 @@ end local function reset_in_place(data) local id = M.id_map[data or -1] or -1 - local vehicle = be:getObjectByID(id) + local vehicle = getObjectByID(id) if vehicle then vehicle:reset() end @@ -510,10 +554,11 @@ end local function onVehicleSpawned(id) if not network.connection.connected then return end - local vehicle = be:getObjectByID(id) - local position = vehicle:getPosition() + local vehicle = getObjectByID(id) + tempVec1:set(vehicle:getPositionXYZ()) if first_vehicle then - vehicle:setPosition(Point3F(position.x + math.random(-5, 5), position.y + math.random(-5, 5), position.z)) + tempVec2:set(tempVec1.x + math.random(-5, 5), tempVec1.y + math.random(-5, 5), tempVec1.z) + vehicle:setPosition(tempVec2) vehicle:queueLuaCommand("recovery.saveHome()") first_vehicle = false end @@ -522,9 +567,8 @@ local function onVehicleSpawned(id) send_vehicle_config(id) -- Attempt to workaround a bug from latest beamng update. Also prevents unicycle cloning(Somewhat) if vehicle:getJBeamFilename() == "unicycle" then - for i = 0, be:getObjectCount() do - local v = be:getObject(i) - if v and (v:getID() ~= vehicle:getID()) and (v:getJBeamFilename() == "unicycle") then + for vid, v in vehiclesIterator() do + if v:getJBeamFilename() == "unicycle" and vid ~= vehicle:getID() then v:delete() end end @@ -549,11 +593,9 @@ end local function onVehicleResetted(id) if not network.connection.connected then return end if M.ownership[id] then - local vehicle = be:getObjectByID(id) - local rotation = quat(vehicle:getRefNodeMatrix():toQuatF()) - local position = vec3(vehicle:getPosition()) - local data = { vehicle_id = id, position = {position.x, position.y, position.z}, rotation = {rotation.x, rotation.y, rotation.z, rotation.w}} - + local vehicle = getObjectByID(id) + local data = { vehicle_id = id, position = {vehicle:getPositionXYZ()}, rotation = vehicle:getRefNodeRotation():toTable()} + network.send_data( { ResetVehicle = data, @@ -564,9 +606,8 @@ local function onVehicleResetted(id) end local function onVehicleSwitched(_id, new_id) - for i = 0, be:getObjectCount() do - local v = be:getObject(i) - if v and (v:getID() ~= new_id) and (v:getJBeamFilename() == "unicycle") then + for vid, v in vehiclesIterator() do + if v:getJBeamFilename() == "unicycle" and vid ~= new_id then v:delete() end end diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua index 441c8f62..9e321416 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_couplers.lua @@ -8,12 +8,12 @@ local ignore_detachment = false local ignored_couplers = {} -local function ignore_coupler_node(node) +local function ignore_coupler_node(node) ignored_couplers[node] = true end local function attach_coupler(node) - local node = v.data.nodes[node] + node = v.data.nodes[node] obj:attachCoupler(node.cid, node.couplerTag or "", node.couplerStrength or 1000000, node.couplerRadius or 0.2, 0, node.couplerLatchSpeed or 0.3, node.couplerTargets or 0) ignore_attachment = true end @@ -31,7 +31,7 @@ local function onCouplerAttached(node_id, obj2_id, obj2_node_id) return end local data = { - obj_a = obj:getID(), + obj_a = objectId, obj_b = obj2_id, node_a_id = node_id, node_b_id = obj2_node_id @@ -49,7 +49,7 @@ local function onCouplerDetached(node_id, obj2_id, obj2_node_id) return end local data = { - obj_a = obj:getID(), + obj_a = objectId, obj_b = obj2_id, node_a_id = node_id, node_b_id = obj2_node_id diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua index bb55d947..2f599f44 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_electrics.lua @@ -113,7 +113,7 @@ local function update_engine_state() if ownership then return end if not electrics.values.engineRunning then return end local engine_running = electrics.values.engineRunning > 0.5 - + -- Trigger starter to swap the engine state if engine_running ~= last_engine_state then controller.mainController.setStarter(true) @@ -143,7 +143,7 @@ local function send() end end local data = { - ElectricsUndefinedUpdate = {obj:getID(), data} + ElectricsUndefinedUpdate = {objectId, data} } if diff_count > 0 then obj:queueGameEngineLua(string.format( @@ -156,7 +156,7 @@ local function apply_diff_signals(diff) local signal_left_input = diff["signal_left_input"] or prev_signal_electrics["signal_left_input"] or 0 local signal_right_input = diff["signal_right_input"] or prev_signal_electrics["signal_right_input"] or 0 local hazard_enabled = (signal_left_input > 0.5 and signal_right_input > 0.5) - + if hazard_enabled then electrics.set_warn_signal(1) else @@ -167,7 +167,7 @@ local function apply_diff_signals(diff) electrics.toggle_right_signal() end end - + prev_signal_electrics["signal_left_input"] = signal_left_input prev_signal_electrics["signal_right_input"] = signal_right_input end @@ -198,7 +198,7 @@ local function apply_diff(buffer_data) apply_diff_signals(diff) for k, v in pairs(diff) do electrics.values[k] = v - + local handler = electrics_handlers[k] if handler then handler(v) end end @@ -225,11 +225,11 @@ local function onExtensionLoaded() end end - -- Ignore common led electrics + -- Ignore common led electrics for i = 0, 10 do ignore_key("led"..tostring(i)) end - + -- Ignore controller electrics if v.data.controller and type(v.data.controller) == 'table' then for _, controller_data in pairs(v.data.controller) do @@ -266,7 +266,7 @@ local function onExtensionLoaded() local electric = controller_data.name .. "_notAttached" local coupler_control_controller = controller.getController(controller_data.name) electrics_handlers[electric] = function(v) update_advanced_coupler_state(coupler_control_controller, v) end - + -- ignore the related couplers, we'll manage them now for _, vn in pairs(tableFromHeaderTable(controller_data.couplerNodes)) do local cid1 = beamstate.nodeNameMap[vn.cid1] @@ -277,19 +277,19 @@ local function onExtensionLoaded() end end end - + -- Ignore commonly used disp_* electrics used on vehicles with gear displays for k,v in pairs(electrics.values) do if type(k) == 'string' and k:sub(1,5) == "disp_" then ignored_keys[k] = true end end - + -- Ignore common extension/controller electrics if _G["4ws"] and type(_G["4ws"]) == 'table' then ignored_keys["4ws"] = true end - + -- Register handlers electrics_handlers["lights_state"] = function(v) electrics.setLightsState(v) end electrics_handlers["fog"] = function(v) electrics.set_fog_lights(v) end @@ -313,10 +313,7 @@ local function kissUpdateOwnership(owned) ownership = owned end - - M.send = send -M.apply = apply M.apply_diff = apply_diff M.ignore_key = ignore_key diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua index 31802de8..e85b7877 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_gearbox.lua @@ -19,7 +19,7 @@ local function set_gear_indices(indices) if mainController and cooldown_timer <= 0 then local index = indices[1] local canShift = true - + -- there's a neutralRejectTimer that will lock sequentials into neutral if we try it more than once -- possibly a game bug if sequential_lock then @@ -27,7 +27,7 @@ local function set_gear_indices(indices) elseif index == 0 and gearbox_is_sequential then sequential_lock = true end - + if canShift then mainController.shiftToGearIndex(index, true) -- true for ignoring sequential bounds last_requseted_gear = index @@ -37,7 +37,7 @@ end local function get_gear_indices() local index = electrics.values.gearIndex - + -- convert gearIndex to values that shiftToGearIndex accepts if index == nil then index = 0 end if not gearbox_is_sequential and not gearbox_is_manual then @@ -49,13 +49,13 @@ local function get_gear_indices() index = 2 -- drive end end - + return {index, 0} end local function get_gearbox_data() local data = { - vehicle_id = obj:getID(), + vehicle_id = objectId, lock_coef = gearbox and gearbox.lockCoef or 0, mode = gearbox and gearbox.mode or "none", gear_indices = get_gear_indices(), @@ -94,7 +94,7 @@ local function onExtensionLoaded() mainController = controller.mainController vehicle_is_electric = tableSize(powertrain.getDevicesByType("electricMotor")) > 0 gearbox = powertrain.getDevice("gearbox") - + -- Search for a gearbox if one wasn't found if not gearbox and not vehicle_is_electric then local devices = powertrain.getDevices() @@ -104,7 +104,7 @@ local function onExtensionLoaded() end end end - + if gearbox then gearbox_is_manual = gearbox.type == "manualGearbox" gearbox_is_sequential = gearbox.type == "sequentialGearbox" @@ -120,7 +120,6 @@ local function kissUpdateOwnership(owned) end end -M.send = send M.apply = apply M.get_gearbox_data = get_gearbox_data M.onExtensionLoaded = onExtensionLoaded diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_nodes.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_nodes.lua deleted file mode 100644 index b0079f49..00000000 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_nodes.lua +++ /dev/null @@ -1,34 +0,0 @@ -local M = {} - -local function send() - local nodes_table = { - vehicle_id = obj:getID(), - nodes = {} - } - for k, node in pairs(v.data.nodes) do - local position = obj:getNodePosition(node.cid) - table.insert(nodes_table.nodes, {position.x, position.y, position.z}) - end - obj:queueGameEngineLua("network.send_messagepack(4, false, \'"..jsonEncode(nodes_table).."\')") -end - -local function apply(nodes) - local nodes = jsonDecode(nodes) - for node, pos in pairs(nodes) do - node = tonumber(node) - obj:setNodePosition(node, float3(pos[1], pos[2], pos[3])) - local beam = v.data.beams[node] - local beamPrecompression = beam.beamPrecompression or 1 - local deformLimit = type(beam.deformLimit) == 'number' and beam.deformLimit or math.huge - obj:setBeam(-1, beam.id1, beam.id2, beam.beamStrength, beam.beamSpring, - beam.beamDamp, type(beam.dampCutoffHz) == 'number' and beam.dampCutoffHz or 0, - beam.beamDeform, deformLimit, type(beam.deformLimitExpansion) == 'number' and beam.deformLimitExpansion or deformLimit, - beamPrecompression - ) - end -end - -M.send = send -M.apply = apply - -return M diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua index 207b0786..49cee456 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua @@ -29,26 +29,38 @@ M.ang_force = 100 M.debug = false M.lerp_factor = 30.0 +local object_position = vec3() +local object_velocity = vec3() +local object_rotation = quat() + +local predicted_position = vec3() local function predict(dt) - M.target_transform.velocity = M.received_transform.velocity + M.received_transform.acceleration * M.received_transform.time_past - local distance = M.target_transform.position:distance(vec3(obj:getPosition())) - local p = M.received_transform.position + M.target_transform.velocity * M.received_transform.time_past - if distance < 2 then - M.target_transform.position = lerp(M.target_transform.position, p, clamp(M.lerp_factor * dt, 0.00001, 1)) + -- M.target_transform.velocity = M.received_transform.velocity + M.received_transform.acceleration * M.received_transform.time_past + local target_velocity = M.target_transform.velocity + target_velocity:setScaled2(M.received_transform.acceleration, M.received_transform.time_past) + target_velocity:setAdd(M.received_transform.velocity) + + local distance = M.target_transform.position:squaredDistance(object_position) + + -- predicted_position = M.received_transform.position + M.target_transform.velocity, M.received_transform.time_past + predicted_position:setScaled2(M.target_transform.velocity, M.received_transform.time_past) + predicted_position:setAdd(M.received_transform.position) + if distance < 2 * 2 then + M.target_transform.position:setLerp(M.target_transform.position, predicted_position, clamp(M.lerp_factor * dt, 0.00001, 1)) else - M.target_transform.position = p + M.target_transform.position:set(predicted_position) end --M.target_transform.angular_velocity = M.received_transform.angular_velocity + M.received_transform.angular_acceleration * M.received_transform.time_past --local rotation_delta = M.target_transform.angular_velocity * M.received_transform.time_past - M.target_transform.rotation = quat(M.received_transform.rotation)-- * quatFromEuler(rotation_delta.x, rotation_delta.y, rotation_delta.z) + M.target_transform.rotation:set(M.received_transform.rotation)-- * quatFromEuler(rotation_delta.x, rotation_delta.y, rotation_delta.z) end local function try_rude() - local distance = M.target_transform.position:distance(vec3(obj:getPosition())) - if distance > 6 then + local distance = M.target_transform.position:squaredDistance(object_position) + if distance > 6 * 6 then local p = M.target_transform.position - obj:queueGameEngineLua("be:getObjectByID("..obj:getID().."):setPositionNoPhysicsReset(Point3F("..p.x..", "..p.y..", "..p.z.."))") + obj:queueGameEngineLua("getObjectByID("..objectId.."):setPositionNoPhysicsReset(vec3("..p.x..", "..p.y..", "..p.z.."))") return true end return false @@ -59,12 +71,25 @@ local function draw_debug() obj.debugDrawProxy:drawSphere(0.3, M.received_transform.position:toFloat3(), color(0,0,255,100)) end +local velocity_difference = vec3() +local position_delta = vec3() +local linear_force = vec3() +local local_ang_vel = vec3() +local angular_velocity_difference = vec3() +local angle_delta = quat() +local angular_force = vec3() +local scaled_ang_vel = vec3() + local function update(dt) if cooldown_timer > 0 then cooldown_timer = cooldown_timer - clamp(dt, 0, 0.02) return end - if dt > 0.1 then return end + if dt > 0.1 or not M.received_transform.time_past then return end + object_position:set(obj:getPositionXYZ()) + object_rotation:set(obj:getRotation()) + object_velocity:set(obj:getVelocityXYZ()) + M.received_transform.time_past = clamp(M.received_transform.time_past + dt, 0, 0.5) predict(dt) if try_rude() then return end @@ -72,35 +97,46 @@ local function update(dt) if M.debug then draw_debug() end - + local force = M.force local ang_force = M.ang_force local c_ang = -math.sqrt(4 * ang_force) - local velocity_difference = M.target_transform.velocity - vec3(obj:getVelocity()) - local position_delta = M.target_transform.position - vec3(obj:getPosition()) - --position_delta = position_delta:normalized() * math.pow(position_delta:length(), 2) - local linear_force = (velocity_difference + position_delta * force) * dt * 5 - if linear_force:length() > 10 then - linear_force = linear_force:normalized() * 10 + velocity_difference:setSub2(M.target_transform.velocity, object_velocity) + position_delta:setSub2(M.target_transform.position, object_position) + + -- linear_force = (velocity_difference + position_delta * force) * dt * 5 + linear_force:setScaled2(position_delta, force) + linear_force:setAdd(velocity_difference) + linear_force:setScaled(dt * 5) + if linear_force:squaredLength() > 10 * 10 then + linear_force:normalize() + linear_force:setScaled(10) end - - local local_ang_vel = vec3( + + local_ang_vel:set( obj:getYawAngularVelocity(), obj:getPitchAngularVelocity(), obj:getRollAngularVelocity() ) - local angular_velocity_difference = M.target_transform.angular_velocity - local_ang_vel - local angle_delta = M.target_transform.rotation / quat(obj:getRotation()) - local angular_force = angle_delta:toEulerYXZ() - local angular_force = (angular_velocity_difference + angular_force * ang_force + c_ang * local_ang_vel) * dt - if angular_force:length() > 25 then + angular_velocity_difference:setSub2(M.target_transform.angular_velocity, local_ang_vel) + angle_delta:setMulInv2(M.target_transform.rotation, object_rotation) + angular_force:set(angle_delta:toEulerYXZ()) + + -- angular_force = (angular_velocity_difference + (angular_force * ang_force) + (c_ang * local_ang_vel)) * dt + angular_force:setScaled(ang_force) + scaled_ang_vel:setScaled2(local_ang_vel, c_ang) + angular_force:setAdd(scaled_ang_vel) + angular_force:setAdd(angular_velocity_difference) + angular_force:setScaled(dt) + + if angular_force:squaredLength() > 25 * 25 then return end - if angular_force:length() > 0.1 then + if angular_force:squaredLength() > 0.1 * 0.1 then kiss_vehicle.apply_linear_velocity_ang_torque( linear_force.x, linear_force.y, @@ -109,7 +145,7 @@ local function update(dt) angular_force.z, angular_force.x ) - elseif linear_force:length() > (dt * 15) then + elseif linear_force:squaredLength() > (dt * 15) * (dt * 15) then kiss_vehicle.apply_linear_velocity( linear_force.x, linear_force.y, @@ -118,30 +154,45 @@ local function update(dt) end end +local transform_velocity = vec3() +local transform_angular_velocity = vec3() local function set_target_transform(buffer_data) local transform = string_buffer.decode(buffer_data) local time_dif = clamp((transform.sent_at - M.received_transform.sent_at), 0.01, 0.1) - M.received_transform.acceleration = (vec3(transform.velocity) - M.received_transform.velocity) / time_dif - if M.received_transform.acceleration:length() > 5 then - M.received_transform.acceleration = M.received_transform.acceleration:normalized() * 5 + transform_velocity:set(transform.velocity[1], transform.velocity[2], transform.velocity[3]) + transform_angular_velocity:set(transform.angular_velocity[1], transform.angular_velocity[2], transform.angular_velocity[3]) + + local acceleration = M.received_transform.acceleration + acceleration:setSub2(transform_velocity, M.received_transform.velocity) + acceleration:setScaled(1 / time_dif) + if acceleration:squaredLength() > 5 * 5 then + acceleration:normalize() + acceleration:setScaled(5) end - M.received_transform.angular_acceleration = (vec3(transform.angular_velocity) - M.received_transform.angular_velocity) / time_dif - if M.received_transform.acceleration:length() > 5 then - M.received_transform.angular_acceleration = M.received_transform.angular_acceleration:normalized() * 5 + + local angular_acceleration = M.received_transform.angular_acceleration + angular_acceleration:setSub2(transform_angular_velocity, M.received_transform.angular_velocity) + angular_acceleration:setScaled(1 / time_dif) + if angular_acceleration:squaredLength() > 5 * 5 then + angular_acceleration:normalize() + angular_acceleration:setScaled(5) end - M.received_transform.position = vec3(transform.position) - M.received_transform.rotation = quat(transform.rotation) - M.received_transform.velocity = vec3(transform.velocity) - M.received_transform.angular_velocity = vec3(transform.angular_velocity) + + M.received_transform.position:set(transform.position[1], transform.position[2], transform.position[3]) + M.received_transform.rotation:set(transform.rotation[1], transform.rotation[2], transform.rotation[3], transform.rotation[4]) + M.received_transform.velocity:set(transform_velocity) + M.received_transform.angular_velocity:set(transform_angular_velocity) M.received_transform.time_past = transform.time_past end local function onExtensionLoaded() - M.received_transform.position = vec3(obj:getPosition()) - M.target_transform.position = vec3(obj:getPosition()) - M.received_transform.rotation = quat(obj:getRotation()) - M.target_transform.rotation = quat(obj:getRotation()) + object_position:set(obj:getPositionXYZ()) + object_rotation:set(obj:getRotation()) + M.received_transform.position:set(object_position) + M.target_transform.position:set(object_position) + M.received_transform.rotation:set(object_rotation) + M.target_transform.rotation:set(object_rotation) cooldown_timer = 1.5 end diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua index 3a728f75..0df90db1 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua @@ -2,7 +2,6 @@ local M = {} local string_buffer = require("string.buffer") -local parts_config = v.config local nodes = {} local ref_nodes = {} @@ -71,17 +70,14 @@ local function update_eligible_nodes() end local function update_transform_info() - local r = quat(obj:getRotation()) - local p = obj:getPosition() - local throttle_input = electrics.values.throttle_input or 0 local brake_input = electrics.values.brake_input or 0 if electrics.values.gearboxMode == "arcade" and electrics.values.gearIndex < 0 then throttle_input, brake_input = brake_input, throttle_input end - + local input = { - vehicle_id = obj:getID() or 0, + vehicle_id = objectId, throttle_input = throttle_input, brake_input = brake_input, clutch = electrics.values.clutch_input or 0, @@ -90,8 +86,8 @@ local function update_transform_info() } local gearbox = kiss_gearbox.get_gearbox_data() local transform = { - position = {p.x, p.y, p.z}, - rotation = {r.x, r.y, r.z, r.w}, + position = {obj:getPositionXYZ()}, + rotation = {obj:getRotation()}, input = input, gearbox = gearbox, vel_pitch = obj:getPitchAngularVelocity(), @@ -100,12 +96,14 @@ local function update_transform_info() } obj:queueGameEngineLua(string.format( "kisstransform.push_transform(%d, %q)", - obj:getID(), string_buffer.encode(transform))) + objectId, string_buffer.encode(transform))) end +local velocity = vec3() +local force = vec3() +local angular_velocity = vec3() local function apply_linear_velocity(x, y, z) - local velocity = vec3(x, y, z) - local force = float3(0, 0, 0) + velocity:set(x, y, z) for k=1, #nodes do local node = nodes[k] if node[3] then @@ -116,23 +114,31 @@ local function apply_linear_velocity(x, y, z) end end +local object_rotation = quat() +local node_position = vec3() local function apply_linear_velocity_ang_torque(x, y, z, pitch, roll, yaw) - local velocity = vec3(x, y, z) - local nodes = nodes + velocity:set(x, y, z) -- 0.1 seems like the safe value we can use for low velocities -- NOTE: Doesn't work as well as expected - if velocity:length() < 0.01 then + --if velocity:length() < 0.01 then --nodes = ref_nodes - end - local rot = vec3(pitch, roll, yaw):rotated(quat(obj:getRotation())) - local node_position = vec3() - local force = float3(0, 0, 0) + --end + + object_rotation:set(obj:getRotation()) + angular_velocity:set(pitch, roll, yaw) + angular_velocity:setRotate(object_rotation) + for k=1, #nodes do local node = nodes[k] if node[3] then + -- as there is no getNodePositionXYZ, this cannot be further optimized + -- TODO: check if future game updates add this, would be a drop-in replacement node_position:set(obj:getNodePosition(node[1])) - local result = (velocity + node_position:cross(rot)) * node[2] - force:set(result.x, result.y, result.z) + + -- force = (velocity + node_position:cross(angular_velocity)) * node[2] + force:setCross(node_position, angular_velocity) + force:setAdd(velocity) + force:setScaled(node[2]) obj:applyForceVector(node[1], force) end end @@ -140,15 +146,13 @@ end local function send_vehicle_config() local config = v.config - local r = quat(obj:getRotation()) - local p = obj:getPosition() local data = { - position = {p.x, p.y, p.z}, - rotation = {r.x, r.y, r.z, r.w}, + position = {obj:getPositionXYZ()}, + rotation = {obj:getRotation()}, } obj:queueGameEngineLua(string.format( "vehiclemanager.send_vehicle_config_inner(%d, %q, %q)", - obj:getID(), jsonEncode(config), string_buffer.encode(data))) + objectId, jsonEncode(config), string_buffer.encode(data))) end M.update_transform_info = update_transform_info @@ -156,7 +160,6 @@ M.apply_linear_velocity_ang_torque = apply_linear_velocity_ang_torque M.update_eligible_nodes = update_eligible_nodes M.apply_linear_velocity = apply_linear_velocity M.onExtensionLoaded = onExtensionLoaded -M.set_reference = set_reference -M.save_state = save_state M.send_vehicle_config = send_vehicle_config + return M diff --git a/KISSMultiplayer/scripts/kiss_mp/modScript.lua b/KISSMultiplayer/scripts/kiss_mp/modScript.lua index 42bf4bfb..6753543e 100644 --- a/KISSMultiplayer/scripts/kiss_mp/modScript.lua +++ b/KISSMultiplayer/scripts/kiss_mp/modScript.lua @@ -1,32 +1,12 @@ print("Executing KissMP modScript...") loadJsonMaterialsFile("art/shapes/kissmp_playermodels/main.materials.json") -load("kissplayers") -registerCoreModule("kissplayers") - -load("vehiclemanager") -registerCoreModule("vehiclemanager") - -load("kisstransform") -registerCoreModule("kisstransform") - -load("kissui") -registerCoreModule("kissui") - -load("kissmods") -registerCoreModule("kissmods") - -load("kissrichpresence") -registerCoreModule("kissrichpresence") - -load("network") -registerCoreModule("network") - -load("kissconfig") -registerCoreModule("kissconfig") - -load("kissvoicechat") -registerCoreModule("kissvoicechat") - ---load("kissutils") ---registerCoreModule("kissutils") +setExtensionUnloadMode("kissplayers", "manual") +setExtensionUnloadMode("vehiclemanager", "manual") +setExtensionUnloadMode("kisstransform", "manual") +setExtensionUnloadMode("kissui", "manual") +setExtensionUnloadMode("kissmods", "manual") +setExtensionUnloadMode("kissrichpresence", "manual") +setExtensionUnloadMode("network", "manual") +setExtensionUnloadMode("kissconfig", "manual") +setExtensionUnloadMode("kissvoicechat", "manual") From fcf8f2eb9620f86f4a93ad1f5119b6377f02b7a2 Mon Sep 17 00:00:00 2001 From: Vlad118 Date: Sun, 5 Apr 2026 13:59:34 +0100 Subject: [PATCH 4/5] fix for when master server domain is dead: added timeout for LUA (fixes slow game startup); offload blocking http listen and timeout for response --- .../extensions/kissmp/ui/tabs/server_list.lua | 1 + kissmp-bridge/src/http_proxy.rs | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/server_list.lua b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/server_list.lua index ce2f192a..46e2be58 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/server_list.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/server_list.lua @@ -1,6 +1,7 @@ local M = {} local imgui = ui_imgui local http = require("socket.http") +http.TIMEOUT = 0.5 local version = require("lua/ge/extensions/kissmp/version") local VERSION_PRTL = version.VERSION_STR diff --git a/kissmp-bridge/src/http_proxy.rs b/kissmp-bridge/src/http_proxy.rs index 69e98a83..19700f3f 100644 --- a/kissmp-bridge/src/http_proxy.rs +++ b/kissmp-bridge/src/http_proxy.rs @@ -14,10 +14,15 @@ struct ServerHostData { pub async fn spawn_http_proxy(discord_tx: std::sync::mpsc::Sender) { // Master server proxy //println!("start"); - let server = tiny_http::Server::http("0.0.0.0:3693").unwrap(); + let server = std::sync::Arc::new(tiny_http::Server::http("0.0.0.0:3693").unwrap()); let mut destroyer: Option> = None; loop { - for request in server.incoming_requests() { + let server_clone = server.clone(); + // Offload blocking HTTP listen to a background thread so Tokio doesn't freeze + let request = match tokio::task::spawn_blocking(move || server_clone.recv()).await { + Ok(Ok(req)) => req, + _ => continue, + }; let addr = request.remote_addr(); if addr.ip() != Ipv4Addr::new(127, 0, 0, 1) { continue; @@ -85,12 +90,15 @@ pub async fn spawn_http_proxy(discord_tx: std::sync::mpsc::Sender Date: Sun, 5 Apr 2026 14:21:41 +0100 Subject: [PATCH 5/5] DOMAIN CHANGE: from kissmp.online to kissmp.thehellbox.ru --- KISSMultiplayer/lua/ge/extensions/kissui.lua | 2 +- kissmp-server/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/KISSMultiplayer/lua/ge/extensions/kissui.lua b/KISSMultiplayer/lua/ge/extensions/kissui.lua index 5d9a0606..4d4191e4 100644 --- a/KISSMultiplayer/lua/ge/extensions/kissui.lua +++ b/KISSMultiplayer/lua/ge/extensions/kissui.lua @@ -15,7 +15,7 @@ M.tabs = { M.dependencies = {"ui_imgui"} -M.master_addr = "http://kissmp.online:3692/" +M.master_addr = "http://kissmp.thehellbox.ru:3692/" M.bridge_launched = false M.show_download = false diff --git a/kissmp-server/src/lib.rs b/kissmp-server/src/lib.rs index ed24dbef..d9567bab 100644 --- a/kissmp-server/src/lib.rs +++ b/kissmp-server/src/lib.rs @@ -136,7 +136,7 @@ impl Server { self.upnp_port = Some(port); info!("Fetching public IP address..."); let socket = UdpSocket::bind(&addr).unwrap(); - socket.connect("kissmp.online:3691"); + socket.connect("kissmp.thehellbox.ru:3691"); let mut i = 0; while i < 5 { let _ = socket.send(b"hi"); @@ -273,7 +273,7 @@ impl Server { let client = self.reqwest_client.clone(); tokio::spawn(async move { let _ = client - .post("http://kissmp.online:3692") + .post("http://kissmp.thehellbox.ru:3692") .body(server_info) .send() .await;