diff --git a/bin/data/entities/scripts/dark/ambient.lua b/bin/data/entities/scripts/dark/ambient.lua index 92645bd1..20c69864 100644 --- a/bin/data/entities/scripts/dark/ambient.lua +++ b/bin/data/entities/scripts/dark/ambient.lua @@ -1,3 +1,4 @@ +local ent = ent local metadata = ent:getComponent("Metadata") local darkMeta = metadata["dark"] or {} local soundMeta = darkMeta["sound"] or {} @@ -7,10 +8,16 @@ local flags = soundMeta["flags"] or 0 local baseVolume = tonumber(soundMeta["volume"]) or 1.0 local radius = tonumber(soundMeta["radius"]) or 0.0 -local startOn = (math.floor(flags / 1) % 2) ~= 0 -local environmental = (math.floor(flags / 2) % 2) ~= 0 +local environmental = (math.floor(flags / 1) % 2) ~= 0 +local isTurnedOff = (math.floor(flags / 4) % 2) ~= 0 local isMusic = (math.floor(flags / 16) % 2) ~= 0 +if isMusic then return end +if schemaName == "" then return end + +local isTurnedOff = (math.floor(flags / 4) % 2) ~= 0 +local startOn = not isTurnedOff + local isPlaying = false local function playSound() @@ -42,7 +49,7 @@ local function playSound() local schemaVol = tonumber(soundMeta["schema_volume"]) or 0 local overrideVol = tonumber(soundMeta["volume"]) or 0 - local baseVolume = 1.0 + local baseVolume = 0.2 if overrideVol ~= 0 then baseVolume = math.pow(10, overrideVol / 2000.0) end local finalVolume = baseVolume * math.pow(10, schemaVol / 2000.0) @@ -57,6 +64,7 @@ local function playSound() if radius > 0 and not environmental then payload.maxDistance = radius + payload.referenceDistance = 1.0 payload.rolloffFactor = 1.0 end @@ -71,7 +79,7 @@ local function stopSound() }) end -ent:addHook("dark:Message.%UID%", function(payload) +ent:addHook("link:Message.%UID%", function(payload) local msg = payload.message if msg == "TurnOn" then diff --git a/bin/data/entities/scripts/dark/door.lua b/bin/data/entities/scripts/dark/door.lua index 30b80589..1abc735b 100644 --- a/bin/data/entities/scripts/dark/door.lua +++ b/bin/data/entities/scripts/dark/door.lua @@ -6,7 +6,7 @@ local physicsBody = ent:getComponent("PhysicsBody") local metadata = ent:getComponent("Metadata") local darkMeta = metadata["dark"] or {} local doorMeta = darkMeta["door"] or {} -local schemaDb = doorMeta["schema_db"] or (darkMeta["schema_db"] or {}) +local schemaDb = darkMeta["schema_db"] or {} local classTags = doorMeta["class_tags"] or (darkMeta["class_tags"] or "") local state = 0 @@ -192,7 +192,7 @@ ent:bind("tick", function(self) end end) -ent:addHook("dark:Message.%UID%", function(payload) +ent:addHook("link:Message.%UID%", function(payload) local msg = payload.message if msg == "TurnOn" then diff --git a/bin/data/entities/scripts/dark/elevator.lua b/bin/data/entities/scripts/dark/elevator.lua new file mode 100644 index 00000000..71ef26dd --- /dev/null +++ b/bin/data/entities/scripts/dark/elevator.lua @@ -0,0 +1,107 @@ +local ent = ent +local scene = entities.currentScene() + +local transform = ent:getComponent("Transform") +local physicsBody = ent:getComponent("PhysicsBody") +local metadata = ent:getComponent("Metadata") +local darkMeta = metadata["dark"] or {} + +local speed = 2.0 +local state = 0 +local currentTargetPos = nil +local activeWaypointDarkID = nil + +local function findInitialWaypoint() + local conns = darkMeta["connections"] or {} + for _, conn in ipairs(conns) do + if conn.flavor == "TPathInit" or conn.flavor == "TPathNext" or conn.flavor == "TPath" then + return conn.target_id + end + end + return nil +end + +local currentWaypointID = findInitialWaypoint() + +local function getWaypointPos(darkID) + if not darkID then return nil end + local targetUID = _G.DarkTargets[darkID] + if targetUID then + local wpEnt = entities.get(targetUID) + if wpEnt then + return wpEnt:getComponent("Transform").position, targetUID + end + end + return nil +end + +local function advanceWaypoint(darkID) + local targetUID = _G.DarkTargets[darkID] + if targetUID then + local wpEnt = entities.get(targetUID) + if wpEnt then + local wpMeta = wpEnt:getComponent("Metadata")["dark"] or {} + local conns = wpMeta["connections"] or {} + for _, conn in ipairs(conns) do + if conn.flavor == "TPathNext" or conn.flavor == "TPath" then + return conn.target_id + end + end + end + end + return nil +end + +ent:bind("tick", function(self) + if state == 1 and currentTargetPos then + local myPos = transform.position + local dir = currentTargetPos - myPos + + local dist = dir:norm() + local step = time.delta() * speed + + if dist <= step then + if physicsBody:initialized() then + physicsBody:setVelocity(Vector3f(0, 0, 0)) + end + transform.position = currentTargetPos + state = 0 + + local nextWp = advanceWaypoint(activeWaypointDarkID) + if nextWp then + currentWaypointID = nextWp + end + + else + local vel = dir:normalize() * speed + if physicsBody:initialized() then + physicsBody:setVelocity(vel) + else + transform.position = myPos + (vel * time.delta()) + end + end + end +end) + +ent:addHook("link:Message.%UID%", function(payload) + local msg = payload.message + + + if (msg == "TurnOn" or msg == "TurnOff") and state == 0 then + local targetPos, tUid = getWaypointPos(currentWaypointID) + + if targetPos and (transform.position - targetPos):norm() < 0.1 then + local nextWp = advanceWaypoint(currentWaypointID) + if nextWp then + currentWaypointID = nextWp + targetPos, tUid = getWaypointPos(currentWaypointID) + end + end + + if targetPos then + currentTargetPos = targetPos + activeWaypointDarkID = currentWaypointID + state = 1 + end + end +end) \ No newline at end of file diff --git a/bin/data/entities/scripts/dark/io.lua b/bin/data/entities/scripts/dark/io.lua index 8fbad98e..698ece2b 100644 --- a/bin/data/entities/scripts/dark/io.lua +++ b/bin/data/entities/scripts/dark/io.lua @@ -1,33 +1,165 @@ local ent = ent local scene = entities.currentScene() -local metadata = ent:getComponent("Metadata") -local FLAVOR_CONTROL_DEVICE = 1 +local metadata = ent:getComponent("Metadata") +local darkMeta = metadata["dark"] or {} +local schemaDb = darkMeta["schema_db"] or {} +local classTags = darkMeta["class_tags"] or "" _G.DarkTargets = _G.DarkTargets or {} -local darkMeta = metadata["dark"] or {} if darkMeta["id"] then _G.DarkTargets[darkMeta["id"]] = ent:uid() end -ent:addHook("dark:Message.%UID%", function(payload) +_G.DarkScripts = _G.DarkScripts or {} + +_G.DarkScripts["RelayTrap"] = function(entity, payload, dMeta) + local msg = payload.message + local eName = entity or entity:uid() + --print(string.format("%s (RelayTrap) is forwarding %s down its links...", eName, msg)) + + entity:callHook("link:Broadcast.%UID%", { + message = msg, + flavors = { "ControlDevice", "SwitchLink" }, + caller = entity:uid(), + callerDarkID = payload.callerDarkID + }) +end +_G.DarkScripts["ElevatorButton"] = _G.DarkScripts["RelayTrap"] + +_G.DarkScripts["RequireAllTrap"] = function(entity, payload, dMeta) + local msg = payload.message + local callerDarkID = payload.callerDarkID + + _G.RAT_States = _G.RAT_States or {} + local uid = entity:uid() + _G.RAT_States[uid] = _G.RAT_States[uid] or { inputs = {}, wasOn = false } + local state = _G.RAT_States[uid] + + if callerDarkID then + state.inputs[callerDarkID] = (msg == "TurnOn") + end + + local allOn = true + local incoming = dMeta["incoming_connections"] or {} + for i = 1, #incoming do + local conn = incoming[i] + if conn.flavor == "ControlDevice" or conn.flavor == "SwitchLink" then + if not state.inputs[conn.source_id] then + allOn = false + break + end + end + end + + if allOn and not state.wasOn then + state.wasOn = true + --print(entity .. " (RequireAllTrap) conditions met! Broadcasting TurnOn.") + entity:callHook("link:Broadcast.%UID%", { message = "TurnOn", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid }) + elseif not allOn and state.wasOn then + state.wasOn = false + --print(entity .. " (RequireAllTrap) condition lost! Broadcasting TurnOff.") + entity:callHook("link:Broadcast.%UID%", { message = "TurnOff", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid }) + end +end + +--[[ +_G.DarkScripts["BaseElevator"] = function(entity, payload, dMeta) + local msg = payload.message + local eName = entity or entity:uid() + + print(string.format("%s (Elevator) received command: %s", eName, msg)) + + if msg == "TurnOn" or msg == "TurnOff" then + print(string.format("--> Commanding Lift '%s' to move!", eName)) + end +end + +_G.DarkScripts["Elevator"] = _G.DarkScripts["BaseElevator"] +]] + +ent:addHook("link:Message.%UID%", function(payload) local msg = payload.message local caller = payload.caller + local eName = ent:name() or ent:uid() - if msg == "TurnOn" then - print("Dark I/O [".. ent:name() .. "]: Received TurnOn from " .. tostring(caller)) - elseif msg == "TurnOff" then - print("Dark I/O [".. ent:name() .. "]: Received TurnOff from " .. tostring(caller)) + --print(string.format("Entity %s (Dark ID: %s) received message: %s from UID: %s", eName, darkMeta["id"] or "Unknown", msg, tostring(caller))) + + local scripts = darkMeta["scripts"] or {} + local scriptHandled = false + + for _, script in ipairs(scripts) do + if _G.DarkScripts[script] then + _G.DarkScripts[script](ent, payload, darkMeta) + scriptHandled = true + end + end + + if not scriptHandled then + if eName:lower():match("filter") or eName:lower():match("trigger") then + _G.DarkScripts["RelayTrap"](ent, payload, darkMeta) + end end end) -local connections = metadata["connections"] -if not connections then return end +local function playButtonSound() + if not classTags or classTags == "" then return end + local myDeviceType = string.match(classTags, "DeviceType%s+([^%s,]+)") -ent:addHook("dark:Broadcast.%UID%", function(payload) + local bestMatch = nil + local bestScore = -1 + + for _, schema in ipairs(schemaDb) do + local sTags = schema.tags or "" + local score = 0 + + if myDeviceType then + local paddedTags = sTags .. "," + + if string.find(paddedTags, "DeviceType%s+" .. myDeviceType .. "[,%s]") then + score = score + 10 + elseif string.find(paddedTags, "DeviceType%s+Gen[,%s]") then + score = score + 5 + elseif string.find(sTags, "DeviceType") then + score = score - 10 + end + + if score > 0 then + if string.find(sTags, "Reject") then + score = score + 5 + elseif string.find(sTags, "StateChange") or string.find(sTags, "Activate") then + score = score + 5 + end + end + end + + if score > bestScore then + bestScore = score + bestMatch = schema + end + end + + print("Button sound resolution:", ent, classTags, myDeviceType, bestMatch and bestMatch.name or "None", bestScore) + + if bestMatch and bestScore > 0 and bestMatch.wavs and #bestMatch.wavs > 0 then + local pick = bestMatch.wavs[math.random(#bestMatch.wavs)] + local resolvedUrl = string.resolveURI(pick, metadata["system"]["root"]) + ent:callHook("sound:Emit.%UID%", { + filename = resolvedUrl, spatial = true, volume = 1.0, maxDistance = 15.0, rolloffFactor = 1.0, unique = true + }) + end +end + +local connections = darkMeta["connections"] + +ent:addHook("link:Broadcast.%UID%", function(payload) local msg = payload.message local validFlavors = payload.flavors or { "ControlDevice", "SwitchLink" } + if not connections then + return + end + for i = 1, #connections do local conn = connections[i] local isValid = false @@ -37,12 +169,15 @@ ent:addHook("dark:Broadcast.%UID%", function(payload) if isValid then local targetUID = _G.DarkTargets[conn.target_id] + --print(string.format("Broadcasting %s to Dark ID %s (UID: %s) via %s", msg, conn.target_id, tostring(targetUID), conn.flavor)) + if targetUID then local targetEnt = entities.get(targetUID) if targetEnt and targetEnt:uid() then - targetEnt:callHook("dark:Message." .. targetUID, { + targetEnt:callHook("link:Message." .. targetUID, { message = msg, - caller = ent:uid() + caller = ent:uid(), + callerDarkID = darkMeta["id"] or payload.callerDarkID }) end end @@ -50,13 +185,39 @@ ent:addHook("dark:Broadcast.%UID%", function(payload) end end) + +if darkMeta["frob"] ~= nil then + ent:addHook("entity:Use.%UID%", function(payload) + local frobWorld = darkMeta["frob"]["world"] or 0 + + local kFrobMove = 1 -- 1 << 0 + local kFrobScript = 2 -- 1 << 1 + + if bit.band(frobWorld, kFrobScript) ~= 0 then + print(ent, json.encode( darkMeta )) + playButtonSound() + + ent:callHook("link:Broadcast.%UID%", { + message = "TurnOn", + flavors = { "ControlDevice", "SwitchLink" }, + caller = payload.user, + callerDarkID = darkMeta["id"] + }) + end + + if bit.band(frobWorld, kFrobMove) ~= 0 then + print("Item picked up!") + -- ent:callHook("inventory:Pickup.%UID%", { user = payload.user }) + end + end) +end + ent:addHook("entity:Destroy.%UID%", function() if darkMeta["id"] and _G.DarkTargets[darkMeta["id"]] == ent:uid() then _G.DarkTargets[darkMeta["id"]] = nil end -end) -ent:addHook("entity:Use.%UID%", function(payload) - if payload.user == ent:uid() then return end - ent:callHook("dark:Broadcast.%UID%", { message = "TurnOn" }) + if _G.RAT_States and _G.RAT_States[ent:uid()] then + _G.RAT_States[ent:uid()] = nil + end end) \ No newline at end of file diff --git a/bin/data/entities/scripts/dark/music.lua b/bin/data/entities/scripts/dark/music.lua index 3dedcdcb..8e6dc322 100644 --- a/bin/data/entities/scripts/dark/music.lua +++ b/bin/data/entities/scripts/dark/music.lua @@ -1,3 +1,4 @@ +local ent = ent local scene = entities.currentScene() local metadata = ent:getComponent("Metadata") local activeSongId = metadata["dark"]["mission song"] or "song02" @@ -13,12 +14,9 @@ local currentLoopCount = 0 local initializedPlayer = false if not songData then - print("[Music Manager] ERROR: Active song '" .. tostring(activeSongId) .. "' not found!") return end -print("[Music Manager] Bound Mission Song: " .. activeSongId) - for _, markerData in ipairs(bakedMarkers) do table.insert(musicMarkers, { position = Vector3f(markerData.position[1], markerData.position[2], markerData.position[3]), @@ -27,7 +25,6 @@ for _, markerData in ipairs(bakedMarkers) do }) end -print("[Music Manager] Pre-caching song assets...") for _, section in ipairs(songData.sections or {}) do if section.samples then for _, sampleName in ipairs(section.samples) do @@ -83,19 +80,15 @@ local function playSection(sectionIdx, isQueue) local url = getSampleUrl(section) if url then if isQueue then - print(string.format("[Music Manager] Queuing section: %s", section.id)) ent:callHook("sound:QueueTrack.%UID%", { filename = url, layer = 1 }) else - print(string.format("[Music Manager] Forcing immediate section: %s", section.id)) ent:callHook("sound:PlayTrack.%UID%", { filename = url, layer = 1, loop = false }) - -- Immediately predict and queue the next track to prevent gaps! local nextIdx, nextLoop = predictNextSection(currentSectionIdx, currentLoopCount) if nextIdx then local nextSec = songData.sections[nextIdx + 1] local nextUrl = getSampleUrl(nextSec) if nextUrl then - print(string.format("[Music Manager] Pre-queuing next section: %s", nextSec.id)) ent:callHook("sound:QueueTrack.%UID%", { filename = nextUrl, layer = 1 }) end end @@ -141,16 +134,12 @@ local function processEvent(eventName) end end - print(string.format("[Music Manager] Event '%s' triggered transition to section %d", eventFound.event, targetSection)) - if eventName ~= "" then currentLoopCount = 0 playSection(targetSection, false) else playSection(targetSection, true) end - else - print(string.format("[Music Manager] WARNING: Event '%s' ignored (not valid from section %d)", eventName, currentSectionIdx)) end end diff --git a/bin/data/entities/scripts/dark/trap.lua b/bin/data/entities/scripts/dark/trap.lua index 01577eb4..f7347385 100644 --- a/bin/data/entities/scripts/dark/trap.lua +++ b/bin/data/entities/scripts/dark/trap.lua @@ -1,26 +1,56 @@ +local ent = ent +local scene = entities.currentScene() + local metadata = ent:getComponent("Metadata") local darkMeta = metadata["dark"] or {} +local soundMeta = darkMeta["sound"] or {} +local schemaDb = darkMeta["schema_db"] or {} local connections = darkMeta["connections"] or {} -ent:addHook("dark:Message.%UID%", function(payload) +local isPlaying = false + +local nativeSchema = soundMeta["schema"] or "" + +ent:addHook("link:Message.%UID%", function(payload) local msg = payload.message - if msg == "TurnOn" then + if msg == "TurnOn" and not isPlaying then + local urlToPlay = "" + for _, conn in ipairs(connections) do if conn.flavor == "SoundDescription" and conn.wavs and #conn.wavs > 0 then - local pick = conn.wavs[math.random(#conn.wavs)] - local resolvedUrl = string.resolveURI(pick, metadata["system"]["root"]) - - ent:callHook("sound:Emit.%UID%", { - filename = resolvedUrl, - spatial = true, - streamed = false, - volume = 1.0, - unique = true, - loop = false - }) + urlToPlay = conn.wavs[math.random(#conn.wavs)] break end end + + if urlToPlay == "" and nativeSchema ~= "" then + for _, entry in ipairs(schemaDb) do + if entry.name:lower() == nativeSchema:lower() then + if entry.wavs and #entry.wavs > 0 then + urlToPlay = entry.wavs[math.random(#entry.wavs)] + end + break + end + end + end + + if urlToPlay ~= "" then + local resolvedUrl = string.resolveURI(urlToPlay, metadata["system"]["root"]) + isPlaying = true + + ent:callHook("sound:Emit.%UID%", { + filename = resolvedUrl, + spatial = false, + streamed = true, + volume = 1.0, + unique = true, + loop = false + }) + end + + elseif msg == "TurnOff" and isPlaying then + isPlaying = false + ent:callHook("sound:Stop.%UID%", {}) end end) \ No newline at end of file diff --git a/bin/data/entities/scripts/dark/trigger.lua b/bin/data/entities/scripts/dark/trigger.lua index 2d6d856e..ff1943e7 100644 --- a/bin/data/entities/scripts/dark/trigger.lua +++ b/bin/data/entities/scripts/dark/trigger.lua @@ -24,7 +24,7 @@ ent:bind( "tick", function(self) if not touching[uid] then touching[uid] = true - ent:queueHook("dark:Broadcast.%UID%", { message = "TurnOn" }, 0) + ent:queueHook("link:Broadcast.%UID%", { message = "TurnOn" }, 0) end end end @@ -32,7 +32,7 @@ ent:bind( "tick", function(self) for uid, _ in pairs(touching) do if not currentCollisions[uid] then touching[uid] = nil - ent:queueHook("dark:Broadcast.%UID%", { message = "TurnOff" }, 0) + ent:queueHook("link:Broadcast.%UID%", { message = "TurnOff" }, 0) end end end ) \ No newline at end of file diff --git a/bin/data/entities/scripts/player.lua b/bin/data/entities/scripts/player.lua index a49d9b7a..4050ed72 100644 --- a/bin/data/entities/scripts/player.lua +++ b/bin/data/entities/scripts/player.lua @@ -236,6 +236,8 @@ end ent:addHook( "entity:Use.%UID%", function( payload ) if payload.user ~= ent:uid() then return end + print( "Use:", entities.get( payload.uid ) ) + local validUse = false if heldObject.uid == 0 and payload.depth > 0 then local prop = entities.get( payload.uid ) diff --git a/engine/inc/uf/ext/ttlg/common.h b/engine/inc/uf/ext/ttlg/common.h index 24da1d36..025ec0ba 100644 --- a/engine/inc/uf/ext/ttlg/common.h +++ b/engine/inc/uf/ext/ttlg/common.h @@ -8,30 +8,6 @@ namespace impl { const float darkToMeters = 0.7f; typedef uf::Meshlet_T Meshlet; - // could be dedicated functions in the engine - template - inline bool readStruct(const uf::stl::vector& buffer, uint32_t& offset, T& outValue, size_t readSize = sizeof(T)) { - if (offset + readSize > buffer.size()) return false; - - if ( readSize < sizeof(T) ) std::memset(&outValue, 0, sizeof(T)); - - size_t copySize = std::min(sizeof(T), readSize); - std::memcpy(&outValue, buffer.data() + offset, copySize); - - offset += readSize; - return true; - } - - template - inline bool readArray(const uf::stl::vector& buffer, uint32_t& offset, size_t count, uf::stl::vector& outArray) { - size_t bytes = count * sizeof(T); - if (offset + bytes > buffer.size()) return false; - outArray.resize(count); - std::memcpy(outArray.data(), buffer.data() + offset, bytes); - offset += bytes; - return true; - } - inline float findWrap( float x ) { return -64.0f * std::floor(x / 64.0f); }; diff --git a/engine/inc/uf/utils/audio/audio.h b/engine/inc/uf/utils/audio/audio.h index 0f433bc0..3c5f1bc4 100644 --- a/engine/inc/uf/utils/audio/audio.h +++ b/engine/inc/uf/utils/audio/audio.h @@ -70,6 +70,8 @@ namespace uf { void UF_API rolloff( pod::AudioSource& source, float v ); float UF_API maxDistance( const pod::AudioSource& source ); void UF_API maxDistance( pod::AudioSource& source, float v ); + float UF_API referenceDistance( const pod::AudioSource& source ); + void UF_API referenceDistance( pod::AudioSource& source, float v ); float UF_API distance( const pod::Vector3f& position ); float UF_API occlusion( const pod::Vector3f& position ); diff --git a/engine/inc/uf/utils/math/physics/narrowphase/ray.h b/engine/inc/uf/utils/math/physics/narrowphase/ray.h index 98a8e46f..2e490020 100644 --- a/engine/inc/uf/utils/math/physics/narrowphase/ray.h +++ b/engine/inc/uf/utils/math/physics/narrowphase/ray.h @@ -19,9 +19,9 @@ namespace impl { namespace uf { namespace physics { - pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::PhysicsBody&, float = FLT_MAX ); - pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, float = FLT_MAX ); - pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, const pod::PhysicsBody*, float = FLT_MAX ); + pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::PhysicsBody&, float = FLT_MAX, uint32_t mask = pod::Collider::MASK_PHYSICAL ); + pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, float = FLT_MAX, uint32_t mask = pod::Collider::MASK_PHYSICAL ); + pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, const pod::PhysicsBody*, float = FLT_MAX, uint32_t mask = pod::Collider::MASK_PHYSICAL ); float UF_API occlusion( const pod::Vector3f& to, const pod::Vector3f& from ); uf::stl::string UF_API getRayMaterialName( const pod::RayQuery& query ); diff --git a/engine/inc/uf/utils/math/physics/structs.h b/engine/inc/uf/utils/math/physics/structs.h index e1de76eb..767b3167 100644 --- a/engine/inc/uf/utils/math/physics/structs.h +++ b/engine/inc/uf/utils/math/physics/structs.h @@ -318,24 +318,27 @@ namespace pod { CATEGORY_NONE = 0, CATEGORY_STATIC = 1 << 0, CATEGORY_DYNAMIC = 1 << 1, - CATEGORY_PLAYER = 1 << 2, + CATEGORY_PLAYER = 1 << 2, CATEGORY_NPC = 1 << 3, CATEGORY_TRIGGER = 1 << 4, CATEGORY_PROJECTILE = 1 << 5, + CATEGORY_INTERACTABLE = 1 << 6, CATEGORY_CHARACTER = CATEGORY_PLAYER | CATEGORY_NPC, CATEGORY_ALL = 0xFFFFFFFF }; // what it collides with enum CollisionMask : uint32_t { - MASK_NONE = 0, + MASK_NONE = 0, MASK_STATIC = CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_PROJECTILE, - MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_TRIGGER, - MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE | CATEGORY_TRIGGER, + MASK_DYNAMIC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC | CATEGORY_TRIGGER | CATEGORY_INTERACTABLE, + MASK_PLAYER = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_NPC | CATEGORY_PROJECTILE | CATEGORY_TRIGGER | CATEGORY_INTERACTABLE, MASK_NPC = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_PROJECTILE | CATEGORY_TRIGGER, MASK_TRIGGER = CATEGORY_PLAYER | CATEGORY_NPC, MASK_PROJECTILE = CATEGORY_STATIC | CATEGORY_DYNAMIC | CATEGORY_PLAYER | CATEGORY_NPC, MASK_CHARACTER = MASK_PLAYER | MASK_NPC, - MASK_ALL = 0xFFFFFFFF + MASK_ALL = 0xFFFFFFFF, + + MASK_PHYSICAL = MASK_ALL & ~CATEGORY_TRIGGER, }; pod::ShapeType type; diff --git a/engine/inc/uf/utils/math/vector.h b/engine/inc/uf/utils/math/vector.h index 54c23d20..74a3e8fd 100644 --- a/engine/inc/uf/utils/math/vector.h +++ b/engine/inc/uf/utils/math/vector.h @@ -104,6 +104,7 @@ namespace uf { template /*FORCE_INLINE*/ pod::Vector /*UF_API*/ copy( const pod::Vector& = {}); // creates a copy of a vector (for whatever reason) template /*FORCE_INLINE*/ pod::Vector /*UF_API*/ copy( const T* ); // creates a copy of a vector (for whatever reason) template /*FORCE_INLINE*/ pod::Vector /*UF_API*/ cast( const U& from ); // casts one vector of one type to another (of the same size) + template /*FORCE_INLINE*/ pod::Vector /*UF_API*/ fill( const T ); // // Equality checking template /*FORCE_INLINE*/ bool /*UF_API*/ equals( const T& left, const T& right ); // equality check between two vectors (==) template /*FORCE_INLINE*/ bool /*UF_API*/ notEquals( const T& left, const T& right ); // equality check between two vectors (==) @@ -112,6 +113,14 @@ namespace uf { template /*FORCE_INLINE*/ bool /*UF_API*/ greater( const T& left, const T& right ); // equality check between two vectors (>) template /*FORCE_INLINE*/ bool /*UF_API*/ greaterEquals( const T& left, const T& right ); // equality check between two vectors (>=) + template /*FORCE_INLINE*/ bool /*UF_API*/ equals( const T& left, const typename T::type_t right ); // equality check between a vector's components (==) + template /*FORCE_INLINE*/ bool /*UF_API*/ notEquals( const T& left, const typename T::type_t right ); // equality check between a vector's components (==) + template /*FORCE_INLINE*/ bool /*UF_API*/ less( const T& left, const typename T::type_t right ); // equality check between a vector's components (<) + template /*FORCE_INLINE*/ bool /*UF_API*/ lessEquals( const T& left, const typename T::type_t right ); // equality check between a vector's components (<=) + template /*FORCE_INLINE*/ bool /*UF_API*/ greater( const T& left, const typename T::type_t right ); // equality check between a vector's components (>) + template /*FORCE_INLINE*/ bool /*UF_API*/ greaterEquals( const T& left, const typename T::type_t right ); // equality check between a vector's components (>=) + + template /*FORCE_INLINE*/ bool /*UF_API*/ isValid( const T& v ); // checks if all components are valid (non NaN, inf, etc.) // Basic arithmetic template /*FORCE_INLINE*/ T /*UF_API*/ add( const T& left, const T& right ); // adds two vectors of same type and size together @@ -220,6 +229,12 @@ namespace uf { FORCE_INLINE bool operator<=(const Vector& rhs) const { return uf::vector::lessEquals(*this, rhs); } \ FORCE_INLINE bool operator>(const Vector& rhs) const { return uf::vector::greater(*this, rhs); } \ FORCE_INLINE bool operator>=(const Vector& rhs) const { return uf::vector::greaterEquals(*this, rhs); } \ + FORCE_INLINE bool operator==(const T rhs) const { return uf::vector::equals(*this, rhs); } \ + FORCE_INLINE bool operator!=(const T rhs) const { return uf::vector::notEquals(*this, rhs); } \ + FORCE_INLINE bool operator<(const T rhs) const { return uf::vector::less(*this, rhs); } \ + FORCE_INLINE bool operator<=(const T rhs) const { return uf::vector::lessEquals(*this, rhs); } \ + FORCE_INLINE bool operator>(const T rhs) const { return uf::vector::greater(*this, rhs); } \ + FORCE_INLINE bool operator>=(const T rhs) const { return uf::vector::greaterEquals(*this, rhs); } \ FORCE_INLINE explicit operator bool() const { return uf::vector::notEquals(*this, Vector{}); } \ template FORCE_INLINE operator Vector() const { return uf::vector::cast(*this); } diff --git a/engine/inc/uf/utils/math/vector/pod.inl b/engine/inc/uf/utils/math/vector/pod.inl index 743a58a9..6b85dce8 100644 --- a/engine/inc/uf/utils/math/vector/pod.inl +++ b/engine/inc/uf/utils/math/vector/pod.inl @@ -56,6 +56,14 @@ pod::Vector uf::vector::cast( const U& from ) { to[i] = from[i]; return to; } +template +pod::Vector uf::vector::fill( const T v ) { + pod::Vector res; + FOR_EACH(T::size, { + res[i] = v; + }); + return res; +} template bool uf::vector::equals( const T& left, const T& right ) { #if UF_USE_SIMD @@ -134,6 +142,86 @@ bool uf::vector::greaterEquals( const T& left, const T& right ) { }); return result; } +// +template +bool uf::vector::equals( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::equals( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] == right) ) result = false; + }); + return result; +} +template +bool uf::vector::notEquals( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::notEquals( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] != right) ) result = false; + }); + return result; +} +template +bool uf::vector::less( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::less( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] < right) ) result = false; + }); + return result; +} +template +bool uf::vector::lessEquals( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::lessEquals( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] <= right) ) result = false; + }); + return result; +} +template +bool uf::vector::greater( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::greater( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] > right) ) result = false; + }); + return result; +} +template +bool uf::vector::greaterEquals( const T& left, const typename T::type_t right ) { +#if UF_USE_SIMD + if constexpr ( simd_able_v && T::size == 4 ) { + return uf::simd::all( uf::simd::greaterEquals( left, right ) ); + } +#endif + bool result = true; + FOR_EACH(T::size, { + if ( !(left[i] >= right) ) result = false; + }); + return result; +} +// template bool uf::vector::isValid( const T& v ) { return uf::vector::equals( v, v ); @@ -274,7 +362,7 @@ T uf::vector::divide( const T& vector, typename T::type_t scalar ) { } #endif T res; - float recip = static_cast(1) / scalar; + float recip = 1.0f / static_cast(scalar); FOR_EACH(T::size, { res[i] = vector[i] * recip; }); @@ -505,10 +593,10 @@ T uf::vector::min( const T& left, const T& right ) { return res; } template -T uf::vector::min( const T& vector, typename T::type_t min ) { +T uf::vector::min( const T& vector, typename T::type_t right ) { T res = vector; FOR_EACH(T::size, { - res[i] = std::min( res[i], min ); + res[i] = std::min( res[i], right ); }); return res; } @@ -534,10 +622,10 @@ T uf::vector::max( const T& left, const T& right ) { return res; } template -T uf::vector::max( const T& vector, typename T::type_t max ) { +T uf::vector::max( const T& vector, typename T::type_t right ) { T res = vector; FOR_EACH(T::size, { - res[i] = std::max( res[i], max ); + res[i] = std::max( res[i], right ); }); return res; } @@ -728,8 +816,8 @@ typename T::type_t uf::vector::norm( const T& vector ) { } template T uf::vector::normalize( const T& vector ) { -#if UF_USE_SIMD - if constexpr ( std::is_same_v ) { +#if 0 && UF_USE_SIMD // causes massive frame drops + if constexpr ( std::is_same_v ) { return uf::simd::normalize( vector ); } #endif @@ -745,13 +833,12 @@ T uf::vector::normalize( const T& vector ) { template T uf::vector::clampMagnitude( const T& v, float maxMag ) { - T res = v; - float mag = uf::vector::magnitude( res ); - if ( mag > maxMag ) { - res /= (maxMag / sqrt(mag)); - } - - return res; + T res = v; + float magSq = uf::vector::magnitude( res ); + if ( magSq > (maxMag * maxMag) ) { + res *= (maxMag / sqrt(magSq)); + } + return res; } template diff --git a/engine/inc/uf/utils/memory/reader.h b/engine/inc/uf/utils/memory/reader.h new file mode 100644 index 00000000..b78027e0 --- /dev/null +++ b/engine/inc/uf/utils/memory/reader.h @@ -0,0 +1,81 @@ +#pragma once + +#include "vector.h" + +namespace uf { + namespace stl { + // technically NOT a templated class, but it works with templates + class UF_API reader { + private: + const uf::stl::vector& m_buffer; + uint32_t m_offset; + uint32_t m_endOffset; + bool m_zeroCopy; // if true, returns a pointer to the original buffer in memory + public: + reader( const uf::stl::vector& buffer, uint32_t offset, uint32_t length, bool zeroCopy = true ); + + inline bool eof() const { return m_offset >= m_endOffset; } + inline uint32_t offset() const { return m_offset; } + inline uint32_t remaining() const { return m_endOffset - m_offset; } + inline void skip( size_t bytes ) { m_offset += bytes; } + + template + const T* read( size_t readSize = sizeof(T) ) { + if ( m_offset + readSize > m_endOffset ) return nullptr; + if ( !m_zeroCopy ) { + static thread_local T copy; + memset(©, 0, sizeof(T)); + size_t copySize = std::min(sizeof(T), readSize); + memcpy(©, m_buffer.data() + m_offset, copySize); + m_offset += readSize; + return © + } + + const T* ptr = (const T*)(m_buffer.data() + m_offset); + m_offset += readSize; + return ptr; + } + + template + bool read( size_t count, uf::stl::vector& outArray ) { + size_t bytes = count * sizeof(T); + if ( m_offset + bytes > m_endOffset ) { + bytes = m_endOffset - m_offset; + count = bytes / sizeof(T); + if (count == 0) return false; + bytes = count * sizeof(T); + } + + if ( !m_zeroCopy ) { + outArray.resize(count); + memcpy(outArray.data(), m_buffer.data() + m_offset, bytes); + } else { + outArray.assign( + (const T*)(m_buffer.data() + m_offset), + (const T*)(m_buffer.data() + m_offset + bytes) + ); + } + m_offset += bytes; + return true; + } + + // to-do: #if C++ >= 20 or something + template + std::span view( size_t count ) { + size_t bytes = count * sizeof(T); + if (m_offset + bytes > m_endOffset) return {}; + std::span view((const T*)(m_buffer.data() + m_offset), count); + m_offset += bytes; + return view; + } + + template + const T* peek() const { + if ( m_offset + sizeof(T) > m_endOffset ) return nullptr; + return (const T*)(m_buffer.data() + m_offset); + } + }; + } +} + +template<> const char* uf::stl::reader::read( size_t readSize ); \ No newline at end of file diff --git a/engine/src/engine/ext/audio/emitter/behavior.cpp b/engine/src/engine/ext/audio/emitter/behavior.cpp index 3d43b097..ce1a7089 100644 --- a/engine/src/engine/ext/audio/emitter/behavior.cpp +++ b/engine/src/engine/ext/audio/emitter/behavior.cpp @@ -44,6 +44,7 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) { if ( ext::json::isNull(json["gain"]) ) json["gain"] = metadataJson["audio"]["gain"]; if ( ext::json::isNull(json["rolloffFactor"]) ) json["rolloffFactor"] = metadataJson["audio"]["rolloffFactor"]; if ( ext::json::isNull(json["maxDistance"]) ) json["maxDistance"] = metadataJson["audio"]["maxDistance"]; + if ( ext::json::isNull(json["referenceDistance"]) ) json["referenceDistance"] = metadataJson["audio"]["referenceDistance"]; if ( ext::json::isNull(json["epsilon"]) ) json["epsilon"] = metadataJson["audio"]["epsilon"]; if ( ext::json::isNull(json["loop"]) ) json["loop"] = metadataJson["audio"]["loop"]; if ( ext::json::isNull(json["streamed"]) ) json["streamed"] = metadataJson["audio"]["streamed"]; @@ -68,6 +69,7 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) { if ( json["gain"].is() ) uf::audio::gain(source, json["gain"].as()); if ( json["rolloffFactor"].is() ) uf::audio::rolloff(source, json["rolloffFactor"].as()); if ( json["maxDistance"].is() ) uf::audio::maxDistance(source, json["maxDistance"].as()); + if ( json["referenceDistance"].is() ) uf::audio::referenceDistance(source, json["referenceDistance"].as()); if ( json["spatial"].is() ) source.settings.spatial = json["spatial"].as(); if ( json["loop"].is() ) uf::audio::loop(source, json["loop"].as()); @@ -97,7 +99,6 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) { uf::stl::string filename = payload["filename"].as(); int layer = payload["layer"].as(1); uf::stl::string channelName = "managed_bgm_channel_" + std::to_string(layer); - UF_MSG_DEBUG("filename={}, channelName={}", filename, channelName); if ( emitter.has( channelName ) ) { auto& source = emitter.get(channelName); @@ -159,8 +160,6 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) { metadata.current = payload.uri; track.active = true; - - UF_MSG_DEBUG("Playing: {} (epsilon: {})", metadata.current, track.epsilon); } else { ext::json::Value json = metadataJson["audio"]; json["filename"] = payload.filename; @@ -259,7 +258,6 @@ void ext::AudioEmitterBehavior::tick( uf::Object& self ) { if ( metadata.tracks.count( metadata.current ) > 0 ) { auto& track = metadata.tracks[metadata.current]; if ( track.active && !bgmFound ) { - UF_MSG_DEBUG("BGM source vanished! Firing TrackEnded natively."); track.active = false; bool isIntro = metadata.current == track.intro; diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index 3be684eb..929a0b3b 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -823,6 +823,7 @@ void uf::graph::process( pod::Graph& graph ) { auto& sceneMetadataJson = scene.getComponent(); auto& graphMetadataJson = graph.metadata; auto& graphMetadataValve = graphMetadataJson["valve"]; + auto& graphMetadataDark = graphMetadataJson["dark"]; auto& storage = uf::graph::getStorage( graph ); std::lock_guard lock(*storage.mutex); @@ -1436,6 +1437,22 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) // convert metadata["dark"] into internal values: auto& metadataDark = node.metadata["dark"]; if ( ext::json::isObject( metadataDark ) ) { + bool emitsAudio = false; + + // bind elevator + bool isElevator = false; + if ( ext::json::isArray( metadataDark["scripts"] ) ) { + ext::json::forEach( metadataDark["scripts"], [&]( ext::json::Value& value ){ + auto script = value.as(); + if ( script == "BaseElevator" || script == "Elevator" ) isElevator = true; + }); + } + + if ( metadataDark["class_tags"].is() ) { + emitsAudio = true; + } else if ( ext::json::isObject( metadataDark["sound"] ) ) { + emitsAudio = true; + } // bind io connectivity if ( ext::json::isArray( metadataDark["connections"] ) || metadataDark["id"].is() ) { @@ -1446,27 +1463,28 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) // bind door if ( ext::json::isObject( metadataDark["door"] ) ) { loadJson["assets"].emplace_back("ent://scripts/dark/door.lua"); - loadJson["behaviors"].emplace_back("AudioEmitterBehavior"); + emitsAudio = true; + } - node.metadata["dark"]["schema_db"] = graph.metadata["dark"]["schema_db"]; + if ( isElevator ) { + loadJson["assets"].emplace_back("ent://scripts/dark/elevator.lua"); + emitsAudio = true; } // bind songs if ( ext::json::isObject( metadataDark["songs"] ) ) { loadJson["assets"].emplace_back("ent://scripts/dark/music.lua"); - loadJson["behaviors"].emplace_back("AudioEmitterBehavior"); - } - - // bind ambient - if ( ext::json::isObject( metadataDark["sound"] ) ) { - loadJson["assets"].emplace_back("ent://scripts/dark/ambient.lua"); - loadJson["behaviors"].emplace_back("AudioEmitterBehavior"); + emitsAudio = true; } // bind trap - if ( node.name.find("SoundTrap") != uf::stl::string::npos || node.name.find("Votrap") != uf::stl::string::npos ) { + if ( node.name == "Sound Trap" || node.name == "VO Trap" ) { loadJson["assets"].emplace_back("ent://scripts/dark/trap.lua"); - loadJson["behaviors"].emplace_back("AudioEmitterBehavior"); + emitsAudio = true; + // bind ambient + } else if ( ext::json::isObject( metadataDark["sound"] ) ) { + loadJson["assets"].emplace_back("ent://scripts/dark/ambient.lua"); + emitsAudio = true; } // bind trigger @@ -1474,6 +1492,55 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) if ( ext::json::isObject( physMeta ) && physMeta["category"].as("") == "trigger" ) { loadJson["assets"].emplace_back("ent://scripts/dark/trigger.lua"); } + + // bind schema based on what we need + uf::stl::string classTags = metadataDark["class_tags"].as(""); + uf::stl::string explicitSchema = ""; + if ( metadataDark["sound"].isObject() ) { + explicitSchema = metadataDark["sound"]["schema"].as(""); + } + + if ( !classTags.empty() || !explicitSchema.empty() ) { + auto& localDb = node.metadata["dark"]["schema_db"]; + + uf::stl::vector searchTerms; + auto extractTerm = [&](const uf::stl::string& prefix) { + size_t p = classTags.find(prefix); + if ( p != uf::stl::string::npos ) { + p += prefix.length(); + size_t end = classTags.find_first_of(" ,", p); + if ( end == uf::stl::string::npos ) end = classTags.length(); + uf::stl::string term = classTags.substr(p, end - p); + if ( !term.empty() ) searchTerms.push_back(term); + } + }; + + extractTerm("DeviceType "); + extractTerm("DoorType "); + + ext::json::forEach( graph.metadata["dark"]["schema_db"], [&]( ext::json::Value& sch ){ + uf::stl::string sName = sch["name"].as(""); + uf::stl::string sTags = sch["tags"].as(""); + bool keep = false; + + if ( !explicitSchema.empty() && sName == explicitSchema ) { + keep = true; + } else { + for ( const auto& term : searchTerms ) { + if ( sTags.find(term) != uf::stl::string::npos ) { + keep = true; + break; + } + } + } + + if ( keep ) { + localDb.emplace_back(sch); + } + }); + } + + if ( emitsAudio ) loadJson["behaviors"].emplace_back("AudioEmitterBehavior"); } if ( ext::json::isObject( tag ) ) { @@ -1579,7 +1646,9 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) auto& transform = entity.getComponent>(); { transform = node.transform; - transform.reference = &parent.getComponent>(); + if ( node.metadata["debug"]["parent node transforms"].as(true) ) { + transform.reference = &parent.getComponent>(); + } // override transform if ( tag["transform"]["offset"].as() ) { auto parsed = uf::transform::decode( tag["transform"], pod::Transform<>{} ); diff --git a/engine/src/ext/lua/lua.cpp b/engine/src/ext/lua/lua.cpp index 5c5ddd35..77826790 100644 --- a/engine/src/ext/lua/lua.cpp +++ b/engine/src/ext/lua/lua.cpp @@ -282,11 +282,18 @@ namespace binds { void ext::lua::initialize() { if ( !ext::lua::enabled ) return; + state.open_libraries( + sol::lib::base + ,sol::lib::package + ,sol::lib::table + ,sol::lib::math + ,sol::lib::string + ,sol::lib::bit32 #if UF_USE_LUAJIT - state.open_libraries(sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::math, sol::lib::string, sol::lib::ffi, sol::lib::jit); -#else - state.open_libraries(sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::math, sol::lib::string); + ,sol::lib::ffi + ,sol::lib::jit #endif + ); // load modules for ( auto pair : modules ) { diff --git a/engine/src/ext/lua/usertypes/physics.cpp b/engine/src/ext/lua/usertypes/physics.cpp index bdb20e9e..fe34a89d 100644 --- a/engine/src/ext/lua/usertypes/physics.cpp +++ b/engine/src/ext/lua/usertypes/physics.cpp @@ -44,8 +44,8 @@ namespace binds { return sol::as_table( events ); } - std::tuple rayCast( pod::PhysicsBody& self, const pod::Vector3f& center, const pod::Vector3f& direction ) { - pod::RayQuery query = uf::physics::rayCast( pod::Ray{center, uf::vector::normalize( direction )}, self, uf::vector::norm( direction ) ); + std::tuple rayCast( pod::PhysicsBody& self, const pod::Vector3f& center, const pod::Vector3f& direction, sol::optional mask ) { + pod::RayQuery query = uf::physics::rayCast( pod::Ray{center, uf::vector::normalize( direction )}, self, uf::vector::norm( direction ), mask.value_or(pod::Collider::MASK_PHYSICAL) ); uf::Object* object = query.hit ? query.body->object : NULL; float depth = query.hit ? query.contact.penetration : -1; return std::make_tuple( object, depth ); diff --git a/engine/src/ext/ttlg/bin.cpp b/engine/src/ext/ttlg/bin.cpp index d0fd2b39..c441ae23 100644 --- a/engine/src/ext/ttlg/bin.cpp +++ b/engine/src/ext/ttlg/bin.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace impl { #pragma pack(push, 1) @@ -137,13 +137,14 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { return false; } - uint32_t offset = 0; + uf::stl::reader reader(buffer, 0, buffer.size()); - impl::BinMainHeader mainHeader; - if ( !impl::readStruct( buffer, offset, mainHeader ) ) { + const auto* pMainHeader = reader.read(); + if ( !pMainHeader ) { UF_MSG_ERROR("Failed to read BIN header: {}", filename); return false; } + impl::BinMainHeader mainHeader = *pMainHeader; if ( strncmp(mainHeader.magic, "LGMM", 4) == 0 ) { UF_MSG_ERROR("Attempting to read LGMM file as an LMGD: {}", filename); @@ -155,14 +156,15 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { return false; } - impl::BinHeader header; - if ( !impl::readStruct(buffer, offset, header ) ) { + const auto* pHeader = reader.read(); + if ( !pHeader ) { UF_MSG_ERROR("Failed to parse BIN header: {}", filename); return false; } + impl::BinHeader header = *pHeader; // skip additional information (to-do: parse) - if ( mainHeader.version == 4 ) offset += 12; + if ( mainHeader.version == 4 ) reader.skip(12); if ( header.offset_poly_list == 0 || header.offset_poly_list >= buffer.size() ) { UF_MSG_ERROR("Invalid BIN file (poly list offset out of bounds): {}", filename); @@ -184,10 +186,10 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { // read materials if ( header.num_mats > 0 && header.offset_mats > 0 && header.offset_mats < buffer.size() ) { - uint32_t matOffset = header.offset_mats; + uf::stl::reader matReader(buffer, header.offset_mats, buffer.size() - header.offset_mats); uf::stl::vector binMats; - if ( impl::readArray(buffer, matOffset, header.num_mats, binMats ) ) { + if ( matReader.read(header.num_mats, binMats ) ) { for ( uint32_t i = 0; i < header.num_mats; ++i ) { uf::stl::string matName = uf::stl::string(binMats[i].name); if ( matName.empty() ) { @@ -200,7 +202,6 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { if ( dotPos != uf::stl::string::npos ) matName = matName.substr(0, dotPos); } - auto it = std::find(graph.materials.begin(), graph.materials.end(), matName); int32_t matIndex = (mainHeader.version == 4) ? i : binMats[i].slot_num; @@ -217,29 +218,29 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { // read vertices if ( header.num_verts > 0 && header.offset_verts > 0 && header.offset_verts < buffer.size() ) { - uint32_t offset = header.offset_verts; - impl::readArray( buffer, offset, header.num_verts, vertices ); + uf::stl::reader vertReader(buffer, header.offset_verts, buffer.size() - header.offset_verts); + vertReader.read(header.num_verts, vertices); } // read UVs if ( header.offset_uv > 0 && header.offset_uv < buffer.size() ) { numUVs = (buffer.size() - header.offset_uv) / sizeof(impl::BinUV); } if ( numUVs > 0 ) { - uint32_t offset = header.offset_uv; - impl::readArray( buffer, offset, numUVs, uvs ); + uf::stl::reader uvReader(buffer, header.offset_uv, buffer.size() - header.offset_uv); + uvReader.read(numUVs, uvs); } // read normals if ( header.offset_norms > 0 && header.offset_norms < buffer.size() ) { numNormals = (buffer.size() - header.offset_norms) / sizeof(pod::Vector3f); } if ( numNormals > 0 ) { - uint32_t offset = header.offset_norms; - impl::readArray( buffer, offset, numNormals, normals ); + uf::stl::reader normReader(buffer, header.offset_norms, buffer.size() - header.offset_norms); + normReader.read(numNormals, normals); } // read subobject information if ( header.num_objs > 0 && header.offset_objs > 0 && header.offset_objs < buffer.size() ) { - uint32_t offset = header.offset_objs; - impl::readArray( buffer, offset, header.num_objs, subObjects ); + uf::stl::reader objReader(buffer, header.offset_objs, buffer.size() - header.offset_objs); + objReader.read(header.num_objs, subObjects); } uf::stl::vector transforms( subObjects.size(), uf::matrix::identity() ); @@ -250,43 +251,59 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { // read subobject faces if ( header.offset_nodes > 0 && header.offset_nodes < buffer.size() ) { - uint32_t offset = header.offset_nodes; + uf::stl::reader nodeReader(buffer, header.offset_nodes, buffer.size() - header.offset_nodes); uf::stl::vector faces; - while ( offset < buffer.size() ) { + + while ( !nodeReader.eof() ) { faces.clear(); - uint8_t nodeType = buffer[offset]; + + const auto* pNodeType = nodeReader.peek(); + if (!pNodeType) break; + uint8_t nodeType = *pNodeType; if ( nodeType == 4 ) { subObjectPolys.emplace_back(); - offset += 3; + nodeReader.skip(3); } else if ( nodeType == 3 ) { - offset += 19; + nodeReader.skip(19); } else if ( nodeType == 2 ) { - uint16_t nf1{}, nf2{}; - offset += 17; - impl::readStruct( buffer, offset, nf1 ); - offset += 2; - impl::readStruct( buffer, offset, nf2 ); + nodeReader.skip(17); - if ( impl::readArray( buffer, offset, nf1 + nf2, faces ) && !subObjectPolys.empty() ) { + const auto* pNf1 = nodeReader.read(); + if (!pNf1) break; + uint16_t nf1 = *pNf1; + + nodeReader.skip(2); + const auto* pNf2 = nodeReader.read(); + if (!pNf2) break; + uint16_t nf2 = *pNf2; + + if ( nodeReader.read(nf1 + nf2, faces) && !subObjectPolys.empty() ) { subObjectPolys.back().insert( subObjectPolys.back().end(), faces.begin(), faces.end() ); } } else if (nodeType == 1) { - uint16_t nf1{}, nf2{}; - offset += 17; - impl::readStruct( buffer, offset, nf1 ); - offset += 10; - impl::readStruct( buffer, offset, nf2 ); + nodeReader.skip(17); - if ( impl::readArray( buffer, offset, nf1 + nf2, faces ) && !subObjectPolys.empty() ) { + const auto* pNf1 = nodeReader.read(); + if (!pNf1) break; + uint16_t nf1 = *pNf1; + + nodeReader.skip(10); + const auto* pNf2 = nodeReader.read(); + if (!pNf2) break; + uint16_t nf2 = *pNf2; + + if ( nodeReader.read(nf1 + nf2, faces) && !subObjectPolys.empty() ) { subObjectPolys.back().insert( subObjectPolys.back().end(), faces.begin(), faces.end() ); } } else if (nodeType == 0) { - uint16_t nf{}; - offset += 17; - impl::readStruct( buffer, offset, nf ); + nodeReader.skip(17); - if ( impl::readArray(buffer, offset, nf, faces) && !subObjectPolys.empty() ) { + const auto* pNf = nodeReader.read(); + if (!pNf) break; + uint16_t nf = *pNf; + + if ( nodeReader.read(nf, faces) && !subObjectPolys.empty() ) { subObjectPolys.back().insert(subObjectPolys.back().end(), faces.begin(), faces.end()); } } else { @@ -308,27 +325,26 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { uint32_t absolutePolyOffset = header.offset_poly_list + polyOffset; if (absolutePolyOffset >= buffer.size()) continue; - impl::BinPolyHeader polyHeader; - uint32_t readOffset = absolutePolyOffset; - if (!impl::readStruct(buffer, readOffset, polyHeader)) continue; + uf::stl::reader polyReader(buffer, absolutePolyOffset, buffer.size() - absolutePolyOffset); - uint32_t dataOffset = absolutePolyOffset + 12; + const auto* pPolyHeader = polyReader.read(); + if (!pPolyHeader) continue; + impl::BinPolyHeader polyHeader = *pPolyHeader; uf::stl::vector vertIndices; - impl::readArray(buffer, dataOffset, polyHeader.num_verts, vertIndices); + polyReader.read(polyHeader.num_verts, vertIndices); uf::stl::vector normIndices; - impl::readArray(buffer, dataOffset, polyHeader.num_verts, normIndices); + polyReader.read(polyHeader.num_verts, normIndices); uf::stl::vector uvIndices; bool hasUVs = ((polyHeader.type & 3) == 3); - if (hasUVs) impl::readArray(buffer, dataOffset, polyHeader.num_verts, uvIndices); + if (hasUVs) polyReader.read(polyHeader.num_verts, uvIndices); uint32_t localMatID = 0; if ( mainHeader.version == 4 ) { - uint8_t matByte = 0; - impl::readStruct(buffer, dataOffset, matByte); - localMatID = matByte; + const auto* matByte = polyReader.read(); + if (matByte) localMatID = *matByte; } else { localMatID = polyHeader.data; } @@ -351,7 +367,9 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { for (uint8_t v = 0; v < polyHeader.num_verts; ++v) { auto& vert = meshlet.vertices.emplace_back(); - uint16_t vIdx = vertIndices[v]; + + uint16_t vIdx = 0; + if (v < vertIndices.size()) vIdx = vertIndices[v]; // Bounds safety if ( vIdx < vertices.size() ) { vert.position = impl::convertPos_NewDark( uf::matrix::multiply(transforms[objIdx], vertices[vIdx].position, 1.0f) + offsets[objIdx] ); @@ -366,10 +384,12 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { } // triangle fan => triangles - for ( uint8_t v = 1; v < polyHeader.num_verts - 1; ++v ) { - meshlet.indices.emplace_back(startVertIdx); - meshlet.indices.emplace_back(startVertIdx + v); - meshlet.indices.emplace_back(startVertIdx + v + 1); + if (polyHeader.num_verts >= 3) { + for ( uint8_t v = 1; v < polyHeader.num_verts - 1; ++v ) { + meshlet.indices.emplace_back(startVertIdx); + meshlet.indices.emplace_back(startVertIdx + v); + meshlet.indices.emplace_back(startVertIdx + v + 1); + } } } } @@ -377,12 +397,6 @@ bool ext::ttlg::loadBin( pod::Graph& graph, const uf::stl::string& filename ) { if ( meshlets.empty() ) { if ( header.num_vhots > 0 || header.num_objs > 0 || header.num_polys == 0 ) { UF_MSG_DEBUG("BIN file acts as a dummy node (no polygons, but has VHOTs/Objs): {}", filename); - /* - uf::stl::string meshName = filename; - graph.meshes.emplace_back(meshName); - graph.primitives.emplace_back(meshName); - return true; - */ } else { UF_MSG_WARNING("BIN file contained no valid polygons: {}", filename); } diff --git a/engine/src/ext/ttlg/mis.cpp b/engine/src/ext/ttlg/mis.cpp index 9893e218..b72e89c2 100644 --- a/engine/src/ext/ttlg/mis.cpp +++ b/engine/src/ext/ttlg/mis.cpp @@ -7,6 +7,7 @@ #include #include #include +#include // to-do: split this into subcomponents @@ -21,17 +22,21 @@ namespace impl { uint32_t dead_beef; // 0xEFBEADDE }; - struct DarkDBInvItem { - char name[12]; - uint32_t offset; - uint32_t length; - }; - struct DarkDBChunkHeader { char name[12]; uint32_t version_high; uint32_t version_low; uint32_t zero; + + }; + + struct DarkDBInvItem { + char name[12]; + uint32_t offset; + uint32_t length; + + inline uint32_t start() const { return offset + sizeof(impl::DarkDBChunkHeader); } + inline uint32_t len() const { return length >= sizeof(impl::DarkDBChunkHeader) ? length - sizeof(impl::DarkDBChunkHeader) : 0; } }; struct WRHeader { @@ -180,8 +185,21 @@ namespace impl { float radius; }; + struct PropertyFrobInfo { + uint32_t world_action; + uint32_t inv_action; + uint32_t tool_action; + uint32_t pad; + }; + struct PropertyPhysType { - int32_t type; // 0 = OBB, 1 = Sphere, 2 = SphereHat, 3 = None + enum int32_t { + OBB = 0, + SPHERE = 1, + SPHERE_HAT = 2, + NONE = 3, + }; + int32_t type; int32_t num_submodels; int32_t remove_on_sleep; int32_t special; @@ -216,21 +234,23 @@ namespace impl { pod::Vector3f rot_velocity; }; - constexpr uint32_t SCH_PLAY_RETRIGGER = (1 << 0); - constexpr uint32_t SCH_PAN_POS = (1 << 1); - constexpr uint32_t SCH_PAN_RANGE = (1 << 2); - constexpr uint32_t SCH_NO_REPEAT = (1 << 3); - constexpr uint32_t SCH_NO_CACHE = (1 << 4); - constexpr uint32_t SCH_STREAM = (1 << 5); - constexpr uint32_t SCH_PLAY_ONCE = (1 << 6); - constexpr uint32_t SCH_NO_COMBAT = (1 << 7); - constexpr uint32_t SCH_NET_AMBIENT = (1 << 8); - constexpr uint32_t SCH_LOC_SPATIAL = (1 << 9); constexpr uint8_t SCHEMA_LOOP_POLY = 0x01; - constexpr uint8_t SCHEMA_LOOP_COUNT = 0x02; + constexpr uint8_t SCHEMA_LOOP_COUNT = 0x02; struct PropertySchPlayParams { + enum uint32_t { + SCH_PLAY_RETRIGGER = (1 << 0), + SCH_PAN_POS = (1 << 1), + SCH_PAN_RANGE = (1 << 2), + SCH_NO_REPEAT = (1 << 3), + SCH_NO_CACHE = (1 << 4), + SCH_STREAM = (1 << 5), + SCH_PLAY_ONCE = (1 << 6), + SCH_NO_COMBAT = (1 << 7), + SCH_NET_AMBIENT = (1 << 8), + SCH_LOC_SPATIAL = (1 << 9), + }; uint32_t flags; int32_t volume; int32_t pan; @@ -343,6 +363,45 @@ namespace impl { uf::stl::vector sections; }; + struct DarkRoomPortal { + int32_t portalId; + int32_t index; + impl::WRPlane portalPlane; + uf::stl::vector edgePlanes; + int32_t farRoomId; + int32_t nearRoomId; + pod::Vector3f center; + int32_t farPortalId; + }; + + struct DarkRoom { + int32_t objId; + int16_t roomId; + pod::Vector3f center; + impl::WRPlane planes[6]; + + uf::stl::vector portals; + uf::stl::vector> portalDists; + uf::stl::vector> watchList; + + pod::Vector3f getSize() const { + pod::Vector3f size; + size.x = -(uf::vector::dot(planes[0].normal, center) + planes[0].d); + size.y = -(uf::vector::dot(planes[1].normal, center) + planes[1].d); + size.z = -(uf::vector::dot(planes[2].normal, center) + planes[2].d); + return size; + } + + bool contains( const pod::Vector3f& pt ) const { + for (int i = 0; i < 6; ++i) { + if ( uf::vector::dot(planes[i].normal, pt) + planes[i].d > 0.001f ) { + return false; + } + } + return true; + } + }; + // do not load these uf::stl::unordered_set modelBlacklist = { @@ -368,17 +427,16 @@ namespace impl { uf::stl::unordered_map light; uf::stl::unordered_map rotDoor; uf::stl::unordered_map transDoor; - + uf::stl::unordered_map frobInfo; uf::stl::unordered_map physType; uf::stl::unordered_map physAttr; uf::stl::unordered_map physDims; uf::stl::unordered_map physState; - uf::stl::unordered_map ambient; - uf::stl::unordered_map schPlayParams; uf::stl::unordered_map schLoopParams; uf::stl::unordered_map> schSamps; + uf::stl::unordered_map objSoundName; uf::stl::unordered_map classTag; uf::stl::unordered_map schMsg; uf::stl::unordered_map schAction; @@ -392,6 +450,10 @@ namespace impl { uf::stl::vector links; uf::stl::unordered_map linkFlavorNames; + uf::stl::unordered_map roomNodes; + uf::stl::vector roomEAX; + uf::stl::vector rooms; + uf::stl::unordered_map> palettes; uf::stl::vector textureToMaterialId; pod::Atlas lightmapAtlas; @@ -415,124 +477,84 @@ namespace impl { return ::fmt::format("c_{}_p_{}", cellIdx, polyIdx); } - uf::stl::string getStringFromOffset( const uf::stl::vector& buffer, uint32_t offset, size_t maxScan = 128 ) { - if ( offset >= buffer.size()) return ""; - size_t max_len = std::min(maxScan, buffer.size() - offset); - const char* ptr = (const char*)(buffer.data() + offset); - return uf::stl::string(ptr, strnlen( ptr, max_len )); - } + template + void extractProperty( impl::DarkContext& ctx, const uf::stl::string& name, uf::stl::unordered_map& map, size_t prefixSkip = 0 ) { + auto invName = ::fmt::format("P${}", name).substr(0, 11); + if ( ctx.inventory.count(invName) == 0 ) return; - void parseRelations( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - uint32_t endOffset = item.offset + item.length; + const auto& item = ctx.inventory.at( invName ); + uf::stl::reader reader( ctx.buffer, item.start(), item.len()); - uint16_t flavorId = 1; - while ( offset + 32 <= endOffset ) { - uf::stl::string relName = uf::stl::string((const char*)(buffer.data() + offset)); - if ( !relName.empty() ) ctx.linkFlavorNames[flavorId] = relName; - flavorId++; - offset += 32; + while ( !reader.eof() ) { + const auto* entry = reader.read(); + if ( !entry ) break; + + if ( entry->size <= 0 ) { + reader.skip( entry->size ); + continue; + } + + T copy{}; + size_t len = std::min(entry->size, sizeof(T)); + const void* data = reader.read< char >(len); + if ( data ) { + std::memcpy(©, data, len); + map[entry->objectId] = copy; + } + + if ( entry->size > len ) { + reader.skip( entry->size - len ); + } } } - void parsePartitionedLinks( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - uint32_t endOffset = item.offset + item.length; + template<> + void extractProperty( impl::DarkContext& ctx, const uf::stl::string& propName, uf::stl::unordered_map& map, size_t prefixSkip ) { + auto fullName = ::fmt::format("P${}", propName).substr(0, 11); + if (ctx.inventory.count(fullName) == 0) return; - size_t count = (endOffset - offset) / sizeof(impl::DarkPartitionedLink); + const auto& item = ctx.inventory.at(fullName); - uf::stl::vector chunkLinks; - if ( !impl::readArray( buffer, offset, count, chunkLinks ) ) return; - for ( const auto& l : chunkLinks ) { - ctx.links.push_back({l.src, l.dest, l.flavor}); // to-do: emplace_back + uf::stl::reader reader( ctx.buffer, item.start(), item.len()); + + while ( !reader.eof() ) { + const auto* entry = reader.read(); + if ( !entry ) break; + + if ( entry->objectId == 0 || entry->size <= prefixSkip ) { + reader.skip(entry->size); + continue; + } + reader.skip( prefixSkip ); + size_t len = entry->size - prefixSkip; + const char* data = reader.read< char >(len); + if ( data ) map[entry->objectId] = uf::stl::string(data, strnlen(data, len)); } } - void parseProperty( impl::DarkContext& ctx, const uf::stl::string& propName ) { - auto& buffer = ctx.buffer; - auto& inventory = ctx.inventory; + template<> + void extractProperty( impl::DarkContext& ctx, const uf::stl::string& propName, uf::stl::unordered_map>& map, size_t prefixSkip ) { + auto fullName = ::fmt::format("P${}", propName).substr(0, 11); + if (ctx.inventory.count(fullName) == 0) return; - auto fullName = ::fmt::format( "P${}", propName ).substr(0, 11); - if ( inventory.count( fullName ) == 0 ) { - return; - } + const auto& item = ctx.inventory.at(fullName); + uf::stl::reader reader( ctx.buffer, item.start(), item.len()); - const auto& item = inventory.at(fullName); - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - uint32_t endOffset = item.offset + item.length; + while ( !reader.eof() ) { + const auto* entry = reader.read(); + if ( !entry ) break; - while ( offset < endOffset ) { - PropertyEntry entry; - if ( !impl::readStruct( buffer, offset, entry ) ) break; + if ( entry->objectId == 0 || entry->size <= prefixSkip ) { + reader.skip(entry->size); + continue; + } + reader.skip( prefixSkip ); + size_t len = entry->size - prefixSkip; + const char* data = reader.read< char >(len); - uint32_t next = offset + entry.size; - - // to-do: clean up most of this redundancy - if ( propName == "Position" && entry.objectId >= 0 ) { - PropertyPosition position; - if ( impl::readStruct( buffer, offset, position, entry.size ) ) { - ctx.properties.position[entry.objectId] = position; - } - } else if ( propName == "Light" ) { - PropertyLight light; - if ( impl::readStruct( buffer, offset, light, entry.size ) ) { - ctx.properties.light[entry.objectId] = light; - } - } else if ( propName == "RotDoor" ) { - PropertyDoor p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.rotDoor[entry.objectId] = p; - } - } else if ( propName == "TransDoor" ) { - PropertyDoor p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.transDoor[entry.objectId] = p; - } - } else if ( propName == "Ambient" || propName == "AmbientHacked" ) { - PropertyAmbient sound; - if ( impl::readStruct( buffer, offset, sound, entry.size ) ) { - ctx.properties.ambient[entry.objectId] = sound; - } - } else if ( propName == "PhysType" ) { - PropertyPhysType p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.physType[entry.objectId] = p; - } - } else if ( propName == "PhysType" && entry.size >= 8 ) { - PropertyPhysType p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.physType[entry.objectId] = p; - } - } else if ( propName == "PhysAttr" && entry.size >= 48 ) { - PropertyPhysAttr p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.physAttr[entry.objectId] = p; - } - } else if ( propName == "PhysDims" && entry.size >= 48 ) { - PropertyPhysDims p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.physDims[entry.objectId] = p; - } - } else if ( propName == "PhysState" && entry.size >= 48 ) { - PropertyPhysState p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.physState[entry.objectId] = p; - } - } else if ( propName == "SchPlayParams" && entry.size >= sizeof(PropertySchPlayParams) ) { - PropertySchPlayParams p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.schPlayParams[entry.objectId] = p; - } - } else if ( propName == "SchLoopParams" && entry.size >= sizeof(PropertySchLoopParams) ) { - PropertySchLoopParams p; - if ( impl::readStruct( buffer, offset, p, entry.size ) ) { - ctx.properties.schLoopParams[entry.objectId] = p; - } - } else if ( propName == "Scripts" ) { - const char* cursor = (const char*)(buffer.data() + offset); - size_t remainingBytes = entry.size; + if ( data ) { + const char* cursor = data; + size_t remainingBytes = len; for ( auto s = 0; s < 4; ++s ) { if ( remainingBytes <= 0 || *cursor == '\0' ) break; @@ -540,7 +562,7 @@ namespace impl { auto script = uf::stl::string( cursor ); if ( script.empty() ) break; - ctx.properties.script[entry.objectId].emplace_back(script); + map[entry->objectId].emplace_back(script); size_t step = strnlen(cursor, remainingBytes) + 1; if ( step > remainingBytes ) break; @@ -548,107 +570,240 @@ namespace impl { cursor += step; remainingBytes -= step; } - } else if ( propName == "Class Tag" || propName == "SchMsg" || propName == "SchAction" ) { - if ( entry.size > 4 && entry.objectId != 0 ) { - auto s = impl::getStringFromOffset( buffer, offset + 4, entry.size - 4 ); - if ( propName == "Class Tag" ) ctx.properties.classTag[entry.objectId] = s; - if ( propName == "SchMsg" ) ctx.properties.schMsg[entry.objectId] = s; - if ( propName == "SchAction" ) ctx.properties.schAction[entry.objectId] = s; - } - } /*else if ( propName == "Class Tag" ) { - auto s = impl::getStringFromOffset( buffer, offset, entry.size ); - if ( !s.empty() ) ctx.properties.classTag[entry.objectId] = s; - } else if ( propName == "SchMsg" ) { - auto s = impl::getStringFromOffset( buffer, offset, entry.size ); - if ( !s.empty() ) ctx.properties.schMsg[entry.objectId] = s; - } else if ( propName == "SchAction" ) { - auto s = impl::getStringFromOffset( buffer, offset, entry.size ); - if ( !s.empty() ) ctx.properties.schAction[entry.objectId] = s; - } */ else if ( propName == "ModelName" ) { - auto modelName = impl::getStringFromOffset( buffer, offset, entry.size ); - if ( !modelName.empty() ) ctx.modelNames[entry.objectId] = modelName; - } else if ( propName == "SymName" ) { - if ( entry.size > 4 ) { - auto symName = impl::getStringFromOffset( buffer, offset + 4, entry.size - 4 ); - if ( !symName.empty() ) ctx.archetypes[entry.objectId] = symName; - } } + } + } - offset = next; + void parseRelations( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + uint16_t flavorId = 1; + while ( reader.remaining() >= 32 ) { + const char* relNameData = reader.read< char >(32); + if ( relNameData ) { + uf::stl::string relName(relNameData, strnlen(relNameData, 32)); + if ( !relName.empty() ) ctx.linkFlavorNames[flavorId] = relName; + } + flavorId++; + } + } + + void parsePartitionedLinks( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + size_t count = item.len() / sizeof(impl::DarkPartitionedLink); + uf::stl::vector chunkLinks; + + if ( reader.read(count, chunkLinks) ) { + for ( const auto& l : chunkLinks ) { + ctx.links.push_back({l.src, l.dest, l.flavor}); + } + } + } + + bool parseRoom(uf::stl::reader& reader, DarkRoom& outRoom) { + const auto* pObjId = reader.read(); + const auto* pRoomId = reader.read(); + // reader.skip(2); + const auto* pCenter = reader.read(); + if ( !pObjId || !pRoomId || !pCenter ) return false; + + outRoom.objId = *pObjId; + outRoom.roomId = *pRoomId; + outRoom.center = *pCenter; + + for ( int i = 0; i < 6; ++i ) { + const auto* pPlane = reader.read(); + if (!pPlane) return false; + outRoom.planes[i] = *pPlane; + } + + const auto* numPortals = reader.read(); + if (!numPortals) return false; + + outRoom.portals.resize(*numPortals); + for ( int i = 0; i < *numPortals; ++i ) { + auto& portal = outRoom.portals[i]; + + portal.portalId = *reader.read(); + portal.index = *reader.read(); + portal.portalPlane = *reader.read(); + + int32_t numEdges = *reader.read(); + reader.read(numEdges, portal.edgePlanes); + + portal.farRoomId = *reader.read(); + portal.nearRoomId = *reader.read(); + portal.center = *reader.read(); + portal.farPortalId = *reader.read(); + } + + outRoom.portalDists.resize(*numPortals); + for ( int i = 0; i < *numPortals; ++i ) { + reader.read(*numPortals, outRoom.portalDists[i]); + } + + const auto* numWatches = reader.read(); + if ( !numWatches ) return false; + + outRoom.watchList.resize(*numWatches); + for ( int i = 0; i < *numWatches; ++i ) { + int32_t watchSize = *reader.read(); + reader.read(watchSize, outRoom.watchList[i]); + } + + return true; + } + + void parseRoomEAX( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + const auto* versionMajor = reader.read(); + const auto* versionMinor = reader.read(); + + const auto* size = reader.read(); + if ( !size || *size <= 0 || *size > 10000 ) { + UF_MSG_ERROR("Invalid EAX size"); + return; + } + + reader.read( *size, ctx.roomEAX ); + } + + void parseRooms( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + const auto* roomsOK = reader.read(); + if ( !roomsOK || *roomsOK == 0 ) return; + + const auto* numRooms = reader.read(); + if ( !numRooms ) return; + + for ( int32_t i = 0; i < *numRooms; ++i ) { + impl::DarkRoom room; + if ( impl::parseRoom( reader, room ) ) { + ctx.rooms.push_back( room ); + } else { + if ( i == *numRooms - 1 ) { + //UF_MSG_DEBUG("Room {} truncated at EOF", i); + } else { + UF_MSG_WARNING("Failed to parse room {} of {}", i, *numRooms); + } + break; + } + } + } + + void processRooms( pod::Graph& graph, impl::DarkContext& ctx ) { + for ( const auto& room : ctx.rooms ) { + auto nodeID = graph.nodes.size(); + graph.root.children.emplace_back(nodeID); + auto& node = graph.nodes.emplace_back(); + ctx.roomNodes[room.roomId] = nodeID; + + node.name = ::fmt::format("room_{}", room.roomId); + + node.transform.position = impl::convertPos_NewDark( room.center ); + + pod::Vector3f localExtents = room.getSize(); + + auto& metadata = node.metadata["dark"]; + metadata["id"] = room.objId; + metadata["room_id"] = room.roomId; + metadata["is_room"] = true; + + auto& physMeta = node.metadata["physics"]; + physMeta["type"] = "obb"; + physMeta["category"] = "trigger"; + physMeta["inertia"] = false; + physMeta["mass"] = 0.0f; + + // extents seem wrong...... + pod::Vector3f worldExtents = impl::convertPos_NewDark(localExtents); + physMeta["extent"] = uf::vector::encode( uf::vector::abs(worldExtents) ); + + if ( room.roomId >= 0 && room.roomId < ctx.roomEAX.size() ) { + int32_t eaxType = ctx.roomEAX[room.roomId]; + metadata["sound"]["eax_type"] = eaxType; + } } } void parseEnvSoundTree( - const uf::stl::vector& buffer, uint32_t& offset, + uf::stl::reader& reader, const uf::stl::unordered_map& tagMap, const uf::stl::unordered_map& valueMap, uf::stl::string currentTags, impl::DarkContext& ctx ) { - int32_t dataSize; - if (!impl::readStruct(buffer, offset, dataSize)) return; + const auto* dataSize = reader.read(); + if (!dataSize) return; - for (int i = 0; i < dataSize; ++i) { - sTagDBData data; - if (!impl::readStruct(buffer, offset, data)) return; - ctx.properties.classTag[data.objId] = currentTags; + for (int i = 0; i < *dataSize; ++i) { + const auto* data = reader.read(); + if (!data) return; + ctx.properties.classTag[data->objId] = currentTags; } - int32_t branchSize; - if (!impl::readStruct(buffer, offset, branchSize)) return; + const auto* branchSize = reader.read(); + if (!branchSize) return; - for (int i = 0; i < branchSize; ++i) { - cTagDBKey key; - if (!impl::readStruct(buffer, offset, key)) return; + for (int i = 0; i < *branchSize; ++i) { + const auto* key = reader.read(); + if (!key) return; uf::stl::string newTags = currentTags; if (!newTags.empty()) newTags += ", "; - uf::stl::string tagName = tagMap.count(key.type) ? tagMap.at(key.type) : ::fmt::format("UnknownTag_{}", key.type); + uf::stl::string tagName = tagMap.count(key->type) ? tagMap.at(key->type) : ::fmt::format("UnknownTag_{}", key->type); uf::stl::string valueNames = ""; bool isEnum = false; for (int v = 0; v < 8; ++v) { - if (key.aEnum[v] == 255) isEnum = true; + if (key->aEnum[v] == 255) isEnum = true; } - if (isEnum || valueMap.count(key.aEnum[0])) { + if (isEnum || valueMap.count(key->aEnum[0])) { for (int v = 0; v < 8; ++v) { - if (key.aEnum[v] != 255 && key.aEnum[v] != 0) { + if (key->aEnum[v] != 255 && key->aEnum[v] != 0) { if (!valueNames.empty()) valueNames += "|"; - valueNames += valueMap.count(key.aEnum[v]) ? valueMap.at(key.aEnum[v]) : std::to_string(key.aEnum[v]); + valueNames += valueMap.count(key->aEnum[v]) ? valueMap.at(key->aEnum[v]) : std::to_string(key->aEnum[v]); } } newTags += tagName + " " + valueNames; } else { - newTags += tagName + " [" + std::to_string(key.minVal) + "-" + std::to_string(key.maxVal) + "]"; + newTags += tagName + " [" + std::to_string(key->minVal) + "-" + std::to_string(key->maxVal) + "]"; } - parseEnvSoundTree(buffer, offset, tagMap, valueMap, newTags, ctx); + parseEnvSoundTree(reader, tagMap, valueMap, newTags, ctx); } } void parseSchSamp( impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - uint32_t endOffset = item.offset + item.length; + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); - while ( offset < endOffset ) { - int32_t objId; - if ( !impl::readStruct( buffer, offset, objId ) ) break; + while ( !reader.eof() ) { + const auto* pObjId = reader.read(); + if ( !pObjId ) break; + int32_t objId = *pObjId; - int32_t numSamples; - if ( !impl::readStruct( buffer, offset, numSamples ) ) break; + const auto* pNumSamples = reader.read(); + if ( !pNumSamples ) break; + int32_t numSamples = *pNumSamples; for ( int32_t i = 0; i < numSamples; ++i ) { - int32_t strLen; - if ( !impl::readStruct( buffer, offset, strLen ) ) break; + const auto* pStrLen = reader.read(); + if ( !pStrLen ) break; + int32_t strLen = *pStrLen; - uf::stl::string sampleName = impl::getStringFromOffset( buffer, offset, strLen ); - offset += strLen; + const char* strData = reader.read< char >(strLen); + if ( !strData ) break; - uint8_t freq; - if ( !impl::readStruct( buffer, offset, freq ) ) break; + uf::stl::string sampleName(strData, strnlen(strData, strLen)); + + const auto* pFreq = reader.read(); + if ( !pFreq ) break; + uint8_t freq = *pFreq; ctx.properties.schSamps[objId].emplace_back(PropertySchSamp{ .name = sampleName, @@ -659,23 +814,21 @@ namespace impl { } void parseObjVec( pod::Graph& graph, impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - DarkDBObjVec_Header header; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - if ( !impl::readStruct( buffer, offset, header ) ) return; + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); - if ( header.maxID <= 0 ) return; - ctx.objects.assign( header.maxID, false ); + const auto* header = reader.read(); + if ( !header || header->maxID <= 0 ) return; - const uint8_t* bitmap = buffer.data() + offset; - uint32_t bitArraySizeBytes = item.length - sizeof(impl::DarkDBChunkHeader) - 8; + ctx.objects.assign( header->maxID, false ); - if ( offset + bitArraySizeBytes > buffer.size() ) return; + uint32_t bitArraySizeBytes = item.len() - sizeof(DarkDBObjVec_Header); + const uint8_t* bitmap = reader.read(bitArraySizeBytes); + if ( !bitmap ) return; - for ( int32_t id = header.minID; id < header.maxID; ++id ) { + for ( int32_t id = header->minID; id < header->maxID; ++id ) { if ( id < 0 ) continue; - int32_t startByte = header.minID >> 3; + int32_t startByte = header->minID >> 3; int32_t myByte = id >> 3; int32_t byteIndex = myByte - startByte; int32_t bitOffset = id & 0x07; @@ -687,16 +840,16 @@ namespace impl { } void parseTextures( pod::Graph& graph, impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - uint32_t size = item.length; - DarkDBTXLIST_Header header; - if ( !impl::readStruct( buffer, offset, header ) ) return; + // uf::stl::reader reader(ctx.buffer, item.start(), item.len()); // should be item.length? + uf::stl::reader reader(ctx.buffer, item.start(), item.length); // should be item.length? + + const auto* header = reader.read(); + if ( !header ) return; uf::stl::vector fams; - if ( impl::readArray( buffer, offset, header.fam_count, fams ) ) { - ctx.families.resize( header.fam_count ); - for ( auto i = 0; i < header.fam_count; ++i ) { + if ( reader.read( header->fam_count, fams ) ) { + ctx.families.resize( header->fam_count ); + for ( auto i = 0; i < header->fam_count; ++i ) { ctx.families[i] = uf::stl::string(fams[i].name, strnlen(fams[i].name, 16)); std::transform(ctx.families[i].begin(), ctx.families[i].end(), ctx.families[i].begin(), ::tolower); std::replace(ctx.families[i].begin(), ctx.families[i].end(), '\\', '/'); @@ -704,11 +857,11 @@ namespace impl { } auto& storage = uf::graph::getStorage(graph); - ctx.textureToMaterialId.resize( header.txt_count, -1 ); + ctx.textureToMaterialId.resize( header->txt_count, -1 ); uf::stl::vector texs; - if ( impl::readArray( buffer, offset, header.txt_count, texs ) ) { - for (uint32_t i = 0; i < header.txt_count; ++i) { + if ( reader.read( header->txt_count, texs ) ) { + for (uint32_t i = 0; i < texs.size() /*header->txt_count*/; ++i) { const auto& tex = texs[i]; uf::stl::string texName(tex.name, strnlen(tex.name, 16)); if (texName.empty() || texName == "null") continue; @@ -721,7 +874,6 @@ namespace impl { matName = ctx.families[tex.fam] + "/" + texName; } - // could instead just check against storage.materials.map.count( matName ) auto it = std::find( graph.materials.begin(), graph.materials.end(), matName ); if ( it == graph.materials.end() ) { ctx.textureToMaterialId[i] = graph.materials.size(); @@ -783,80 +935,83 @@ namespace impl { } bool parseSong( const uf::stl::vector& buffer, impl::Song& outSong ) { - uint32_t offset = 0; + uf::stl::reader reader(buffer, 0, buffer.size()); - uint32_t fileVersion; - if ( !impl::readStruct( buffer, offset, fileVersion ) ) return false; - if ( fileVersion != 1) { - UF_MSG_WARNING("Song file version mismatch (expected 1, got {})", fileVersion); + const auto* fileVersion = reader.read(); + if ( !fileVersion ) return false; + if ( *fileVersion != 1) { + UF_MSG_WARNING("Song file version mismatch (expected 1, got {})", *fileVersion); } - impl::sSongInfo songInfo; - if ( !impl::readStruct( buffer, offset, songInfo ) ) return false; - outSong.id = uf::stl::string(songInfo.id, strnlen(songInfo.id, kSONG_MaxStringLen)); + const auto* songInfo = reader.read(); + if ( !songInfo ) return false; + outSong.id = uf::stl::string(songInfo->id, strnlen(songInfo->id, kSONG_MaxStringLen)); - uint32_t numGlobalEvents; - if ( !impl::readStruct( buffer, offset, numGlobalEvents ) ) return false; + const auto* numGlobalEvents = reader.read(); + if ( !numGlobalEvents ) return false; - for ( uint32_t i = 0; i < numGlobalEvents; ++i ) { - impl::sSongEventInfo evtInfo; - if ( !impl::readStruct( buffer, offset, evtInfo ) ) break; + for ( uint32_t i = 0; i < *numGlobalEvents; ++i ) { + const auto* evtInfo = reader.read(); + if ( !evtInfo ) break; + + UF_MSG_DEBUG("Song Global Event: {}, flags: {}", evtInfo->eventString, evtInfo->flags); auto& evt = outSong.globalEvents.emplace_back(); - evt.eventString = uf::stl::string(evtInfo.eventString, strnlen(evtInfo.eventString, kSONG_MaxStringLen)); - evt.flags = evtInfo.flags; + evt.eventString = uf::stl::string(evtInfo->eventString, strnlen(evtInfo->eventString, kSONG_MaxStringLen)); + evt.flags = evtInfo->flags; - uint32_t numGotos; - if ( !impl::readStruct( buffer, offset, numGotos ) ) break; + const auto* numGotos = reader.read(); + if ( !numGotos ) break; - for ( uint32_t j = 0; j < numGotos; ++j ) { - impl::sSongGotoInfo gotoInfo; - if ( !impl::readStruct( buffer, offset, gotoInfo ) ) break; - evt.gotos.push_back({ gotoInfo.sectionIndex, gotoInfo.probability }); + for ( uint32_t j = 0; j < *numGotos; ++j ) { + const auto* gotoInfo = reader.read(); + if ( !gotoInfo ) break; + UF_MSG_DEBUG(" -> Goto section: {}, prob: {}", gotoInfo->sectionIndex, gotoInfo->probability); + evt.gotos.push_back({ gotoInfo->sectionIndex, gotoInfo->probability }); } } - uint32_t numSections; - if ( !impl::readStruct( buffer, offset, numSections ) ) return false; + const auto* numSections = reader.read(); + if ( !numSections ) return false; - for ( uint32_t i = 0; i < numSections; ++i ) { - impl::sSongSectionInfo secInfo; - if ( !impl::readStruct( buffer, offset, secInfo ) ) break; + for ( uint32_t i = 0; i < *numSections; ++i ) { + const auto* secInfo = reader.read(); + if ( !secInfo ) break; auto& sec = outSong.sections.emplace_back(); - sec.id = uf::stl::string(secInfo.id, strnlen(secInfo.id, kSONG_MaxStringLen)); - sec.volume = secInfo.volume; - sec.loopCount = secInfo.loopCount; + sec.id = uf::stl::string(secInfo->id, strnlen(secInfo->id, kSONG_MaxStringLen)); + sec.volume = secInfo->volume; + sec.loopCount = secInfo->loopCount; - uint32_t numSamples; - if ( !impl::readStruct( buffer, offset, numSamples ) ) break; + const auto* numSamples = reader.read(); + if ( !numSamples ) break; - for ( uint32_t j = 0; j < numSamples; ++j ) { - impl::sSongSampleInfo sampInfo; - if ( !impl::readStruct( buffer, offset, sampInfo ) ) break; + for ( uint32_t j = 0; j < *numSamples; ++j ) { + const auto* sampInfo = reader.read(); + if ( !sampInfo ) break; - uf::stl::string sampleName = uf::stl::string(sampInfo.name, strnlen(sampInfo.name, kSONG_MaxStringLen)); + uf::stl::string sampleName = uf::stl::string(sampInfo->name, strnlen(sampInfo->name, kSONG_MaxStringLen)); sec.samples.push_back({ sampleName }); } - uint32_t numSecEvents; - if ( !impl::readStruct( buffer, offset, numSecEvents ) ) break; + const auto* numSecEvents = reader.read(); + if ( !numSecEvents ) break; - for ( uint32_t j = 0; j < numSecEvents; ++j ) { - impl::sSongEventInfo evtInfo; - if ( !impl::readStruct( buffer, offset, evtInfo ) ) break; + for ( uint32_t j = 0; j < *numSecEvents; ++j ) { + const auto* evtInfo = reader.read(); + if ( !evtInfo ) break; auto& evt = sec.events.emplace_back(); - evt.eventString = uf::stl::string(evtInfo.eventString, strnlen(evtInfo.eventString, kSONG_MaxStringLen)); - evt.flags = evtInfo.flags; + evt.eventString = uf::stl::string(evtInfo->eventString, strnlen(evtInfo->eventString, kSONG_MaxStringLen)); + evt.flags = evtInfo->flags; - uint32_t numGotos; - if ( !impl::readStruct( buffer, offset, numGotos ) ) break; + const auto* numGotos = reader.read(); + if ( !numGotos ) break; - for ( uint32_t k = 0; k < numGotos; ++k ) { - impl::sSongGotoInfo gotoInfo; - if ( !impl::readStruct( buffer, offset, gotoInfo ) ) break; - evt.gotos.push_back({ gotoInfo.sectionIndex, gotoInfo.probability }); + for ( uint32_t k = 0; k < *numGotos; ++k ) { + const auto* gotoInfo = reader.read(); + if ( !gotoInfo ) break; + evt.gotos.push_back({ gotoInfo->sectionIndex, gotoInfo->probability }); } } } @@ -952,10 +1107,10 @@ namespace impl { } void parseWorld( pod::Graph& graph, impl::DarkContext& ctx, const impl::DarkDBInvItem& item ) { - auto& buffer = ctx.buffer; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - impl::WRHeader header; - if ( !impl::readStruct( buffer, offset, header ) ) return; + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + const auto* header = reader.read(); + if ( !header ) return; auto& storage = uf::graph::getStorage(graph); uf::stl::string meshName = "worldspawn"; @@ -964,7 +1119,6 @@ namespace impl { auto& mesh = storage.meshes[meshName]; auto& primitives = storage.primitives[meshName]; - //uf::stl::unordered_map, impl::Meshlet> meshlets; uf::stl::unordered_map meshlets; // cell information @@ -980,42 +1134,42 @@ namespace impl { uf::stl::vector animLightList; uf::stl::vector lightIndices; }; - uf::stl::vector cells( header.numCells ); + uf::stl::vector cells( header->numCells ); // fill cell information - for ( uint32_t c = 0; c < header.numCells; ++c ) { + for ( uint32_t c = 0; c < cells.size(); ++c ) { auto& cell = cells[c]; cell.cellIdx = c; - if ( !impl::readStruct( buffer, offset, cell.header ) ) break; + const auto* cellHeader = reader.read(); + if ( !cellHeader ) break; + cell.header = *cellHeader; // read cell information - impl::readArray( buffer, offset, cell.header.numVertices, cell.vertices ); - impl::readArray( buffer, offset, cell.header.numPolygons, cell.polys ); - impl::readArray( buffer, offset, cell.header.numTextured, cell.texInfo ); + reader.read( cell.header.numVertices, cell.vertices ); + reader.read( cell.header.numPolygons, cell.polys ); + reader.read( cell.header.numTextured, cell.texInfo ); // read index count - uint32_t numIndices; - if ( !impl::readStruct( buffer, offset, numIndices ) ) break; - if ( offset + numIndices > buffer.size() ) break; + const auto* numIndices = reader.read(); + if ( !numIndices ) break; + if ( reader.remaining() < *numIndices ) break; // fill indices cell.polyIndices.resize( cell.header.numPolygons ); for ( uint8_t i = 0; i < cell.header.numPolygons; ++i ) { - cell.polyIndices[i].resize( cell.polys[i].count ); - for ( uint8_t j = 0; j < cell.polys[i].count; ++j ) { - cell.polyIndices[i][j] = buffer[offset++]; - } + // The reader can populate the vector directly! + reader.read( cell.polys[i].count, cell.polyIndices[i] ); } // read planes - impl::readArray( buffer, offset, cell.header.numPlanes, cell.planes ); + reader.read( cell.header.numPlanes, cell.planes ); // read animated lights - impl::readArray(buffer, offset, cell.header.numAnimLights, cell.animLightList); + reader.read( cell.header.numAnimLights, cell.animLightList ); // read lightmaps information - impl::readArray(buffer, offset, cell.header.numTextured, cell.lmInfos); + reader.read( cell.header.numTextured, cell.lmInfos ); // read lightmap uint32_t lightPixelSize = 2; // to-do: deduce? @@ -1030,10 +1184,11 @@ namespace impl { uint32_t w = cell.lmInfos[i].lx; uint32_t h = cell.lmInfos[i].ly; uint32_t lmSizeBytes = w * h * lightPixelSize; + uint32_t totalLmBytes = lmSizeBytes * lmCount; - if ( lmSizeBytes > 0 && ( offset + lmSizeBytes * lmCount ) <= buffer.size()) { + if ( lmSizeBytes > 0 && reader.remaining() >= totalLmBytes ) { uf::stl::vector samples; - impl::readArray( buffer, offset, w * h * lmCount, samples ); + reader.read( w * h * lmCount, samples ); for ( int layer = 0; layer < lmCount; ++layer ) { pod::Image image; @@ -1048,7 +1203,7 @@ namespace impl { auto color = pod::Vector3f{ (float)((sample >> 10) & 0x1F), (float)((sample >> 5) & 0x1F), - (float)((sample ) & 0x1F), + (float)((sample ) & 0x1F), } / 31.0f; impl::encodeRGBE( color, &image.pixels[(y * w + x) * 4] ); } @@ -1076,13 +1231,13 @@ namespace impl { } } } else { - offset += ( lmSizeBytes * lmCount ); + reader.skip( totalLmBytes ); } } - uint32_t lightCount; - if ( impl::readStruct( buffer, offset, lightCount ) ) { - impl::readArray( buffer, offset, lightCount, cell.lightIndices ); + const auto* lightCount = reader.read(); + if ( lightCount ) { + reader.read( *lightCount, cell.lightIndices ); } } @@ -1136,7 +1291,7 @@ namespace impl { // prepare meshlet //auto& meshlet = meshlets[std::make_pair(cell.cellIdx, materialID)]; - auto& meshlet = meshlets[(static_cast(cell.cellIdx) << 32) | static_cast(materialID)]; + auto& meshlet = meshlets[((uint64_t)(cell.cellIdx) << 32) | (uint32_t)(materialID)]; meshlet.primitive.instance.materialID = materialID; meshlet.primitive.instance.lightmapID = atlasTextureID; @@ -1227,8 +1382,8 @@ namespace impl { size_t primitiveID = 0; for ( auto& [ meshletKey, meshlet ] : meshlets ) { // auto& [ auxID, matID ] = meshletKey; - uint32_t auxID = static_cast(meshletKey >> 32); - uint32_t matID = static_cast(meshletKey & 0xFFFFFFFF); + uint32_t auxID = (uint32_t)(meshletKey >> 32); + uint32_t matID = (uint32_t)(meshletKey & 0xFFFFFFFF); meshlet.primitive.drawCommand.indices = meshlet.indices.size(); meshlet.primitive.drawCommand.vertices = meshlet.vertices.size(); @@ -1255,23 +1410,20 @@ namespace impl { if ( !ctx.objects[objectID] ) continue; // inactive object if ( ctx.properties.position.count(objectID) == 0 ) continue; // no position bound - auto nodeID = graph.nodes.size(); - graph.root.children.emplace_back(nodeID); - + auto nodeID = graph.nodes.size(); auto& node = graph.nodes.emplace_back(); - auto& metadata = node.metadata["dark"]; // bind name - if ( ctx.customNames.count(objectID) ) { - node.name = uf::stl::string(ctx.customNames.at(objectID).c_str()); - } else { - uf::stl::string symName; - if ( ctx.findInheritedProperty(objectID, ctx.archetypes, symName) ) { - node.name = uf::stl::string(symName.c_str()); - } - if ( node.name.empty()) node.name = ::fmt::format("object #{}", objectID); - } + uf::stl::string archetype = "Unknown"; + ctx.findInheritedProperty(objectID, ctx.archetypes, archetype); + + if ( ctx.customNames.count(objectID) ) { + node.name = uf::stl::string(ctx.customNames.at(objectID).c_str()); + } else { + if ( archetype != "Unknown" ) node.name = uf::stl::string(archetype.c_str()); + if ( node.name.empty()) node.name = ::fmt::format("object #{}", objectID); + } // bind position PropertyPosition position = ctx.properties.position.at(objectID); { @@ -1285,7 +1437,25 @@ namespace impl { node.transform.position = impl::convertPos_NewDark( position.position ); node.transform.orientation = uf::quaternion::multiply(qHeading, uf::quaternion::multiply(qBank, qPitch)); - + } + + // bind parent + size_t parentNodeID = -1; + for ( const auto& room : ctx.rooms ) { + if ( room.contains( position.position ) ) { + if ( ctx.roomNodes.count(room.roomId)) { + parentNodeID = ctx.roomNodes.at(room.roomId); + break; + } + } + } + + if ( parentNodeID != -1 ) { + node.metadata["debug"]["parent node transforms"] = false; + + graph.nodes[parentNodeID].children.emplace_back(nodeID); + } else { + graph.root.children.emplace_back(nodeID); } // bind model @@ -1313,13 +1483,10 @@ namespace impl { // bind door PropertyDoor doorProp; bool isDoor = false; - - if ( ctx.findInheritedProperty( objectID, ctx.properties.rotDoor, doorProp ) ) { + if ( ( isDoor = ctx.findInheritedProperty( objectID, ctx.properties.rotDoor, doorProp ) ) ) { metadata["door"]["is_rotating"] = true; - isDoor = true; - } else if ( ctx.findInheritedProperty( objectID, ctx.properties.transDoor, doorProp ) ) { + } else if ( ( isDoor = ctx.findInheritedProperty( objectID, ctx.properties.transDoor, doorProp ) ) ) { metadata["door"]["is_rotating"] = false; - isDoor = true; } if ( isDoor ) { @@ -1333,10 +1500,15 @@ namespace impl { // bind class tags uf::stl::string classTags; if ( ctx.findInheritedProperty( objectID, ctx.properties.classTag, classTags ) ) { - UF_MSG_DEBUG("objectID={}, name={}, classTags={}", objectID, node.name, classTags); metadata["class_tags"] = classTags; } + // bind object sound name + uf::stl::string objSound; + if ( ctx.findInheritedProperty( objectID, ctx.properties.objSoundName, objSound ) ) { + metadata["sound"]["schema"] = objSound; + } + // bind ambient sound PropertyAmbient ambient; if ( ctx.findInheritedProperty( objectID, ctx.properties.ambient, ambient ) ) { @@ -1369,8 +1541,8 @@ namespace impl { if ( resolved.hasPlayParams ) { metadata["sound"]["schema_volume"] = resolved.playParams.volume; - metadata["sound"]["play_once"] = (resolved.playParams.flags & impl::SCH_PLAY_ONCE) != 0; - metadata["sound"]["stream"] = (resolved.playParams.flags & impl::SCH_STREAM) != 0; + metadata["sound"]["play_once"] = (resolved.playParams.flags & PropertySchPlayParams::SCH_PLAY_ONCE) != 0; + metadata["sound"]["stream"] = (resolved.playParams.flags & PropertySchPlayParams::SCH_STREAM) != 0; } if ( resolved.hasLoopParams ) { @@ -1383,18 +1555,31 @@ namespace impl { // bind script uf::stl::vector scripts; if ( ctx.findInheritedProperty( objectID, ctx.properties.script, scripts ) ) { - for ( const auto& s : scripts ) metadata["scripts"].emplace_back(s); + for ( const auto& s : scripts ) { + metadata["scripts"].emplace_back(s); + } + } + + // bind frobbage + PropertyFrobInfo frob; + bool isFrobbable = false; + if ( ctx.findInheritedProperty(objectID, ctx.properties.frobInfo, frob) ) { + isFrobbable = frob.world_action != 0; + + metadata["frob"]["world"] = frob.world_action; + metadata["frob"]["inv"] = frob.inv_action; + metadata["frob"]["tool"] = frob.tool_action; } // bind physics PropertyPhysType physType; // to-do: optimize this as it cuts my FPS by ~30% with lots of small physics objects - if ( ctx.findInheritedProperty( objectID, ctx.properties.physType, physType ) && physType.type != 3 ) { + if ( ctx.findInheritedProperty( objectID, ctx.properties.physType, physType ) && physType.type != impl::PropertyPhysType::NONE ) { auto& physMeta = node.metadata["physics"]; - if ( physType.type == 0 ) { + if ( physType.type == impl::PropertyPhysType::OBB ) { physMeta["type"] = "obb"; - } else if ( physType.type == 1 || physType.type == 2 ) { + } else if ( physType.type == impl::PropertyPhysType::SPHERE || physType.type == impl::PropertyPhysType::SPHERE_HAT ) { physMeta["type"] = "sphere"; physMeta["radius"] = 0.5f; } else { @@ -1403,64 +1588,52 @@ namespace impl { PropertyPhysAttr physAttr; if ( ctx.findInheritedProperty( objectID, ctx.properties.physAttr, physAttr ) ) { - physMeta["mass"] = physAttr.mass; + physMeta["mass"] = (physType.type == impl::PropertyPhysType::OBB || physAttr.edge_trigger != 0) ? 0.0f : physAttr.mass; physMeta["friction"] = physAttr.base_friction; physMeta["restitution"] = physAttr.elasticity; - - if ( physAttr.gravity == 0.0f ) { - physMeta["gravity"] = uf::vector::encode(pod::Vector3f{0.0f, 0.0f, 0.0f}); - } else { - physMeta["gravity"] = uf::vector::encode(pod::Vector3f{0.0f, -0.0981f * physAttr.gravity, 0.0f}); - } + physMeta["gravity"] = uf::vector::encode( pod::Vector3f{0.0f, -0.0981f * physAttr.gravity, 0.0f} ); if ( physAttr.edge_trigger != 0 ) { physMeta["category"] = "trigger"; physMeta["inertia"] = false; - physMeta["mass"] = 0.0f; - } - if ( physType.type == 0 ) { - physMeta["mass"] = 0.0f; } } PropertyPhysDims physDims; if ( ctx.findInheritedProperty( objectID, ctx.properties.physDims, physDims ) ) { - if ( physType.type == 0 ) { - pod::Vector3f extent = impl::convertPos_NewDark(physDims.size) * 0.5f; - if ( extent.x > 0 && extent.y > 0 && extent.z > 0 ) { - physMeta["extent"] = uf::vector::encode(extent); - } - - pod::Vector3f center = impl::convertPos_NewDark(physDims.offset[0]); - if ( center.x != 0.f || center.y != 0.f || center.z != 0.f ) { - physMeta["center"] = uf::vector::encode(center); - } - } else if ( physType.type == 1 || physType.type == 2 ) { - if ( physDims.radius[0] > 0 ) { - physMeta["radius"] = physDims.radius[0] * impl::darkToMeters; - } + if ( physType.type == impl::PropertyPhysType::OBB ) { + pod::Vector3f center = impl::convertPos_NewDark( physDims.offset[0] ); + pod::Vector3f extent = impl::convertPos_NewDark( uf::vector::abs( physDims.size ) ) * 0.5f; + if ( physAttr.edge_trigger != 0 ) extent += 1.0f; + if ( extent > 0.f ) physMeta["extent"] = uf::vector::encode(extent); + if ( !(center == 0.f) ) physMeta["center"] = uf::vector::encode(center); + } else if ( physType.type == impl::PropertyPhysType::SPHERE || physType.type == impl::PropertyPhysType::SPHERE_HAT ) { pod::Vector3f offset = impl::convertPos_NewDark(physDims.offset[0]); - if ( offset.x != 0.f || offset.y != 0.f || offset.z != 0.f ) { - physMeta["offset"] = uf::vector::encode(offset); - } + if ( physDims.radius[0] > 0.f ) physMeta["radius"] = physDims.radius[0] * impl::darkToMeters; + if ( !(offset == 0.f) ) physMeta["offset"] = uf::vector::encode(offset); } } PropertyPhysState physState; if ( ctx.findInheritedProperty( objectID, ctx.properties.physState, physState ) ) { pod::Vector3f vel = impl::convertPos_NewDark(physState.velocity, 1.0f); - if ( vel.x != 0.f || vel.y != 0.f || vel.z != 0.f ) physMeta["velocity"] = uf::vector::encode(vel); - pod::Vector3f angVel = { physState.rot_velocity.x, physState.rot_velocity.z, physState.rot_velocity.y }; - if ( angVel.x != 0.f || angVel.y != 0.f || angVel.z != 0.f ) physMeta["angularVelocity"] = uf::vector::encode(angVel); + + if ( !(vel == 0.f) ) physMeta["velocity"] = uf::vector::encode(vel); + if ( !(angVel == 0.f) ) physMeta["angularVelocity"] = uf::vector::encode(angVel); } + } else if ( node.mesh != -1 && (isDoor || isFrobbable) ) { + auto& physMeta = node.metadata["physics"]; + physMeta["type"] = "obb"; + physMeta["mass"] = 0.0f; } // fill out metadata { metadata["id"] = objectID; - metadata["cell"] = position.cell; + metadata["cell"] = position.cell; + metadata["archetype"] = archetype; } // bind light (at the end because we insert a new node) @@ -1493,12 +1666,7 @@ namespace impl { } for ( const auto& link : ctx.links ) { - if ( !objectToNode.count(link.sourceId) || !objectToNode.count(link.destId) ) continue; - size_t sourceIdx = objectToNode[link.sourceId]; - size_t destIdx = objectToNode[link.destId]; - - auto& srcNode = graph.nodes[sourceIdx]; - auto& connection = srcNode.metadata["dark"]["connections"].emplace_back(); + if ( !objectToNode.count(link.sourceId) ) continue; uf::stl::string flavor = ""; if ( ctx.linkFlavorNames.count(link.flavor) ) { @@ -1507,19 +1675,38 @@ namespace impl { flavor = ::fmt::format("Unknown_{}", link.flavor); } + size_t sourceIdx = objectToNode[link.sourceId]; + auto& srcNode = graph.nodes[sourceIdx]; + + if ( flavor == "SoundDescription" ) { + auto& connection = srcNode.metadata["dark"]["connections"].emplace_back(); + connection["flavor"] = flavor; + connection["target_id"] = link.destId; + + if ( ctx.schemas.count(link.destId) ) { + const auto& schema = ctx.schemas.at(link.destId); + connection["target_node"] = schema.name; + for (const auto& wav : schema.wavs) { + connection["wavs"].emplace_back(wav.name); + } + } else { + connection["target_node"] = "UnknownSchema"; + } + continue; + } + + if ( !objectToNode.count(link.destId) ) continue; + + size_t destIdx = objectToNode[link.destId]; + auto& connection = srcNode.metadata["dark"]["connections"].emplace_back(); + connection["flavor"] = flavor; connection["target_node"] = graph.nodes[destIdx].name; connection["target_id"] = link.destId; - // bind sound descriptions - if ( flavor == "SoundDescription" ) { - if ( ctx.schemas.count(link.destId) ) { - const auto& schema = ctx.schemas.at(link.destId); - for (const auto& wav : schema.wavs) { - connection["wavs"].emplace_back(wav.name); - } - } - } + auto& inConnection = graph.nodes[destIdx].metadata["dark"]["incoming_connections"].emplace_back(); + inConnection["flavor"] = flavor; + inConnection["source_id"] = link.sourceId; } } @@ -1533,13 +1720,16 @@ namespace impl { graph.root.children.emplace_back( nodeID ); node.name = "Song"; - uf::stl::string missionSong = ""; + uf::stl::string missionSong = ""; if ( ctx.inventory.count("SONGPARAMS") > 0 ) { const auto& item = ctx.inventory.at("SONGPARAMS"); - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - impl::PropertySongParams params; - if ( impl::readStruct( ctx.buffer, offset, params ) ) { - missionSong = uf::stl::string(params.songName, strnlen(params.songName, 32)); + uf::stl::reader reader(ctx.buffer, item.start(), item.len()); + + size_t readLen = std::min(item.len(), 32); + const char* songData = reader.read< char >(readLen); + + if ( songData ) { + missionSong = uf::stl::string(songData, strnlen(songData, readLen)); } } node.metadata["dark"]["mission song"] = missionSong; @@ -1612,22 +1802,23 @@ namespace impl { } } - uf::stl::unordered_map parseNameMap(const uf::stl::vector& buffer, uint32_t& offset) { + uf::stl::unordered_map parseNameMap( uf::stl::reader& reader ) { uf::stl::unordered_map map; - int32_t upperBound, lowerBound, size; - if (!impl::readStruct(buffer, offset, upperBound)) return map; - if (!impl::readStruct(buffer, offset, lowerBound)) return map; - if (!impl::readStruct(buffer, offset, size)) return map; + const auto* upperBound = reader.read(); + const auto* lowerBound = reader.read(); + const auto* size = reader.read(); - for (int i = 0; i < size; ++i) { - char flag; - if (!impl::readStruct(buffer, offset, flag)) break; + if (!upperBound || !lowerBound || !size) return map; - if (flag == '+') { - uf::stl::vector text; - if (!impl::readArray(buffer, offset, 16, text)) break; - map[i + lowerBound] = uf::stl::string(text.data(), strnlen(text.data(), 16)); + for (int i = 0; i < *size; ++i) { + const auto* flag = reader.read< char >(); + if (!flag) break; + + if (*flag == '+') { + uf::stl::vector< char > text; + if (!reader.read(16, text)) break; + map[i + *lowerBound] = uf::stl::string(text.data(), strnlen(text.data(), 16)); } } return map; @@ -1637,39 +1828,39 @@ namespace impl { auto& buffer = ctx.buffer; auto& inventory = ctx.inventory; - uint32_t chunkCount; - uint32_t offset = header.inv_offset; - if ( !impl::readStruct( buffer, offset, chunkCount ) ) return; - for ( uint32_t i = 0; i < chunkCount; ++i ) { - impl::DarkDBInvItem item; - if ( !impl::readStruct( buffer, offset, item ) ) break; - uf::stl::string name = item.name; - inventory[name] = item; + uf::stl::reader reader(buffer, header.inv_offset, buffer.size() - header.inv_offset); + + const auto* chunkCount = reader.read(); + if ( chunkCount ) { + for ( uint32_t i = 0; i < *chunkCount; ++i ) { + const auto* item = reader.read(); + if ( !item ) break; + uf::stl::string name = item->name; + inventory[name] = *item; + } } - // parse strings - impl::parseProperty( ctx, "SymName" ); - impl::parseProperty( ctx, "ModelName" ); - // parse properties - impl::parseProperty( ctx, "Position" ); - impl::parseProperty( ctx, "Light" ); - impl::parseProperty( ctx, "TransDoor" ); - impl::parseProperty( ctx, "RotDoor" ); - impl::parseProperty( ctx, "Ambient" ); - impl::parseProperty( ctx, "AmbientHacked" ); - impl::parseProperty( ctx, "Scripts" ); - // physics - impl::parseProperty( ctx, "PhysType" ); - impl::parseProperty( ctx, "PhysAttr" ); - impl::parseProperty( ctx, "PhysDims" ); - impl::parseProperty( ctx, "PhysState" ); - // schemas - impl::parseProperty( ctx, "SchPlayParams" ); - impl::parseProperty( ctx, "SchLoopParams" ); + impl::extractProperty( ctx, "SymName", ctx.archetypes, 4 ); + impl::extractProperty( ctx, "ModelName", ctx.modelNames ); + impl::extractProperty( ctx, "Class Tag", ctx.properties.classTag, 4 ); + impl::extractProperty( ctx, "SchMsg", ctx.properties.schMsg, 4 ); + impl::extractProperty( ctx, "SchAction", ctx.properties.schAction, 4 ); + impl::extractProperty( ctx, "Scripts", ctx.properties.script ); - impl::parseProperty( ctx, "Class Tag" ); - impl::parseProperty( ctx, "SchMsg" ); - impl::parseProperty( ctx, "SchAction" ); + impl::extractProperty( ctx, "Position", ctx.properties.position ); + impl::extractProperty( ctx, "Light", ctx.properties.light ); + impl::extractProperty( ctx, "TransDoor", ctx.properties.transDoor ); + impl::extractProperty( ctx, "RotDoor", ctx.properties.rotDoor ); + impl::extractProperty( ctx, "FrobInfo", ctx.properties.frobInfo ); + impl::extractProperty( ctx, "Ambient", ctx.properties.ambient ); + impl::extractProperty( ctx, "AmbientHacked", ctx.properties.ambient ); + impl::extractProperty( ctx, "ObjSoundN", ctx.properties.objSoundName, 0 ); + impl::extractProperty( ctx, "PhysType", ctx.properties.physType ); + impl::extractProperty( ctx, "PhysAttr", ctx.properties.physAttr ); + impl::extractProperty( ctx, "PhysDims", ctx.properties.physDims ); + impl::extractProperty( ctx, "PhysState", ctx.properties.physState ); + impl::extractProperty( ctx, "SchPlayParams", ctx.properties.schPlayParams ); + impl::extractProperty( ctx, "SchLoopParams", ctx.properties.schLoopParams ); // parse schsamp if ( inventory.count("SchSamp") > 0 ) { @@ -1690,22 +1881,20 @@ namespace impl { // parse tags if ( inventory.count("Speech_DB") > 0 ) { const auto& item = inventory["Speech_DB"]; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); - auto& buffer = ctx.buffer; + uf::stl::reader speechReader(ctx.buffer, item.start(), item.len()); - auto concepts = parseNameMap(buffer, offset); - auto tags = parseNameMap(buffer, offset); - auto values = parseNameMap(buffer, offset); + auto concepts = impl::parseNameMap(speechReader); + auto tags = impl::parseNameMap(speechReader); + auto values = impl::parseNameMap(speechReader); if ( inventory.count("ENV_SOUND") > 0 ) { - const auto& item = inventory["ENV_SOUND"]; - uint32_t offset = item.offset + sizeof(impl::DarkDBChunkHeader); + const auto& envItem = inventory["ENV_SOUND"]; + uf::stl::reader envReader(ctx.buffer, envItem.start(), envItem.len()); - int32_t numRequiredTags; - if ( impl::readStruct(buffer, offset, numRequiredTags) ) { - offset += numRequiredTags; - - parseEnvSoundTree(buffer, offset, tags, values, "", ctx); + const auto* numRequiredTags = envReader.read(); + if ( numRequiredTags ) { + envReader.skip(*numRequiredTags); + impl::parseEnvSoundTree(envReader, tags, values, "", ctx); } } } @@ -1742,11 +1931,10 @@ namespace impl { return; } - uint32_t offset = 0; - impl::DarkDBHeader header; - if ( !impl::readStruct( buffer, offset, header ) ) return; - - if ( header.dead_beef != 0xEFBEADDE ) { + uf::stl::reader reader(buffer, 0, buffer.size()); + const auto* header = reader.read(); + if ( !header ) return; + if ( header->dead_beef != 0xEFBEADDE ) { UF_MSG_ERROR("Invalid DB in GAM: DEADBEEF not found."); return; } @@ -1754,7 +1942,7 @@ namespace impl { uf::stl::unordered_map inventory; std::swap( ctx.inventory, inventory ); std::swap( ctx.buffer, buffer ); - impl::readInventory( ctx, header ); + impl::readInventory( ctx, *header ); std::swap( ctx.buffer, buffer ); std::swap( ctx.inventory, inventory ); } @@ -1768,21 +1956,20 @@ void ext::ttlg::loadMis( pod::Graph& graph, const uf::stl::string& filename, con return; } - uint32_t offset = 0; - impl::DarkDBHeader header; - if ( !impl::readStruct( buffer, offset, header ) ) { + uf::stl::reader reader(buffer, 0, buffer.size()); + const auto* header = reader.read(); + if ( !header ) { UF_MSG_ERROR("Failed to read DB header: {}", filename); return; } - if ( header.dead_beef != 0xEFBEADDE ) { + if ( header->dead_beef != 0xEFBEADDE ) { UF_MSG_ERROR("Invalid DB: DEADBEEF not found: {}", filename); return; } - if ( header.inv_offset >= buffer.size() ) { + if ( header->inv_offset >= buffer.size() ) { UF_MSG_ERROR("Inventory offset is past EOF: {}", filename); return; } - uf::graph::preprocess( graph, metadata, filename ); auto& storage = uf::graph::getStorage( graph ); @@ -1850,7 +2037,7 @@ void ext::ttlg::loadMis( pod::Graph& graph, const uf::stl::string& filename, con // load initial data impl::loadGam( ctx, /*metadata["game"].as*/("game://Data/SHOCK2.GAM") ); // to-do: deduce path from metadata - impl::readInventory( ctx, header ); + impl::readInventory( ctx, *header ); // mission parsing if ( ctx.inventory.count("TXLIST") > 0 ) { impl::parseTextures( graph, ctx, ctx.inventory["TXLIST"] ); @@ -1859,8 +2046,15 @@ void ext::ttlg::loadMis( pod::Graph& graph, const uf::stl::string& filename, con if ( ctx.inventory.count("WRRGB") > 0 ) { impl::parseWorld( graph, ctx, ctx.inventory["WRRGB"] ); } + if ( ctx.inventory.count("ROOM_DB") > 0 ) { + impl::parseRooms( ctx, ctx.inventory["ROOM_DB"] ); + } + if ( ctx.inventory.count("ROOM_EAX") > 0 ) { + impl::parseRoomEAX( ctx, ctx.inventory["ROOM_EAX"] ); + } if ( ctx.inventory.count("ObjVec") > 0 ) { impl::parseObjVec( graph, ctx, ctx.inventory["ObjVec"] ); + impl::processRooms( graph, ctx ); impl::loadObjects( graph, ctx ); impl::loadMaterials( graph, ctx ); } @@ -1887,6 +2081,7 @@ void ext::ttlg::loadMis( pod::Graph& graph, const uf::stl::string& filename, con if ( filename.starts_with("game://") ) graph.metadata["exporter"]["enabled"] = false; // disable exporting if loaded from a VPK graph.metadata["exporter"]["unwrap"] = false; // do not unwrap UVs for baking (we already have those) graph.metadata["baking"]["enabled"] = false; // disable lightmap baking (we already have those) + graph.metadata["debug"]["parent node transforms"] = false; // disable parenting node transforms uf::graph::postprocess(graph); } \ No newline at end of file diff --git a/engine/src/utils/audio/audio.cpp b/engine/src/utils/audio/audio.cpp index 013e310f..d919cd7b 100644 --- a/engine/src/utils/audio/audio.cpp +++ b/engine/src/utils/audio/audio.cpp @@ -228,6 +228,15 @@ void uf::audio::maxDistance( pod::AudioSource& source, float v ) { source.alSource.set( AL_MAX_DISTANCE, v ); } +float uf::audio::referenceDistance( const pod::AudioSource& source ) { + float v; + source.alSource.get( AL_REFERENCE_DISTANCE, v ); + return v; +} +void uf::audio::referenceDistance( pod::AudioSource& source, float v ) { + source.alSource.set( AL_REFERENCE_DISTANCE, v ); +} + // float uf::audio::distance( const pod::Vector3f& position ) { return uf::vector::distance( ::listener.position, position ); diff --git a/engine/src/utils/math/physics/impl.cpp b/engine/src/utils/math/physics/impl.cpp index ee7edab3..714bf497 100644 --- a/engine/src/utils/math/physics/impl.cpp +++ b/engine/src/utils/math/physics/impl.cpp @@ -323,6 +323,7 @@ void uf::physics::setColliderCategory( pod::PhysicsBody& body, const uf::stl::st if ( c == "NPC" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_NPC ); if ( c == "TRIGGER" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_TRIGGER ); if ( c == "PROJECTILE" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_PROJECTILE ); + if ( c == "INTERACTABLE" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_INTERACTABLE ); if ( c == "CHARACTER" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_CHARACTER ); if ( c == "ALL" ) return uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_ALL ); } @@ -339,6 +340,7 @@ void uf::physics::setColliderMask( pod::PhysicsBody& body, const uf::stl::string if ( m == "TRIGGER" ) return uf::physics::setColliderMask( body, pod::Collider::MASK_TRIGGER ); if ( m == "PROJECTILE" ) return uf::physics::setColliderMask( body, pod::Collider::MASK_PROJECTILE ); if ( m == "CHARACTER" ) return uf::physics::setColliderMask( body, pod::Collider::MASK_CHARACTER ); + if ( m == "PHYSICAL" ) return uf::physics::setColliderMask( body, pod::Collider::MASK_PHYSICAL ); if ( m == "ALL" ) return uf::physics::setColliderMask( body, pod::Collider::MASK_ALL ); } void uf::physics::setGravity( pod::PhysicsBody& body, const pod::Vector3f& gravity ) { diff --git a/engine/src/utils/math/physics/narrowphase/ray.cpp b/engine/src/utils/math/physics/narrowphase/ray.cpp index 7911a070..6b61248b 100644 --- a/engine/src/utils/math/physics/narrowphase/ray.cpp +++ b/engine/src/utils/math/physics/narrowphase/ray.cpp @@ -309,13 +309,13 @@ void impl::drawRay( const pod::Ray& ray, const pod::RayQuery& query ) { uf::debug::addLine( start, end, query.hit ? pod::Vector4f{ 0, 1, 0, 1 } : pod::Vector4f{ 1, 0, 0, 1 } ); } -pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDistance ) { +pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDistance, uint32_t mask ) { return rayCast( ray, body.world ? *body.world : uf::physics::getWorld(), &body, maxDistance ); } -pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world, float maxDistance ) { +pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world, float maxDistance, uint32_t mask ) { return rayCast( ray, world, NULL, maxDistance ); } -pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world, const pod::PhysicsBody* body, float maxDistance ) { +pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world, const pod::PhysicsBody* body, float maxDistance, uint32_t mask ) { pod::RayQuery rayHit; rayHit.invoker = body; rayHit.contact.penetration = maxDistance; @@ -334,6 +334,8 @@ pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world if ( body == b ) continue; + if ( !(b->collider.category & mask) ) continue; + switch ( b->collider.type ) { case pod::ShapeType::AABB: impl::rayAabb( ray, *b, rayHit ); break; case pod::ShapeType::OBB: impl::rayObb( ray, *b, rayHit ); break; @@ -361,7 +363,7 @@ float uf::physics::occlusion( const pod::Vector3f& to, const pod::Vector3f& from ray.origin = from; ray.direction = dir; - pod::RayQuery hit = uf::physics::rayCast( ray, uf::physics::getWorld(), dist ); + pod::RayQuery hit = uf::physics::rayCast( ray, uf::physics::getWorld(), dist, pod::Collider::MASK_PHYSICAL ); if ( hit.contact.penetration >= dist ) return 1.0f; @@ -379,7 +381,7 @@ pod::AcousticBounce uf::physics::acousticReflection( const pod::Vector3f& source primaryRay.origin = sourcePos; primaryRay.direction = rayDirection; - pod::RayQuery firstHit = uf::physics::rayCast( primaryRay, uf::physics::getWorld(), maxDistance ); + pod::RayQuery firstHit = uf::physics::rayCast( primaryRay, uf::physics::getWorld(), maxDistance, pod::Collider::MASK_PHYSICAL ); if ( firstHit.contact.penetration >= maxDistance ) return result; @@ -403,7 +405,7 @@ pod::AcousticBounce uf::physics::acousticReflection( const pod::Vector3f& source secondaryRay.origin = bounceOrigin; secondaryRay.direction = toListener / distToListener; - pod::RayQuery secondHit = uf::physics::rayCast( secondaryRay, uf::physics::getWorld(), distToListener ); + pod::RayQuery secondHit = uf::physics::rayCast( secondaryRay, uf::physics::getWorld(), distToListener, pod::Collider::MASK_PHYSICAL ); if ( secondHit.contact.penetration >= distToListener ) { result.valid = true; diff --git a/engine/src/utils/memory/reader.cpp b/engine/src/utils/memory/reader.cpp new file mode 100644 index 00000000..8449c1f1 --- /dev/null +++ b/engine/src/utils/memory/reader.cpp @@ -0,0 +1,14 @@ +#include + +uf::stl::reader::reader( const uf::stl::vector& buffer, uint32_t offset, uint32_t length, bool zeroCopy ) : m_buffer(buffer), m_offset(offset), m_endOffset(offset + length), m_zeroCopy( zeroCopy ) { + +} + +template<> +const char* uf::stl::reader::read( size_t readSize ) { + if ( m_offset + readSize > m_endOffset ) return nullptr; + + const char* ptr = (const char*)(m_buffer.data() + m_offset); + m_offset += readSize; + return ptr; +} \ No newline at end of file