diff --git a/KISSMultiplayer/lua/ge/extensions/kissghosts.lua b/KISSMultiplayer/lua/ge/extensions/kissghosts.lua new file mode 100644 index 00000000..785354e4 --- /dev/null +++ b/KISSMultiplayer/lua/ge/extensions/kissghosts.lua @@ -0,0 +1,190 @@ +local M = {} + +M.global_state = {} +M.ghost_state = {} +M.overrides = {} + +local actual_ghost_states = {} +local function set_vehicle_ghost(veh_id, ghost_state, mesh_fade, is_global) + ghost_state = ghost_state or 0 + if M.overrides[veh_id] then + -- we still want M.ghost_state to keep its original value + ghost_state = M.overrides[veh_id] + else + M.ghost_state[veh_id] = ghost_state + end + + if is_global then + M.global_state[veh_id] = ghost_state + end + + if actual_ghost_states[veh_id] == ghost_state then return end + actual_ghost_states[veh_id] = ghost_state + + local veh = getObjectByID(veh_id) + if ghost_state == 0 then -- no ghost + veh:setActive(1) + veh:queueLuaCommand("kiss_vehicle.set_collision(true)") + elseif ghost_state == 1 then -- no collisions + veh:setActive(1) + veh:queueLuaCommand("kiss_vehicle.set_collision(false)") + elseif ghost_state == 2 then -- in stasis (more logic for this state is in onUpdate) + veh:setActive(1) + veh:queueLuaCommand("kiss_vehicle.set_collision(false)") + elseif ghost_state == 3 then -- tucked somewhere in a parallel universe + veh:setActive(0) + end + + if mesh_fade ~= nil then + if type(mesh_fade) ~= "number" then + mesh_fade = mesh_fade and 0.5 or 1 + end + veh:setMeshAlpha(mesh_fade, "") + end +end + +local bb_center_a, bb_half_axis0_a, bb_half_axis1_a, bb_half_axis2_a = vec3(), vec3(), vec3(), vec3() +local bb_center_b, bb_half_axis0_b, bb_half_axis1_b, bb_half_axis2_b = vec3(), vec3(), vec3(), vec3() + +local function check_overlaps(vid_a) + bb_center_a:set(be:getObjectOOBBCenterXYZ(vid_a)) + bb_half_axis0_a:set(be:getObjectOOBBHalfAxisXYZ(vid_a, 0)) + bb_half_axis1_a:set(be:getObjectOOBBHalfAxisXYZ(vid_a, 1)) + bb_half_axis2_a:set(be:getObjectOOBBHalfAxisXYZ(vid_a, 2)) + + local id_to_owner_map = vehiclemanager.id_to_owner_map + for vid_b in vehiclesIterator() do + if vid_a ~= vid_b and id_to_owner_map[vid_a] ~= id_to_owner_map[vid_b] then + bb_center_b:set(be:getObjectOOBBCenterXYZ(vid_b)) + bb_half_axis0_b:set(be:getObjectOOBBHalfAxisXYZ(vid_b, 0)) + bb_half_axis1_b:set(be:getObjectOOBBHalfAxisXYZ(vid_b, 1)) + bb_half_axis2_b:set(be:getObjectOOBBHalfAxisXYZ(vid_b, 2)) + + if overlapsOBB_OBB(bb_center_a, bb_half_axis0_a, bb_half_axis1_a, bb_half_axis2_a, + bb_center_b, bb_half_axis0_b, bb_half_axis1_b, bb_half_axis2_b) then + return true + end + end + end + + return false +end + +local function set_respawn_override(vid, is_global) + if check_overlaps(vid) then + M.overrides[vid] = 1 + else + M.overrides[vid] = nil + end + + set_vehicle_ghost(vid, M.ghost_state[vid], M.overrides[vid] ~= nil, is_global) +end + +local function set_pause_override(vid, override, is_global) + if override then + M.overrides[vid] = 2 + elseif check_overlaps(vid) then + M.overrides[vid] = 1 + else + M.overrides[vid] = nil + end + + set_vehicle_ghost(vid, M.ghost_state[vid], M.overrides[vid] ~= nil, is_global) +end + +local velocity = vec3() +local function set_pause_override_for_owned(bool) + for id in pairs(vehiclemanager.ownership) do + if bool then + local vehicle = getObjectByID(id) + velocity:set(vehicle:getVelocityXYZ()) + if velocity:length() < 1 then + set_pause_override(id, true, true) + end + else + set_pause_override(id, false, true) + end + end + vehiclemanager.send_vehicle_meta_updates() +end + +local function onUpdate() + for vid, v in pairs(actual_ghost_states) do + if v == 2 then + -- 0.75 appears to be a safe value + -- values too small cause breakage + local veh = getObjectByID(vid) + if veh and v then + veh:applyClusterVelocityScaleAdd(0, 0.75, 0, 0, 0) + end + end + end + + for vid, v in pairs(M.overrides) do + if v == 1 then + set_respawn_override(vid, true) + end + end +end + +local pause_counter = 0 +local pause_requests = {} + +local function attempt_pause(id) + if pause_requests[id] then + return + end + pause_requests[id] = true + pause_counter = pause_counter + 1 + if pause_counter > 0 then + set_pause_override_for_owned(true) + SFXSystem.setGlobalParameter("g_GamePause", 1) + end +end + +local function attempt_unpause(id) + if not pause_requests[id] then + return + end + pause_requests[id] = nil + pause_counter = math.max(0, pause_counter - 1) + + if pause_counter == 0 then + set_pause_override_for_owned(false) + SFXSystem.setGlobalParameter("g_GamePause", 0) + end +end + +local function onVehicleSpawned(veh_id) + -- because collisions are disabled vehicle side on respawn, this *must* update + actual_ghost_states[veh_id] = nil + + set_respawn_override(veh_id, true) +end + +local function onVehicleResetted(veh_id) + -- because collisions are disabled vehicle side on respawn, this *must* update + actual_ghost_states[veh_id] = nil + + set_respawn_override(veh_id, true) +end + +local function onVehicleDestroyed(vehId) + M.global_state[vehId] = nil + M.ghost_state[vehId] = nil + M.overrides[vehId] = nil +end + +M.onUpdate = onUpdate +M.onVehicleDestroyed = onVehicleDestroyed +M.onVehicleSpawned = onVehicleSpawned +M.onVehicleResetted = onVehicleResetted + +M.set_vehicle_ghost = set_vehicle_ghost +M.set_pause_override = set_pause_override +M.set_respawn_override = set_respawn_override + +M.attempt_pause = attempt_pause +M.attempt_unpause = attempt_unpause + +return M \ No newline at end of file diff --git a/KISSMultiplayer/lua/ge/extensions/network.lua b/KISSMultiplayer/lua/ge/extensions/network.lua index e31bd5a4..7a19e317 100644 --- a/KISSMultiplayer/lua/ge/extensions/network.lua +++ b/KISSMultiplayer/lua/ge/extensions/network.lua @@ -31,6 +31,16 @@ M.connection = { time_offset = 0 } +local original_simTimeAuthorityFuncs = nil +-- pushPauseRequest +-- popPauseRequest + +local blocked_inputs = {"slower_motion", "faster_motion", "toggle_slow_motion", "pause"} +local function block_inputs(block) + core_input_actionFilter.setGroup('kissmpActions', blocked_inputs) + core_input_actionFilter.addAction(0, 'kissmpActions', block) +end + local CHUNK_SIZE = 65000 -- Safe size under 65536 limit local message_handlers = {} @@ -121,6 +131,12 @@ local function disconnect(data) if getMissionFilename() ~= "" then returnToMainMenu() end + + if original_simTimeAuthorityFuncs then + simTimeAuthority.pushPauseRequest = original_simTimeAuthorityFuncs.pushPauseRequest + simTimeAuthority.popPauseRequest = original_simTimeAuthorityFuncs.popPauseRequest + end + block_inputs(false) end local function handle_player_info(player_info) @@ -395,10 +411,20 @@ local function connect(addr, player_name, is_public) end end vehiclemanager.loading_map = true + + original_simTimeAuthorityFuncs = { + pushPauseRequest = simTimeAuthority.pushPauseRequest, + popPauseRequest = simTimeAuthority.popPauseRequest, + } + + simTimeAuthority.pushPauseRequest = kissghosts.attempt_pause + simTimeAuthority.popPauseRequest = kissghosts.attempt_unpause + if #missing_mods == 0 then kissmods.mount_mods(mod_names) change_map(server_info.map) end + block_inputs(true) kissrichpresence.update() kissui.chat.add_message("Connected!") end diff --git a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua index e5bc0b5b..a1ebd486 100644 --- a/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua +++ b/KISSMultiplayer/lua/ge/extensions/vehiclemanager.lua @@ -7,12 +7,14 @@ local generation = 0 local meta_timer = 0 local colors_buffer = {} local plates_buffer = {} +local global_ghost_state_buffer = {} local first_vehicle = true M.loading_map = false M.id_map = {} M.server_ids = {} M.ownership = {} +M.id_to_owner_map = {} M.vehicle_updates_buffer = {} M.packet_gen_buffer = {} M.is_network_session = false @@ -115,12 +117,19 @@ local function send_vehicle_meta_updates() end colors_buffer[id] = colors + local ghost_state = kissghosts.global_state[id] or 0 + if global_ghost_state_buffer[id] then + changed = changed or global_ghost_state_buffer[id] ~= ghost_state + end + global_ghost_state_buffer[id] = ghost_state + if changed then local data = { VehicleMetaUpdate = { id, plate, - colors + colors, + ghost_state } } network.send_data(data, true) @@ -174,6 +183,7 @@ local function send_vehicle_config_inner(id, parts_config_json, buffer_data) vehicle_data.rotation = {rotation.x, rotation.y, rotation.z, rotation.w} vehicle_data.server_id = 0 vehicle_data.owner = 0 + vehicle_data.global_ghost_state = 0 network.send_data( { VehicleData = vehicle_data @@ -215,6 +225,7 @@ local function spawn_vehicle(data) M.vehicle_buffer[data.server_id] = data return end + M.id_to_owner_map[data.in_game_id] = data.owner if data.owner == network.get_client_id() then log("I", "kissmp.vehiclemanager.spawn_vehicle", "Vehicle belongs to local client, setting ownership") M.id_map[data.server_id] = data.in_game_id @@ -436,6 +447,8 @@ local function update_vehicle_meta(data) extensions.core_vehicle_manager.liveUpdateVehicleColors(id, vehicle, i, table_to_color(ct)) end vehicle:setField('partConfig', '', serialize(vd.config)) + + kissghosts.set_pause_override(id, data.global_ghost_state == 2, true) end local function electrics_diff_update(data) @@ -557,15 +570,10 @@ end local function onVehicleSpawned(id) if not network.connection.connected then return end local vehicle = getObjectByID(id) - tempVec1:set(vehicle:getPositionXYZ()) - if first_vehicle then - 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 vehicle:queueLuaCommand("extensions.addModulePath('lua/vehicle/extensions/kiss_mp')") vehicle:queueLuaCommand("extensions.loadModulesInDirectory('lua/vehicle/extensions/kiss_mp')") + vehicle:queueLuaCommand("rawset(_G, 'ghostOnReset', true)") -- this is important for ghosting + send_vehicle_config(id) -- Attempt to workaround a bug from latest beamng update. Also prevents unicycle cloning(Somewhat) if vehicle:getJBeamFilename() == "unicycle" then @@ -628,6 +636,7 @@ local function onMissionLoaded(mission) if not network.connection.connected then return end M.id_map = {} M.ownership = {} + M.id_to_owner_map = {} M.loading_map = false first_vehicle = true end @@ -655,6 +664,7 @@ M.attach_coupler = attach_coupler M.detach_coupler = detach_coupler M.attach_coupler_inner = attach_coupler_inner M.detach_coupler_inner = detach_coupler_inner +M.send_vehicle_meta_updates = send_vehicle_meta_updates M.set_position = set_position M.set_position_rotation = set_position_rotation diff --git a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua index 0df90db1..cae45430 100644 --- a/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua +++ b/KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua @@ -5,6 +5,8 @@ local string_buffer = require("string.buffer") local nodes = {} local ref_nodes = {} +M.collision = true + local last_node = 1 local nodes_per_frame = 32 @@ -155,11 +157,23 @@ local function send_vehicle_config() objectId, jsonEncode(config), string_buffer.encode(data))) end +local function set_collision(collision) + M.collision = collision + + if collision then + obj:setGhostEnabled(false) + else + obj:setGhostEnabled(true) + end +end + M.update_transform_info = update_transform_info 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_collision = set_collision 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 6753543e..177e163d 100644 --- a/KISSMultiplayer/scripts/kiss_mp/modScript.lua +++ b/KISSMultiplayer/scripts/kiss_mp/modScript.lua @@ -2,6 +2,7 @@ print("Executing KissMP modScript...") loadJsonMaterialsFile("art/shapes/kissmp_playermodels/main.materials.json") setExtensionUnloadMode("kissplayers", "manual") +setExtensionUnloadMode("kissghosts", "manual") setExtensionUnloadMode("vehiclemanager", "manual") setExtensionUnloadMode("kisstransform", "manual") setExtensionUnloadMode("kissui", "manual") diff --git a/kissmp-server/src/events.rs b/kissmp-server/src/events.rs index d69f3908..dbca766f 100644 --- a/kissmp-server/src/events.rs +++ b/kissmp-server/src/events.rs @@ -192,6 +192,7 @@ impl Server { vehicle.data.palete_0 = meta.colors_table[1]; vehicle.data.palete_1 = meta.colors_table[2]; vehicle.data.plate = meta.plate.clone(); + vehicle.data.global_ghost_state = meta.global_ghost_state.clone(); let mut meta = meta.clone(); meta.vehicle_id = server_id; for (_, client) in &mut self.connections { diff --git a/kissmp-server/src/lua.rs b/kissmp-server/src/lua.rs index 2ce25d2b..1ffcbc02 100644 --- a/kissmp-server/src/lua.rs +++ b/kissmp-server/src/lua.rs @@ -426,7 +426,7 @@ pub fn setup_lua() -> (rlua::Lua, mpsc::Receiver) { let build_vehicle = lua_ctx .create_function( move |_, - (parts_config, color, p0, p1, plate, name, position, rotation): ( + (parts_config, color, p0, p1, plate, name, position, rotation, global_ghost_state): ( String, Vec, Vec, @@ -435,6 +435,7 @@ pub fn setup_lua() -> (rlua::Lua, mpsc::Receiver) { String, Vec, Vec, + i8, )| { Ok(LuaVehicleData(VehicleData { parts_config, @@ -448,6 +449,7 @@ pub fn setup_lua() -> (rlua::Lua, mpsc::Receiver) { owner: None, position: [position[0], position[1], position[2]], rotation: [rotation[0], rotation[1], rotation[2], rotation[3]], + global_ghost_state: global_ghost_state, })) }, ) diff --git a/shared/src/vehicle/mod.rs b/shared/src/vehicle/mod.rs index 73d22fef..45c4c596 100644 --- a/shared/src/vehicle/mod.rs +++ b/shared/src/vehicle/mod.rs @@ -30,6 +30,7 @@ pub struct VehicleData { pub owner: Option, pub position: [f32; 3], pub rotation: [f32; 4], + pub global_ghost_state: i8, } // A single packet that contains all of the vehicle updates. diff --git a/shared/src/vehicle/vehicle_meta.rs b/shared/src/vehicle/vehicle_meta.rs index f80f94a2..6f0b0e3a 100644 --- a/shared/src/vehicle/vehicle_meta.rs +++ b/shared/src/vehicle/vehicle_meta.rs @@ -5,4 +5,5 @@ pub struct VehicleMeta { pub vehicle_id: u32, pub plate: Option, pub colors_table: [[f32; 8]; 3], + pub global_ghost_state: i8, }