From e4ad164203e53544a3b0c71d52354ffaca2dfe3c Mon Sep 17 00:00:00 2001 From: ecker Date: Sun, 7 Jun 2026 00:59:19 -0500 Subject: [PATCH] odd bug fixes involving entities that load more than one lua script (nasty hack), issue with audio not streaming from non-disk VFSs, more valve compliance (doors, IO connectivity, ambience_generic) --- bin/data/config.json | 2 +- bin/data/entities/ambient_generic.json | 6 + bin/data/entities/door.json | 4 +- .../entities/gui/mainmenu/scripts/menu.lua | 2 +- bin/data/entities/gui/mainmenu/start.json | 2 +- bin/data/entities/io.json | 3 + bin/data/entities/light.json | 2 +- bin/data/entities/prop.json | 4 +- bin/data/entities/scripts/ambient_generic.lua | 68 +++++ bin/data/entities/scripts/door.lua | 239 ++++++++++++------ bin/data/entities/scripts/io.lua | 143 +++++++++++ .../animal_crossing.json | 0 .../base_sourceengine.json | 19 +- .../gm_construct.json | 0 .../loading.json | 0 .../mds_mcdonalds.json | 41 +++ .../player.json | 0 .../rp_downtown_v2.json | 0 .../scene.json | 0 .../sh2_mcdonalds.json | 0 .../sourceengine.json | 0 .../ss2_medsci1.json | 0 .../test_grid.json | 0 .../sourceengine/base_sourceengine.json | 24 +- .../scenes/sourceengine/mds_mcdonalds.json | 31 +-- bin/data/scenes/vbsp/mds_mcdonalds.json | 29 --- engine/inc/uf/ext/valve/common.h | 2 + engine/inc/uf/utils/audio/audio.h | 2 + engine/inc/uf/utils/audio/metadata.h | 7 + engine/inc/uf/utils/string/ext.h | 1 + engine/src/engine/asset/asset.cpp | 4 +- .../src/engine/ext/audio/emitter/behavior.cpp | 7 +- engine/src/engine/graph/decode.cpp | 2 + engine/src/engine/graph/encode.cpp | 2 + engine/src/engine/graph/graph.cpp | 51 +++- engine/src/engine/object/behaviors/graph.cpp | 6 +- engine/src/engine/object/behaviors/lua.cpp | 25 +- engine/src/engine/object/object.cpp | 4 +- engine/src/ext/audio/vorbis.cpp | 46 +++- engine/src/ext/audio/wav.cpp | 43 +++- engine/src/ext/lua/usertypes/object.cpp | 38 ++- engine/src/ext/lua/usertypes/physics.cpp | 12 + engine/src/ext/valve/bsp.cpp | 142 +++++++---- engine/src/ext/valve/common.cpp | 35 +++ engine/src/ext/valve/mdl.cpp | 17 +- engine/src/ext/valve/vpk.cpp | 11 +- engine/src/ext/valve/vtf.cpp | 2 +- engine/src/ext/vulkan/graphic.cpp | 4 +- engine/src/utils/audio/audio.cpp | 4 + engine/src/utils/debug/draw.cpp | 5 +- engine/src/utils/string/ext.cpp | 4 + 51 files changed, 820 insertions(+), 275 deletions(-) create mode 100644 bin/data/entities/ambient_generic.json create mode 100644 bin/data/entities/io.json create mode 100644 bin/data/entities/scripts/ambient_generic.lua create mode 100644 bin/data/entities/scripts/io.lua rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/animal_crossing.json (100%) rename bin/data/scenes/{vbsp => sourceengine-deprecated}/base_sourceengine.json (70%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/gm_construct.json (100%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/loading.json (100%) create mode 100644 bin/data/scenes/sourceengine-deprecated/mds_mcdonalds.json rename bin/data/scenes/{vbsp => sourceengine-deprecated}/player.json (100%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/rp_downtown_v2.json (100%) rename bin/data/scenes/{vbsp => sourceengine-deprecated}/scene.json (100%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/sh2_mcdonalds.json (100%) rename bin/data/scenes/{vbsp => sourceengine-deprecated}/sourceengine.json (100%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/ss2_medsci1.json (100%) rename bin/data/scenes/{sourceengine => sourceengine-deprecated}/test_grid.json (100%) delete mode 100644 bin/data/scenes/vbsp/mds_mcdonalds.json diff --git a/bin/data/config.json b/bin/data/config.json index 34d97ab1..edefcd60 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -4,7 +4,7 @@ "start": "StartMenu", "matrix": { "reverseInfinite": true }, "lights": { "enabled": true, - "lightmaps": false, + "lightmaps": true, "max": 32, "shadows": { "enabled": true, diff --git a/bin/data/entities/ambient_generic.json b/bin/data/entities/ambient_generic.json new file mode 100644 index 00000000..e7eb7cf0 --- /dev/null +++ b/bin/data/entities/ambient_generic.json @@ -0,0 +1,6 @@ +{ + "assets": ["./scripts/ambient_generic.lua"], + "behaviors": [ + "SoundEmitterBehavior" + ] +} \ No newline at end of file diff --git a/bin/data/entities/door.json b/bin/data/entities/door.json index efbf47eb..7f878e12 100644 --- a/bin/data/entities/door.json +++ b/bin/data/entities/door.json @@ -7,8 +7,8 @@ "physics": { "mass": 0, // "inertia": [0, 0, 0], - // "type": "bounding box" - "type": "mesh" + "type": "bounding box" + // "type": "mesh" } } } \ No newline at end of file diff --git a/bin/data/entities/gui/mainmenu/scripts/menu.lua b/bin/data/entities/gui/mainmenu/scripts/menu.lua index 159371b5..37ae6f3c 100644 --- a/bin/data/entities/gui/mainmenu/scripts/menu.lua +++ b/bin/data/entities/gui/mainmenu/scripts/menu.lua @@ -30,7 +30,7 @@ local timer = Timer.new() if not timer:running() then timer:start() end local playSound = function( key ) - local url = "/ui/" .. key .. ".ogg" + local url = "/ui/" .. key .. ".wav" soundEmitter:callHook("sound:Emit.%UID%", { filename = url }) -- local assetLoader = scene:getComponent("Asset") -- assetLoader:cache(ent:formatHookName("asset:Load.%UID%"), string.resolveURI(url)) diff --git a/bin/data/entities/gui/mainmenu/start.json b/bin/data/entities/gui/mainmenu/start.json index 66aaf5b8..aa1a9c1e 100644 --- a/bin/data/entities/gui/mainmenu/start.json +++ b/bin/data/entities/gui/mainmenu/start.json @@ -16,7 +16,7 @@ "events": { "click": { "name": "game:Scene.Load", - "payload": { "scene": "VBSP" }, + "payload": { "scene": "SourceEngine" }, "delay": 0.125 } }, diff --git a/bin/data/entities/io.json b/bin/data/entities/io.json new file mode 100644 index 00000000..c74e96cb --- /dev/null +++ b/bin/data/entities/io.json @@ -0,0 +1,3 @@ +{ + "assets": ["./scripts/io.lua"] +} \ No newline at end of file diff --git a/bin/data/entities/light.json b/bin/data/entities/light.json index 35efce45..303e3bc2 100644 --- a/bin/data/entities/light.json +++ b/bin/data/entities/light.json @@ -26,7 +26,7 @@ "bias": { "constant": 1.25, "slope": 1.75, - "shader": 0.000005 // 0.000005 //0.000000005 + "shader": 0.00001 // 0.000005 //0.000000005 }, "radius": [0.5, 0], "resolution": 1024, diff --git a/bin/data/entities/prop.json b/bin/data/entities/prop.json index 0872c67c..b62c1abb 100644 --- a/bin/data/entities/prop.json +++ b/bin/data/entities/prop.json @@ -8,8 +8,8 @@ "physics": { "mass": 0, // "inertia": false, - // "type": "bounding box" - "type": "mesh" + "type": "bounding box" + // "type": "mesh" // "type": "hull" } } diff --git a/bin/data/entities/scripts/ambient_generic.lua b/bin/data/entities/scripts/ambient_generic.lua new file mode 100644 index 00000000..276fc129 --- /dev/null +++ b/bin/data/entities/scripts/ambient_generic.lua @@ -0,0 +1,68 @@ +local ent = ent +local metadata = ent:getComponent("Metadata") +local metadataVale = metadata["valve"] or {} + +local soundFile = metadataVale["message"] or "" +local flags = metadataVale["spawnflags"] or 0 + +local volume = tonumber(metadataVale["health"]) or 10.0 +volume = volume / 10.0 + +local playEverywhere = (math.floor(flags / 1) % 2) ~= 0 +local startSilent = (math.floor(flags / 16) % 2) ~= 0 +local isNotLooped = (math.floor(flags / 32) % 2) ~= 0 + +local isPlaying = false + +local function playSound() + if isPlaying or soundFile == "" then return end + isPlaying = true + + local url = "valve://sound/" .. soundFile + + local payload = { + filename = string.resolveURI(url, metadata["system"]["root"]), + spatial = not playEverywhere, + streamed = true, + volume = volume, + unique = true + } + payload["wants loops"] = not isNotLooped + ent:callHook("sound:Emit.%UID%", payload) +end + +local function stopSound() + if not isPlaying or soundFile == "" then return end + isPlaying = false + + local url = "valve://sound/" .. soundFile + ent:callHook("sound:Stop.%UID%", { + filename = string.resolveURI(url, metadata["system"]["root"]) + }) +end + +ent:addHook("io:Input.%UID%", function(payload) + local input = payload.input + + if input == "PlaySound" then + playSound() + elseif input == "StopSound" then + stopSound() + elseif input == "ToggleSound" then + if isPlaying then + stopSound() + else + playSound() + end + elseif input == "Volume" then + local newVol = tonumber(payload.parameter) + if newVol then + volume = newVol / 10.0 + -- to-do: update volume of currently playing sound + end + end +end) + +if not startSilent then + playSound() +end \ No newline at end of file diff --git a/bin/data/entities/scripts/door.lua b/bin/data/entities/scripts/door.lua index 24f33109..10ecc404 100644 --- a/bin/data/entities/scripts/door.lua +++ b/bin/data/entities/scripts/door.lua @@ -4,27 +4,69 @@ local controller = entities.controller() local timer = Timer.new() if not timer:running() then - timer:start(); + timer:start() end -local polarity = 1 local state = 0 -local targetAlpha = 1.57 -local alpha = 0 -local target = Vector3f(0,0,0) +local currentDistance = 0 +local polarity = 1 + local transform = ent:getComponent("Transform") local physicsBody = ent:getComponent("PhysicsBody") local metadata = ent:getComponent("Metadata") +local metadataDoor = metadata["door"] or {} -local speed = metadata["speed"] or 1.0 -local normal = Vector3f(0,0,-1) -if metadata["normal"] ~= nil then - local sign = -1 - if metadata["angle"] < 0 then sign = 1 end - normal = Vector3f( metadata["normal"][1] * sign, metadata["normal"][2] * sign, metadata["normal"][3] * sign ):normalize() +local speed = metadataDoor["speed"] or 100.0 +local wait = metadataDoor["wait"] or 4.0 +local isRotating = (metadataDoor["axis"] ~= nil) + +local targetDistance = 0 +local moveDir = Vector3f(0, 0, 0) +local rotAxis = Vector3f(0, 1, 0) +local flags = metadataDoor["spawnflags"] or 0 + +local isToggle = (math.floor(flags / 32) % 2) ~= 0 +if isToggle then + wait = -1 +end + +if isRotating then + local ax = metadataDoor["axis"] + rotAxis = Vector3f(ax[1], ax[2], ax[3]):normalize() + + local dist = metadataDoor["distance"] or 90.0 + + local isReverse = (math.floor(flags / 2) % 2) ~= 0 + if isReverse then + polarity = -1 + end + + polarity = polarity * -1 + + if dist < 0 then + polarity = polarity * -1 + dist = math.abs(dist) + end + + targetDistance = math.rad(dist) + speed = math.rad(speed) +else + local dir = metadataDoor["direction"] + if dir then moveDir = Vector3f(dir[1], dir[2], dir[3]):normalize() end + + local lip = metadataDoor["lip"] or 8.0 + + if physicsBody:initialized() then + local obb = OBB(physicsBody:bounds()) + local size = obb.extent * 2.0 + + local travelSize = math.abs(moveDir:dot(size)) + targetDistance = travelSize - lip + else + targetDistance = 96.0 - lip + end end --- local soundEmitter = ent:loadChild("/sound.json",true) local soundEmitter = ent local playSound = function( key, loop ) @@ -32,92 +74,141 @@ local playSound = function( key, loop ) local url = "/door/" .. key .. ".ogg" soundEmitter:queueHook("sound:Emit.%UID%", { filename = string.resolveURI(url, metadata["system"]["root"]), - spatial = true, - streamed = true, - volume = "sfx", - loop = loop + spatial = true, streamed = true, volume = "sfx", loop = loop }, 0) end -local stopSound = function( key ) - local url = "/door/" .. key .. ".ogg" - soundEmitter:queueHook("sound:Stop.%UID%", { - filename = string.resolveURI(url, metadata["system"]["root"]) - }, 0) -end -local playSoundscape = function( key ) - local url = "/soundscape/" .. key .. ".ogg" - soundEmitter:queueHook("sound:Emit.%UID%", { - filename = string.resolveURI(url, metadata["system"]["root"]), - spatial = false, - volume = "sfx", - loop = true, - streamed = true - }, 0) -end -local stopSoundscape = function( key ) - local url = "/soundscape/" .. key .. ".ogg" - soundEmitter:queueHook("sound:Stop.%UID%", { - filename = string.resolveURI(url, metadata["system"]["root"]) - }, 0) + +local function toggleDoor( payload ) + if state == 0 or state == 3 then + state = 1 + playSound("default_move") + + if isRotating and payload.uid ~= nil then + local isOneWay = (math.floor(flags / 16) % 2) ~= 0 + + if not isOneWay then + local user = entities.get( payload.user ) + if user and physicsBody:initialized() then + local userPos = user:getComponent("Transform").position + + local obb = OBB(physicsBody:bounds()) + local doorCenter = obb.center + local hingePos = transform.position + + local doorBlade = doorCenter - hingePos + local doorNormal = rotAxis:cross(doorBlade):normalize() + + local delta = userPos - doorCenter + + if doorNormal:dot(delta) > 0 then + polarity = 1 + else + polarity = -1 + end + + local baseDist = metadataDoor["distance"] or 90.0 + if baseDist < 0 then + polarity = polarity * -1 + end + end + end + end + elseif state == 2 then + state = 3 + playSound("default_move") + end end + -- on tick ent:bind( "tick", function(self) - rot = nil + local deltaMove = 0 + local step = time.delta() * speed + if state == 1 then - alpha = alpha + time.delta() * speed - rot = Quaternion.axisAngle( Vector3f(0, 1, 0), time.delta() * speed * polarity ) + deltaMove = step + currentDistance = currentDistance + step - if alpha > targetAlpha then + if currentDistance >= targetDistance then + deltaMove = deltaMove - (currentDistance - targetDistance) + currentDistance = targetDistance state = 2 - alpha = targetAlpha + timer:reset() playSound("default_stop") + + ent:queueHook("io:FireOutput.%UID%", { output = "OnOpen" }, 0) end + elseif state == 3 then + deltaMove = -step + currentDistance = currentDistance - step - end - if state == 3 then - alpha = alpha - time.delta() * speed - rot = Quaternion.axisAngle( Vector3f(0, 1, 0), time.delta() * speed * -polarity ) - - if alpha < 0 then + if currentDistance <= 0 then + deltaMove = deltaMove - currentDistance + currentDistance = 0 state = 0 - alpha = 0 playSound("default_stop") end + elseif state == 2 and wait >= 0 then + if timer:elapsed() >= wait then + state = 3 + playSound("default_move") + end end - if state > 0 and rot ~= nil then - if physicsBody:initialized() then - physicsBody:applyRotation( rot ) + if deltaMove ~= 0 then + local finalMove = deltaMove * polarity + + if isRotating then + local rot = Quaternion.axisAngle(rotAxis, finalMove) + if physicsBody:initialized() then + physicsBody:applyRotation(rot) + else + transform:rotate(rot) + end else - transform:rotate( rot ) + local vec = moveDir * finalMove + if physicsBody:initialized() then + print("Moving by: ", finalMove, " Axis: ", moveDir.x, moveDir.y, moveDir.z) + physicsBody:setVelocity(moveDir * (finalMove / time.delta())) + else + transform.position = transform.position + vec + end + end + else + if not isRotating and physicsBody:initialized() then + physicsBody:setVelocity(Vector3f(0,0,0)) end end end ) + -- on use ent:addHook( "entity:Use.%UID%", function( payload ) if payload.user == ent:uid() then return end --- if timer:elapsed() <= 0.125 then return end --- timer:reset() + local useOpens = (math.floor(flags / 256) % 2) ~= 0 - print("Processing use: " .. ent:name() .. " | " .. payload["depth"] ) + if useOpens then + toggleDoor( payload ) - if state == 0 or state == 3 then - state = 1 - playSound("default_move") - if payload.uid ~= nil then - local user = entities.get( payload.user ) - local userTransform = user:getComponent("Transform") - local delta = transform.position - userTransform.position - local side = normal:dot(delta) - if side > 0 then - polarity = -1 - elseif side < 0 then - polarity = 1 - end - end - elseif state == 2 --[[or state == 1]] then - state = 3 - playSound("default_move") + ent:queueHook("io:FireOutput.%UID%", { output = "OnUse" }, 0) + else + playSound("default_locked") end -end ) \ No newline at end of file +end ) + +ent:addHook("io:Input.%UID%", function( payload ) + local input = payload.input + print(ent:name() .. " received I/O Input: " .. input) + + local mockPayload = { + user = payload.caller, + uid = ent:uid() + } + + if input == "Open" and (state == 0 or state == 3) then + toggleDoor(mockPayload) + elseif input == "Close" and (state == 2 or state == 1) then + toggleDoor(mockPayload) + elseif input == "Toggle" or input == "Use" then + toggleDoor(mockPayload) + end +end) \ No newline at end of file diff --git a/bin/data/entities/scripts/io.lua b/bin/data/entities/scripts/io.lua new file mode 100644 index 00000000..76aa545b --- /dev/null +++ b/bin/data/entities/scripts/io.lua @@ -0,0 +1,143 @@ +local ent = ent +local scene = entities.currentScene() +local metadata = ent:getComponent("Metadata") + +-- assign targets +_G.IOTargets = _G.IOTargets or {} + +local metadataValve = metadata["valve"] or {} +if metadataValve["targetname"] then + local tname = metadataValve["targetname"] + if not _G.IOTargets[tname] then + _G.IOTargets[tname] = {} + end + + local uid = ent:uid() + local exists = false + for _, id in ipairs(_G.IOTargets[tname]) do + if id == uid then exists = true break end + end + if not exists then + table.insert(_G.IOTargets[tname], uid) + end +end + +ent:addHook("io:Input.%UID%", function(payload) + local input = payload.input + local param = payload.parameter + + -- kill + if input == "Kill" or input == "KillHierarchy" then + entities.destroy(ent) + -- relays + elseif input == "FireUser1" then + ent:callHook("io:FireOutput.%UID%", { output = "OnUser1" }) + elseif input == "FireUser2" then + ent:callHook("io:FireOutput.%UID%", { output = "OnUser2" }) + elseif input == "FireUser3" then + ent:callHook("io:FireOutput.%UID%", { output = "OnUser3" }) + elseif input == "FireUser4" then + ent:callHook("io:FireOutput.%UID%", { output = "OnUser4" }) + -- colors + elseif input == "Alpha" then + local alphaVal = tonumber(param) / 255.0 + if alphaVal then + -- to-do: apply to instance / material + end + elseif input == "Color" then + local r, g, b = param:match("(%d+) (%d+) (%d+)") + -- to-do: apply to instance / material + else + --print("I/O [(".. ent:uid() ..") " .. ent:name() .. "]: Received Input '" .. input .. "' with param '" .. tostring(param) .. "'") + end +end ) + +-- no connections defined, bail early +local connections = metadata["connections"] +if not connections then return end + +-- define connections +local timer = Timer.new() +if not timer:running() then + timer:start() +end +local timesFired = {} +for i = 1, #connections do + timesFired[i] = 0 +end + +local pendingOutputs = {} + +ent:bind("tick", function(self) + local currentTime = timer:elapsed() + + for i = #pendingOutputs, 1, -1 do + local job = pendingOutputs[i] + if currentTime >= job.fireTime then + + local targetUIDs = _G.IOTargets[job.target] + if targetUIDs then + for _, targetUID in ipairs(targetUIDs) do + local targetEnt = entities.get(targetUID) + if targetEnt:uid() then + targetEnt:callHook("io:Input." .. targetUID, { + input = job.input, + parameter = job.parameter, + caller = ent:uid() + }) + end + end + else + print("I/O Warning: Targetname '" .. job.target .. "' not found!") + end + + table.remove(pendingOutputs, i) + end + end +end) + +ent:addHook("io:FireOutput.%UID%", function(payload) + local outputName = payload.output + + for i = 1, #connections do + local conn = connections[i] + + if conn.output == outputName then + local limit = conn.times or -1 + + if limit == -1 or timesFired[i] < limit then + timesFired[i] = timesFired[i] + 1 + + local delay = conn.delay or 0.0 + table.insert(pendingOutputs, { + fireTime = timer:elapsed() + delay, + target = conn.target, + input = conn.input, + parameter = conn.parameter + }) + end + end + end +end) + +ent:addHook("entity:Destroy.%UID%", function() + if metadataValve["targetname"] then + local tname = metadataValve["targetname"] + if _G.IOTargets[tname] then + for i, id in ipairs(_G.IOTargets[tname]) do + if id == ent:uid() then + table.remove(_G.IOTargets[tname], i) + break + end + end + end + end +end) + +ent:addHook("entity:Use.%UID%", function(payload) + if payload.user == ent:uid() then return end + + ent:callHook("io:FireOutput.%UID%", { output = "OnPlayerUse" }) + ent:callHook("io:FireOutput.%UID%", { output = "OnUse" }) + ent:callHook("io:FireOutput.%UID%", { output = "OnPressed" }) +end) \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/animal_crossing.json b/bin/data/scenes/sourceengine-deprecated/animal_crossing.json similarity index 100% rename from bin/data/scenes/sourceengine/animal_crossing.json rename to bin/data/scenes/sourceengine-deprecated/animal_crossing.json diff --git a/bin/data/scenes/vbsp/base_sourceengine.json b/bin/data/scenes/sourceengine-deprecated/base_sourceengine.json similarity index 70% rename from bin/data/scenes/vbsp/base_sourceengine.json rename to bin/data/scenes/sourceengine-deprecated/base_sourceengine.json index eea42986..90cb35f2 100644 --- a/bin/data/scenes/vbsp/base_sourceengine.json +++ b/bin/data/scenes/sourceengine-deprecated/base_sourceengine.json @@ -11,16 +11,27 @@ // exact matches "worldspawn": { "physics": { "type": "mesh", "static": true, "mass": 0 }, - "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true } + "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, + "unwrap mesh": true }, - "info_player_start": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } }, - + "light_environment": { "ignore": false, "light": { + // "color": [0.1, 0.1, 0.1], + "power": 1000, + "global": true, + "bias": { + "constant": 0, + "slope": 5, + "shader": 0.000025 + }, + "radius": [0.9999999, 0], + "resolution": 2048 + } }, // "/^light_[^e]/": { "ignore": true }, // regexp matches // "/^worldspawn_/": { "physics": { "type": "mesh", "static": true } }, "/^func_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, - // "/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, + "/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, "/^prop_static/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, "/^prop_dynamic/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, "/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" } }, diff --git a/bin/data/scenes/sourceengine/gm_construct.json b/bin/data/scenes/sourceengine-deprecated/gm_construct.json similarity index 100% rename from bin/data/scenes/sourceengine/gm_construct.json rename to bin/data/scenes/sourceengine-deprecated/gm_construct.json diff --git a/bin/data/scenes/sourceengine/loading.json b/bin/data/scenes/sourceengine-deprecated/loading.json similarity index 100% rename from bin/data/scenes/sourceengine/loading.json rename to bin/data/scenes/sourceengine-deprecated/loading.json diff --git a/bin/data/scenes/sourceengine-deprecated/mds_mcdonalds.json b/bin/data/scenes/sourceengine-deprecated/mds_mcdonalds.json new file mode 100644 index 00000000..2d85cb0d --- /dev/null +++ b/bin/data/scenes/sourceengine-deprecated/mds_mcdonalds.json @@ -0,0 +1,41 @@ +{ + "import": "./base_sourceengine.json", + "assets": [ + // { "filename": "./models/mds_mcdonalds.glb" } + { "filename": "./models/mds_mcdonalds/graph.json" } + // ,{ "filename": "/ball.json", "delay": 1 } + // ,{ "filename": "/ragdoll.json", "delay": 1 } + // ,{ "filename": "/craeture.json", "delay": 2.0 } + // ,{ "filename": "/cesiumMan.json", "delay": 2.0 } + ], + "metadata": { + "graph": { + "bgm": "./audio/soundscape/sh2_ambience.wav", + "tags": { + // exact matches + "func_door_rotating_5473": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, + "func_door_rotating_5509": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, + "func_door_rotating_5568": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, + "func_door_rotating_5584": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, + + // "prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } }, + + // regex matches + "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } }, + + "/^tools\\/toolsnodraw/": { "material": { + "base": [ 1.0, 1.0, 1.0, 1.0 ], + "emissive": [ 0.0, 0.0, 0.0, 0.0 ], + "fMetallic": 0.0, + "fRoughness": 0.10000000149011612, + "fOcclusion": 1.0, + "fAlphaCutoff": 0.5, + "iAlbedo": 27, + "modeAlpha": 1 + } }, + "/alpha_mtl/": { "material": { "modeAlpha": 1 } }, + "/offwndwb/": { "material": { "modeAlpha": 1 } } + } + } + } +} \ No newline at end of file diff --git a/bin/data/scenes/vbsp/player.json b/bin/data/scenes/sourceengine-deprecated/player.json similarity index 100% rename from bin/data/scenes/vbsp/player.json rename to bin/data/scenes/sourceengine-deprecated/player.json diff --git a/bin/data/scenes/sourceengine/rp_downtown_v2.json b/bin/data/scenes/sourceengine-deprecated/rp_downtown_v2.json similarity index 100% rename from bin/data/scenes/sourceengine/rp_downtown_v2.json rename to bin/data/scenes/sourceengine-deprecated/rp_downtown_v2.json diff --git a/bin/data/scenes/vbsp/scene.json b/bin/data/scenes/sourceengine-deprecated/scene.json similarity index 100% rename from bin/data/scenes/vbsp/scene.json rename to bin/data/scenes/sourceengine-deprecated/scene.json diff --git a/bin/data/scenes/sourceengine/sh2_mcdonalds.json b/bin/data/scenes/sourceengine-deprecated/sh2_mcdonalds.json similarity index 100% rename from bin/data/scenes/sourceengine/sh2_mcdonalds.json rename to bin/data/scenes/sourceengine-deprecated/sh2_mcdonalds.json diff --git a/bin/data/scenes/vbsp/sourceengine.json b/bin/data/scenes/sourceengine-deprecated/sourceengine.json similarity index 100% rename from bin/data/scenes/vbsp/sourceengine.json rename to bin/data/scenes/sourceengine-deprecated/sourceengine.json diff --git a/bin/data/scenes/sourceengine/ss2_medsci1.json b/bin/data/scenes/sourceengine-deprecated/ss2_medsci1.json similarity index 100% rename from bin/data/scenes/sourceengine/ss2_medsci1.json rename to bin/data/scenes/sourceengine-deprecated/ss2_medsci1.json diff --git a/bin/data/scenes/sourceengine/test_grid.json b/bin/data/scenes/sourceengine-deprecated/test_grid.json similarity index 100% rename from bin/data/scenes/sourceengine/test_grid.json rename to bin/data/scenes/sourceengine-deprecated/test_grid.json diff --git a/bin/data/scenes/sourceengine/base_sourceengine.json b/bin/data/scenes/sourceengine/base_sourceengine.json index a3f71328..46ba51bf 100644 --- a/bin/data/scenes/sourceengine/base_sourceengine.json +++ b/bin/data/scenes/sourceengine/base_sourceengine.json @@ -14,13 +14,7 @@ "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, "unwrap mesh": true }, - "worldspawn_skybox": { - "physics": { "type": "mesh", "static": true, "mass": 0 }, - "grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true }, - "unwrap mesh": true - }, - "info_player_spawn": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } }, - "light_environment": { "ignore": false, "light": { + /*"light_environment": { "ignore": false, "light": { // "color": [0.1, 0.1, 0.1], "power": 1000, "global": true, @@ -31,15 +25,17 @@ }, "radius": [0.9999999, 0], "resolution": 2048 - } }, - // "/^light_[^e]/": { "ignore": true }, + } },*/ + + // automatically handled + // "/^func_door/": { "action": "load", "payload": { "import": "/door.json" } }, + // "/^prop_door/": { "action": "load", "payload": { "import": "/door.json" } }, + + "/ambient_generic/": { "action": "load", "payload": { "import": "/ambient_generic.json" } }, // regexp matches - // "/^worldspawn_/": { "physics": { "type": "mesh", "static": true } }, - "/^func_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, - "/^prop_door_/": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [-1,0,0] } } }, - "/^prop_static/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, - "/^prop_dynamic/": { /*"action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } }*/ }, + "/^prop_static/": { "action": "load", "payload": { "import": "/prop.json" } }, + "/^prop_dynamic/": { "action": "load", "payload": { "import": "/prop.json" } }, "/^func_physbox/": { "action": "load", "payload": { "import": "/prop.json" } }, "/^prop_physics/": { "action": "load", "payload": { "import": "/prop.json" } }, diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index 2d85cb0d..a42a865b 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -1,40 +1,15 @@ { "import": "./base_sourceengine.json", "assets": [ - // { "filename": "./models/mds_mcdonalds.glb" } + // { "filename": "./models/mds_mcdonalds.bsp" } { "filename": "./models/mds_mcdonalds/graph.json" } - // ,{ "filename": "/ball.json", "delay": 1 } - // ,{ "filename": "/ragdoll.json", "delay": 1 } - // ,{ "filename": "/craeture.json", "delay": 2.0 } - // ,{ "filename": "/cesiumMan.json", "delay": 2.0 } ], "metadata": { "graph": { - "bgm": "./audio/soundscape/sh2_ambience.wav", + // "bgm": "./audio/soundscape/sh2_ambience.wav", + // realistically shouldn't ever need to define additional tags for BSPs "tags": { - // exact matches - "func_door_rotating_5473": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, - "func_door_rotating_5509": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, - "func_door_rotating_5568": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, - "func_door_rotating_5584": { "action": "load", "payload": { "import": "/door.json", "metadata": { "angle":-1.570795, "normal": [1,0,0] } } }, - - // "prop_physics_override_5813": { "action": "load", "payload": { "import": "/physics_prop.json" } }, - - // regex matches - "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } }, - "/^tools\\/toolsnodraw/": { "material": { - "base": [ 1.0, 1.0, 1.0, 1.0 ], - "emissive": [ 0.0, 0.0, 0.0, 0.0 ], - "fMetallic": 0.0, - "fRoughness": 0.10000000149011612, - "fOcclusion": 1.0, - "fAlphaCutoff": 0.5, - "iAlbedo": 27, - "modeAlpha": 1 - } }, - "/alpha_mtl/": { "material": { "modeAlpha": 1 } }, - "/offwndwb/": { "material": { "modeAlpha": 1 } } } } } diff --git a/bin/data/scenes/vbsp/mds_mcdonalds.json b/bin/data/scenes/vbsp/mds_mcdonalds.json deleted file mode 100644 index 6c06a1ed..00000000 --- a/bin/data/scenes/vbsp/mds_mcdonalds.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "import": "./base_sourceengine.json", - "assets": [ - { "filename": "./models/mds_mcdonalds.bsp" } - // { "filename": "./models/mds_mcdonalds/graph.json" } - ], - "metadata": { - "graph": { - // "bgm": "./audio/soundscape/sh2_ambience.wav", - "tags": { - // regex matches - "/^prop_physics_[^o]/": { "action": "load", "payload": { "import": "/prop.json" } }, - - "/^tools\\/toolsnodraw/": { "material": { - "base": [ 1.0, 1.0, 1.0, 1.0 ], - "emissive": [ 0.0, 0.0, 0.0, 0.0 ], - "fMetallic": 0.0, - "fRoughness": 0.10000000149011612, - "fOcclusion": 1.0, - "fAlphaCutoff": 0.5, - "iAlbedo": 27, - "modeAlpha": 1 - } }, - "/alpha_mtl/": { "material": { "modeAlpha": 1 } }, - "/offwndwb/": { "material": { "modeAlpha": 1 } } - } - } - } -} \ No newline at end of file diff --git a/engine/inc/uf/ext/valve/common.h b/engine/inc/uf/ext/valve/common.h index 7ae33c57..efbf8c08 100644 --- a/engine/inc/uf/ext/valve/common.h +++ b/engine/inc/uf/ext/valve/common.h @@ -21,6 +21,8 @@ namespace impl { return pod::Vector3f{ -vertex.y, vertex.z, vertex.x } * scale; } + ext::json::Value processValue( const uf::stl::string& v ); uf::stl::string readString( std::ifstream& file ); bool parseKeyValue( const uf::stl::string& line, uf::stl::string& key, uf::stl::string& value ); + pod::Material& addMaterial( pod::Graph& graph, const uf::stl::string& name ); } \ No newline at end of file diff --git a/engine/inc/uf/utils/audio/audio.h b/engine/inc/uf/utils/audio/audio.h index 4476c4c5..eafe8633 100644 --- a/engine/inc/uf/utils/audio/audio.h +++ b/engine/inc/uf/utils/audio/audio.h @@ -94,6 +94,8 @@ namespace uf { const uf::stl::string& getFilename() const; float getDuration() const; + + bool hasLoops() const; }; namespace audio { extern uf::Audio null; diff --git a/engine/inc/uf/utils/audio/metadata.h b/engine/inc/uf/utils/audio/metadata.h index 4d34a56b..0c5d3d0d 100644 --- a/engine/inc/uf/utils/audio/metadata.h +++ b/engine/inc/uf/utils/audio/metadata.h @@ -47,7 +47,14 @@ namespace uf { uf::Timer<> timer; float elapsed = 0; + + struct { + bool has = false; + uint32_t start = 0; + uint32_t end = 0; + } loop; } info; + struct { bool streamed = true; bool loop = false; diff --git a/engine/inc/uf/utils/string/ext.h b/engine/inc/uf/utils/string/ext.h index 2fbedcc1..fcc5f0f3 100644 --- a/engine/inc/uf/utils/string/ext.h +++ b/engine/inc/uf/utils/string/ext.h @@ -24,6 +24,7 @@ namespace uf { uf::stl::string UF_API rtrim( const uf::stl::string& ); uf::stl::string UF_API trim( const uf::stl::string& ); uf::stl::vector UF_API split( const uf::stl::string&, const uf::stl::string& ); + uf::stl::vector UF_API split( const uf::stl::string&, char ); uf::stl::string UF_API si( double value, const uf::stl::string& unit, size_t precision = 3 ); bool UF_API contains( const uf::stl::string&, const uf::stl::string& ); uf::stl::vector UF_API cStrings( const uf::stl::vector& ); diff --git a/engine/src/engine/asset/asset.cpp b/engine/src/engine/asset/asset.cpp index 23398e99..e63108b1 100644 --- a/engine/src/engine/asset/asset.cpp +++ b/engine/src/engine/asset/asset.cpp @@ -275,7 +275,7 @@ bool uf::asset::has( const uf::asset::Payload& payload ) { } void uf::asset::remove( const uf::stl::string& url ) { if ( !uf::asset::has( url ) ) return; - auto& userdata = uf::asset::map[url]; + auto userdata = std::move(uf::asset::map[url]); #if UF_COMPONENT_POINTERED_USERDATA if ( userdata.data ) uf::pointeredUserdata::destroy( userdata ); #else @@ -287,7 +287,7 @@ uf::asset::userdata_t& uf::asset::get( const uf::stl::string& url ) { return uf::asset::map[url]; } uf::asset::userdata_t uf::asset::release( const uf::stl::string& url ) { - auto userdata = uf::asset::get( url ); + auto userdata = std::move(uf::asset::get( url )); uf::asset::map.erase( url ); return userdata; } diff --git a/engine/src/engine/ext/audio/emitter/behavior.cpp b/engine/src/engine/ext/audio/emitter/behavior.cpp index 62d53d63..f5b9ac87 100644 --- a/engine/src/engine/ext/audio/emitter/behavior.cpp +++ b/engine/src/engine/ext/audio/emitter/behavior.cpp @@ -28,7 +28,7 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { for ( auto& audio : sounds ) { if ( audio.getFilename() != filename ) continue; audio.stop(); - audio.destroy(); + // audio.destroy(); } }); this->addHook( "sound:Emit.%UID%", [&]( pod::PCM& waveform ){ @@ -61,8 +61,11 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { if ( json["gain"].is() ) audio.setGain(json["gain"].as()); if ( json["rolloffFactor"].is() ) audio.setRolloffFactor(json["rolloffFactor"].as()); if ( json["maxDistance"].is() ) audio.setMaxDistance(json["maxDistance"].as()); - if ( json["loop"].is() ) audio.loop(json["loop"].as()); if ( json["spatial"].is() ) audio.setSpatial(json["spatial"].as()); + if ( json["loop"].is() ) audio.loop(json["loop"].as()); + if ( json["wants loop"].is() ) { + audio.loop(json["wants loop"].as(true) && audio.hasLoops()); + } float volume = 1.0f; if ( json["volume"].is() ) volume = json["volume"].as(); diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index d9a41e4c..880ace7e 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -96,6 +96,7 @@ namespace { material.indexEmissive = json["iEmissive"].as(material.indexEmissive); material.indexOcclusion = json["iOcclusion"].as(material.indexOcclusion); material.indexMetallicRoughness = json["iMetallicRoughness"].as(material.indexMetallicRoughness); + material.modeCull = json["modeCull"].as(material.modeCull); material.modeAlpha = json["modeAlpha"].as(material.modeAlpha); return material; @@ -166,6 +167,7 @@ namespace { instance.materialID = json["materialID"].as( instance.materialID ); instance.primitiveID = json["primitiveID"].as( instance.primitiveID ); instance.meshID = json["meshID"].as( instance.meshID ); + instance.lightmapID = json["lightmapID"].as( instance.lightmapID ); instance.auxID = json["auxID"].as( instance.auxID ); instance.objectID = json["objectID"].as( instance.objectID ); instance.bounds.min = uf::vector::decode( json["bounds"]["min"], instance.bounds.min ); diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index 57b39ec6..cd7a8da6 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -58,6 +58,7 @@ namespace { if ( material.indexEmissive >= 0 ) json["iEmissive"] = material.indexEmissive; if ( material.indexOcclusion >= 0 ) json["iOcclusion"] = material.indexOcclusion; if ( material.indexMetallicRoughness >= 0 ) json["iMetallicRoughness"] = material.indexMetallicRoughness; + json["modeCull"] = material.modeCull; json["modeAlpha"] = material.modeAlpha; return json; } @@ -115,6 +116,7 @@ namespace { json["materialID"] = instance.materialID; json["primitiveID"] = instance.primitiveID; json["meshID"] = instance.meshID; + json["lightmapID"] = instance.lightmapID; json["auxID"] = instance.auxID; json["objectID"] = instance.objectID; diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index f6c7e83d..ef9f93bf 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -768,7 +768,7 @@ void uf::graph::process( pod::Graph& graph ) { // root entity should already be bound, but just in case if ( !graph.root.entity ) { graph.root.entity = new uf::Object; - UF_MSG_DEBUG("binding root: {}", (void*) graph.root.entity); + // UF_MSG_DEBUG("binding root: {}", (void*) graph.root.entity); } // copy lighting settings from graph @@ -952,8 +952,7 @@ void uf::graph::process( pod::Graph& graph ) { image.clear(); #endif } - } - + } // process nodes UF_DEBUG_TIMER_MULTITRACE("Processing nodes"); @@ -964,6 +963,29 @@ void uf::graph::process( pod::Graph& graph ) { if ( node.entity ) UF_DEBUG_TIMER_MULTITRACE("Processed node: {}", node.name); } + // spawn player if not already spawned + if ( auto* player = scene.findByName("Player"); !player ) { + int32_t spawnID = -1; + uf::stl::vector spawns; + for ( auto nodeID = 0; nodeID < graph.nodes.size(); ++nodeID ) { + auto& node = graph.nodes[nodeID]; + if ( node.name == "info_player_start" ) { + spawnID = nodeID; + } else if ( node.name.starts_with("info_player_") ) { + spawns.emplace_back(nodeID); + } + } + if ( spawnID == -1 && !spawns.empty() ) spawnID = uf::stl::random( spawns ); + if ( spawnID != -1 ) { + auto& node = graph.nodes[spawnID]; + auto& child = /*graph.root.entity->*/node.entity->loadChild( "./player.json", false ); // to-do: do not hardcode this + auto& childTransform = child.getComponent>(); + + auto flatten = uf::transform::flatten( node.transform ); + childTransform = flatten; + } + } + // patch materials/textures UF_DEBUG_TIMER_MULTITRACE("Patching textures/materials"); for ( auto& name : graph.materials ) { @@ -1205,6 +1227,22 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) metadataJson["system"]["graph"]["name"] = node.name; metadataJson["system"]["graph"]["index"] = index; + uf::Serializer loadJson; + // convert metadata["valve"] into internal values: + auto& metadataValve = node.metadata["valve"]; + if ( ext::json::isObject( metadataValve ) ) { + // bind door script + if ( ext::json::isObject( metadataValve["door"] ) ) { + node.metadata["door"] = metadataValve["door"]; + loadJson["imports"].emplace_back("/door.json"); + } + // bind io connectivity + if ( ext::json::isArray( metadataValve["connections"] ) || metadataValve["targetname"].is() ) { + node.metadata["connections"] = metadataValve["connections"]; + loadJson["imports"].emplace_back("/io.json"); + } + } + if ( ext::json::isObject( tag ) ) { if ( tag["action"].as() == "load" ) { if ( tag["filename"].is() ) { @@ -1213,6 +1251,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) } else if ( ext::json::isObject( tag["payload"] ) ) { uf::Serializer json = tag["payload"]; json["root"] = graphMetadataJson["root"]; + json.import( loadJson ); entity.load(json); } } else if ( tag["action"].as() == "attach" ) { @@ -1227,8 +1266,14 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent ) if ( tag["static"].is() ) { metadata.system.ignoreGraph = tag["static"].as(); } + } else if ( ext::json::isObject( loadJson ) ) { + loadJson["root"] = graphMetadataJson["root"]; + entity.load( loadJson ); } + // import metadata + metadataJson.import( node.metadata ); + // create as light { // attempt to resolve a light name diff --git a/engine/src/engine/object/behaviors/graph.cpp b/engine/src/engine/object/behaviors/graph.cpp index c591a7f2..02a564c1 100644 --- a/engine/src/engine/object/behaviors/graph.cpp +++ b/engine/src/engine/object/behaviors/graph.cpp @@ -45,13 +45,13 @@ void uf::GraphBehavior::initialize( uf::Object& self ) { if ( !uf::asset::has( payload ) ) uf::asset::load( payload ); auto& graph = payload.asComponent ? this->getComponent() : uf::asset::get( payload ); if ( !payload.asComponent ) { - this->moveComponent( uf::asset::get( payload.filename ) ); - uf::asset::remove( payload.filename ); + auto userdata = uf::asset::release( payload.filename ); + this->moveComponent( userdata ); } // bind graph's root entity to self if different if ( graph.root.entity && graph.root.entity != this ) { - UF_MSG_DEBUG("binding root transform to self"); + //UF_MSG_DEBUG("binding root transform to self"); auto& transform = this->getComponent>(); auto& root = *graph.root.entity; root.getComponent>().reference = &transform; diff --git a/engine/src/engine/object/behaviors/lua.cpp b/engine/src/engine/object/behaviors/lua.cpp index 78cf40a9..d7b09d06 100644 --- a/engine/src/engine/object/behaviors/lua.cpp +++ b/engine/src/engine/object/behaviors/lua.cpp @@ -22,20 +22,21 @@ void uf::LuaBehavior::initialize( uf::Object& self ) { this->addHook( "asset:Load.%UID%", [&](pod::payloads::assetLoad& payload){ if ( !uf::asset::isExpected( payload, uf::asset::Type::LUA ) ) return; if ( !uf::asset::has( payload ) ) uf::asset::load( payload ); - auto& script = uf::asset::get( payload ); - if ( !payload.asComponent ) { - // auto asset = uf::asset::release( payload.filename ); - // this->moveComponent( asset ); - this->moveComponent( uf::asset::get( payload.filename ) ); - uf::asset::remove( payload.filename ); - } - /* - if ( !uf::asset::has(payload.filename) ) uf::asset::load( payload ); - auto& script = uf::asset::get(payload.filename); - */ - // auto& script = this->getComponent(); + + auto script = uf::asset::get( payload.filename ); script.env["ent"] = &this->as(); ext::lua::run( script ); + /* + // crashes when trying to destroy the moved component (despite it having no reason to get destroyed) + auto& script = payload.asComponent ? this->getComponent() : uf::asset::get( payload ); + if ( !payload.asComponent ) { + auto userdata = uf::asset::release( payload.filename ); + this->moveComponent( userdata ); + } + + script.env["ent"] = &this->as(); + ext::lua::run( script ); + */ }); #endif } diff --git a/engine/src/engine/object/object.cpp b/engine/src/engine/object/object.cpp index 0d2e9361..a2bc3881 100644 --- a/engine/src/engine/object/object.cpp +++ b/engine/src/engine/object/object.cpp @@ -71,6 +71,8 @@ void uf::Object::queueDeletion() { // mark for destruction metadata.system.markedForDeletion = true; uf::instantiator::queueDeletion( *this ); + // dispatch deletion hooks + this->callHook("entity:Destroy.%UID%"); } uf::Hooks::return_t uf::Object::callHook( const uf::stl::string& name ) { @@ -253,7 +255,7 @@ void uf::Object::loadAssets( const uf::Serializer& _json ){ switch ( assetType ) { case uf::asset::Type::LUA: { - // payload.asComponent = true; + payload.asComponent = false; if ( bind ) uf::instantiator::bind("LuaBehavior", *this); } break; case uf::asset::Type::GRAPH: { diff --git a/engine/src/ext/audio/vorbis.cpp b/engine/src/ext/audio/vorbis.cpp index 4b5c1c45..649c5bfa 100644 --- a/engine/src/ext/audio/vorbis.cpp +++ b/engine/src/ext/audio/vorbis.cpp @@ -47,17 +47,19 @@ namespace { int seek( void* userdata, ogg_int64_t to, int type ) { VorbisVfsContext* ctx = (VorbisVfsContext*) userdata; + int64_t targetOffset = 0; switch ( type ) { - case SEEK_CUR: ctx->currentOffset += to; break; - case SEEK_END: ctx->currentOffset = ctx->totalSize + to; break; - case SEEK_SET: ctx->currentOffset = to; break; + case SEEK_CUR: targetOffset = (int64_t)ctx->currentOffset + to; break; + case SEEK_END: targetOffset = (int64_t)ctx->totalSize + to; break; + case SEEK_SET: targetOffset = to; break; default: return -1; } - if ((int64_t)ctx->currentOffset < 0) ctx->currentOffset = 0; - if (ctx->currentOffset > ctx->totalSize) ctx->currentOffset = ctx->totalSize; + if (targetOffset < 0) targetOffset = 0; + if ((size_t)targetOffset > ctx->totalSize) targetOffset = ctx->totalSize; + ctx->currentOffset = (size_t)targetOffset; return 0; } @@ -130,6 +132,29 @@ void ext::vorbis::open(uf::Audio::Metadata& metadata) { metadata.info.frequency = info->rate; metadata.info.duration = ov_time_total(vorbisFile, -1); + metadata.info.loop.has = false; + metadata.info.loop.start = 0; + metadata.info.loop.end = (uint32_t)ov_pcm_total(vorbisFile, -1); // Default to full file + + vorbis_comment* vc = ov_comment(vorbisFile, -1); + if ( vc != nullptr ) { + for ( auto i = 0; i < vc->comments; ++i ) { + uf::stl::string comment(vc->user_comments[i], vc->comment_lengths[i]); + uf::stl::string upperComment = uf::string::uppercase( comment ); + + // to-do: handle exceptions without exceptions + if ( upperComment.starts_with("LOOPSTART=" )) { + metadata.info.loop.start = std::stoul(comment.substr(10)); + metadata.info.loop.has = true; + } else if ( upperComment.starts_with("LOOPLENGTH=" )) { + uint32_t length = std::stoul(comment.substr(11)); + metadata.info.loop.end = metadata.info.loop.start + length; + } else if ( upperComment.starts_with("LOOPEND=") ) { + metadata.info.loop.end = std::stoul(comment.substr(8)); + } + } + } + if ( !format(metadata, info->channels, 16) ) { ov_clear(vorbisFile); metadata.stream.context = nullptr; @@ -154,6 +179,10 @@ void ext::vorbis::load( uf::Audio::Metadata& metadata ) { } while ( read > 0 ); metadata.al.buffer.buffer(metadata.info.format, bytes.data(), (ALsizei) bytes.size(), metadata.info.frequency); + if ( metadata.info.loop.has ) { + ALint loopPoints[2] = { (ALint) metadata.info.loop.start, (ALint) metadata.info.loop.end }; + alBufferiv(metadata.al.buffer.getIndex(), 0x2015 /* AL_LOOP_POINTS_SOFT */, loopPoints); + } metadata.al.source.set(AL_BUFFER, (ALint) metadata.al.buffer.getIndex()); ov_clear(vorbisFile); @@ -175,7 +204,8 @@ void ext::vorbis::stream(uf::Audio::Metadata& metadata) { int result = OV_READ(vorbisFile, buffer + totalRead, uf::audio::bufferSize - totalRead, endian, 2, 1, &bitStream); if (result <= 0) { if (result == 0 && metadata.settings.loop) { - if (ov_raw_seek(vorbisFile, 0) != 0) { + uint32_t seekTarget = metadata.info.loop.has ? metadata.info.loop.start : 0; + if ( ov_pcm_seek(vorbisFile, seekTarget) != 0 ) { UF_MSG_ERROR("Vorbis: failed to loop (seek to start): {}", metadata.filename); break; } @@ -235,7 +265,9 @@ void ext::vorbis::update(uf::Audio::Metadata& metadata) { int result = OV_READ(vorbisFile, buffer + totalRead, uf::audio::bufferSize - totalRead, endian, 2, 1, &bitStream); if (result <= 0) { if (result == 0 && metadata.settings.loop) { - if (ov_raw_seek(vorbisFile, 0) != 0) { + uint32_t seekTarget = metadata.info.loop.has ? metadata.info.loop.start : 0; + + if ( ov_pcm_seek(vorbisFile, seekTarget) != 0 ) { UF_MSG_ERROR("Vorbis: failed to loop (seek to start): {}", metadata.filename); break; } diff --git a/engine/src/ext/audio/wav.cpp b/engine/src/ext/audio/wav.cpp index 523e9328..f6e08ef3 100644 --- a/engine/src/ext/audio/wav.cpp +++ b/engine/src/ext/audio/wav.cpp @@ -41,18 +41,18 @@ namespace { drwav_bool32 drwav_vfs_seek(void* pUserData, int offset, drwav_seek_origin origin) { DrWavVfsContext* ctx = (DrWavVfsContext*)pUserData; + long long targetOffset = 0; - if ((int)origin == 0) { - ctx->currentOffset = (size_t)offset; - } else if ((int)origin == 1) { - if (offset < 0 && (size_t)(-offset) > ctx->currentOffset) { - ctx->currentOffset = 0; - } else { - ctx->currentOffset += offset; - } + if ( (int)origin == 0 ) { + targetOffset = offset; + } else if ( (int)origin == 1 ) { + targetOffset = (long long)ctx->currentOffset + offset; } - if (ctx->currentOffset > ctx->totalSize) ctx->currentOffset = ctx->totalSize; + if (targetOffset < 0) targetOffset = 0; + if ((size_t)targetOffset > ctx->totalSize) targetOffset = ctx->totalSize; + + ctx->currentOffset = (size_t)targetOffset; return 1; } @@ -136,6 +136,23 @@ void ext::wav::open(uf::Audio::Metadata& metadata) { metadata.info.frequency = wav->sampleRate; metadata.info.duration = (double) wav->totalPCMFrameCount / wav->sampleRate; + for ( drwav_uint32 i = 0; i < wav->metadataCount; ++i ) { + if ( wav->pMetadata[i].type == drwav_metadata_type_smpl ) { + const drwav_smpl& smpl = wav->pMetadata[i].data.smpl; + + if ( smpl.sampleLoopCount > 0 && smpl.pLoops != nullptr ) { + metadata.info.loop.has = true; + metadata.info.loop.start = smpl.pLoops[0].firstSampleOffset; + metadata.info.loop.end = smpl.pLoops[0].lastSampleOffset; + + if ( metadata.info.loop.end == 0 ) { + metadata.info.loop.end = (uint32_t)wav->totalPCMFrameCount; + } + } + break; + } + } + // determine OpenAL format if (wav->channels == 1 && wav->bitsPerSample == 8) metadata.info.format = AL_FORMAT_MONO8; @@ -168,6 +185,10 @@ void ext::wav::load(uf::Audio::Metadata& metadata) { } metadata.al.buffer.buffer(metadata.info.format, bytes.data(), (ALsizei) bytes.size(), metadata.info.frequency); + if ( metadata.info.loop.has ) { + ALint loopPoints[2] = { (ALint) metadata.info.loop.start, (ALint) metadata.info.loop.end }; + alBufferiv(metadata.al.buffer.getIndex(), 0x2015 /* AL_LOOP_POINTS_SOFT */, loopPoints); + } metadata.al.source.set(AL_BUFFER, (ALint) metadata.al.buffer.getIndex()); funs::close(&metadata); @@ -186,7 +207,7 @@ void ext::wav::stream(uf::Audio::Metadata& metadata) { if (bytesRead == 0) { if (metadata.settings.loop) { metadata.stream.consumed = 0; - if (funs::seek(&metadata, 0, SEEK_SET) != 0) { + if ( funs::seek(&metadata, metadata.info.loop.start, SEEK_SET) != 0 ) { UF_MSG_ERROR("WAV: failed to loop (seek to start): {}", metadata.filename); break; } @@ -238,7 +259,7 @@ void ext::wav::update(uf::Audio::Metadata& metadata) { if (bytesRead == 0) { // no more data left to read, reset file stream if we're looping if (!metadata.settings.loop) break; - if (funs::seek(&metadata, 0, SEEK_SET) != 0) { + if ( funs::seek(&metadata, metadata.info.loop.start, SEEK_SET) != 0 ) { UF_MSG_ERROR("WAV: failed to loop (seek to start): {}", metadata.filename); break; } diff --git a/engine/src/ext/lua/usertypes/object.cpp b/engine/src/ext/lua/usertypes/object.cpp index 39efa213..67ceafb0 100644 --- a/engine/src/ext/lua/usertypes/object.cpp +++ b/engine/src/ext/lua/usertypes/object.cpp @@ -161,20 +161,42 @@ namespace binds { if ( !functionPointer ) return false; pod::Behavior::function_t& function = *functionPointer; + bool hasExisting = (bool)(function); // check if a function is already bound to this slot + auto prev = function; // copy the existing function + #if !UF_LUA_PCALLS - function = fun; + if ( hasExisting ) { + function = [prev, fun]( uf::Object& s ) { + prev(s); + fun(s); + }; + } else { + function = fun; + } #else - function = [fun]( uf::Object& s ) { - auto result = fun(s); - if ( !result.valid() ) { - sol::error err = result; - UF_MSG_ERROR("{}", err.what()); - } - }; + if ( hasExisting ) { + function = [prev, fun]( uf::Object& s ) { + prev(s); // call the previous script's tick + auto result = fun(s); // call the new script's tick + if ( !result.valid() ) { + sol::error err = result; + UF_MSG_ERROR("{}", err.what()); + } + }; + } else { + function = [fun]( uf::Object& s ) { + auto result = fun(s); + if ( !result.valid() ) { + sol::error err = result; + UF_MSG_ERROR("{}", err.what()); + } + }; + } #endif self.generateGraph(); return true; } + uf::Object& findByUid( uf::Object& self, size_t index ) { auto* pointer = self.findByUid( index ); if ( pointer ) return pointer->as(); diff --git a/engine/src/ext/lua/usertypes/physics.cpp b/engine/src/ext/lua/usertypes/physics.cpp index 1cf7da8f..e5a22602 100644 --- a/engine/src/ext/lua/usertypes/physics.cpp +++ b/engine/src/ext/lua/usertypes/physics.cpp @@ -49,6 +49,10 @@ namespace binds { return std::make_tuple( object, depth ); } + pod::AABB bounds( const pod::PhysicsBody& self ) { + return self.bounds; + } + pod::PhysicsBody& asAabb( pod::PhysicsBody& self, const pod::AABB& shape ) { return uf::physics::initialize( self, shape ); } @@ -140,10 +144,16 @@ UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(pod::OBB, return self = copy; }, #if OBB_EXTENT_CENTER + []( pod::OBB& self, const pod::AABB& copy ) { + return self = pod::OBB{ .extent = (copy.max - copy.min) * 0.5f, .center = (copy.max + copy.min) * 0.5f }; + }, []( pod::OBB& self, const pod::Vector3f& extent, const pod::Vector3f& center ) { return self = pod::OBB{ .extent = extent, .center = center }; } #else + []( pod::OBB& self, const pod::AABB& copy ) { + return self = pod::OBB{ .center = (copy.max + copy.min) * 0.5f, .extent = (copy.max - copy.min) * 0.5f }; + }, []( pod::OBB& self, const pod::Vector3f& center, const pod::Vector3f& extent ) { return self = pod::OBB{ .center = center, .extent = extent }; } @@ -235,6 +245,8 @@ UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(pod::PhysicsBody, UF_LUA_REGISTER_USERTYPE_DEFINE( getMass, UF_LUA_C_FUN(::binds::body::getMass) ), UF_LUA_REGISTER_USERTYPE_DEFINE( setMass, UF_LUA_C_FUN(::binds::body::setMass) ), + UF_LUA_REGISTER_USERTYPE_DEFINE( bounds, UF_LUA_C_FUN(::binds::body::bounds) ), + UF_LUA_REGISTER_USERTYPE_DEFINE( asAabb, UF_LUA_C_FUN(::binds::body::asAabb) ), UF_LUA_REGISTER_USERTYPE_DEFINE( asObb, UF_LUA_C_FUN(::binds::body::asObb) ), UF_LUA_REGISTER_USERTYPE_DEFINE( asSphere, UF_LUA_C_FUN(::binds::body::asSphere) ), diff --git a/engine/src/ext/valve/bsp.cpp b/engine/src/ext/valve/bsp.cpp index 84ae83bf..a47a8294 100644 --- a/engine/src/ext/valve/bsp.cpp +++ b/engine/src/ext/valve/bsp.cpp @@ -201,6 +201,30 @@ namespace impl { pod::Atlas lightmapAtlas; }; + struct BSP_IO { + uf::stl::string output; + uf::stl::string target; + uf::stl::string input; + uf::stl::string parameter; + float delay; + int timesToFire; + }; + + impl::BSP_IO parseIO( const uf::stl::string& output, const uf::stl::string& value ) { + char delimiter = value.find('\x1B') != uf::stl::string::npos ? '\x1B' : ','; // either ESC or a comma + auto tokens = uf::string::split( value, delimiter ); + + impl::BSP_IO io; + io.output = output; + io.target = tokens.size() > 0 ? tokens[0] : ""; + io.input = tokens.size() > 1 ? tokens[1] : ""; + io.parameter = tokens.size() > 2 ? tokens[2] : ""; + io.delay = tokens.size() > 3 ? std::stof(tokens[3]) : 0.0f; + io.timesToFire = tokens.size() > 4 ? std::stoi(tokens[4]) : -1; + + return io; + } + pod::Atlas::hash_t faceHash( size_t i ) { return ::fmt::format("face_{}", i); } @@ -403,12 +427,9 @@ namespace impl { } // parse angles - // to-do: fix oddities auto angles = metadata["angles"].as(""); if ( angles != "" ) { - auto pyr = impl::str2vec( angles ) * DEG_2_RAD; - pyr.x = -pyr.x; - + auto pyr = impl::str2vec( angles ) * -DEG_2_RAD; node.transform.orientation = uf::quaternion::euler( pyr ); } @@ -456,13 +477,45 @@ namespace impl { light.intensity *= 0.2f; // scale down } - // parse player spawn info - if ( classname == "info_player_start" ) { - spawnID = nodeID; - } else if ( classname.starts_with("info_player_") ) { - spawns.emplace_back(nodeID); + // parse door + if ( classname.starts_with("func_door") ) { + auto& metadataDoor = metadata["door"]; + + metadataDoor["speed"] = metadata["speed"].as(100.0f); + metadataDoor["wait"] = metadata["wait"].as(4.0f); + metadataDoor["lip"] = metadata["lip"].as(8.0f); + metadataDoor["spawnflags"] = metadata["spawnflags"].as(0); + + if ( classname == "func_door" ) { + auto movedirStr = metadata["movedir"].as(""); + if ( movedirStr == "" && metadata["angle"].as() ) { + float ang = metadata["angle"].as(); + if ( ang == -1 ) movedirStr = "-90 0 0"; + else if ( ang == -2 ) movedirStr = "90 0 0"; + else movedirStr = ::fmt::format("0 {} 0", ang); + } + + if ( movedirStr != "" ) { + pod::Transform<> t; + auto pyr = impl::str2vec( movedirStr ) * -DEG_2_RAD; + t.orientation = uf::quaternion::euler( pyr ); + auto axes = uf::transform::axes( t ); + + metadataDoor["direction"] = uf::vector::encode( axes.forward ); + } + } else if ( classname == "func_door_rotating" ) { + metadataDoor["distance"] = metadata["distance"].as(90.0f); + + int flags = metadataDoor["spawnflags"].as(); + pod::Vector3f axis = {0, 1, 0}; + if ( flags & 64 ) axis = {0, 0, 1}; + if ( flags & 128 ) axis = {1, 0, 0}; + + metadataDoor["axis"] = uf::vector::encode( axis ); + } } + // parse parent auto targetname = metadata["targetname"].as(""); if ( targetname != "" ) { targets[targetname] = nodeID; @@ -471,6 +524,7 @@ namespace impl { // to-do: add additional parsing } + // re-parent entities uf::stl::vector newChildren; for ( auto nodeID : graph.root.children ) { auto& node = graph.nodes[nodeID]; @@ -488,19 +542,6 @@ namespace impl { } } graph.root.children = newChildren; - - // no valid spawn - UF_ASSERT( !(spawnID == -1 && spawns.empty()) ); // to-do: make the engine implicitly spawn the player at origin - // pick a random candidate if none was found - if ( spawnID == -1 ) spawnID = uf::stl::random( spawns ); - for ( auto nodeID : spawns ) { - auto& node = graph.nodes[nodeID]; - if ( nodeID == spawnID ) { - node.name = "info_player_start"; // mutate into spawn - } else if ( node.name == "info_player_start" ) { - node.name = ::fmt::format( "_{}", node.name ); // mutate out of spawn - } - } } } @@ -559,26 +600,9 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co } } - size_t imageID = graph.images.size(); - auto imgKeyName = graph.images.emplace_back(matName); - auto& image = storage.images[imgKeyName].data; - - size_t textureID = graph.textures.size(); - auto texKeyName = graph.textures.emplace_back(matName); - storage.textures[texKeyName].index = imageID; - size_t materialID = graph.materials.size(); + auto& material = impl::addMaterial( graph, matName ); context.texdataToMaterial[texDataID] = materialID; - - auto matKeyName = graph.materials.emplace_back(matName); - auto& material = storage.materials[matKeyName]; - material.indexAlbedo = textureID; - material.colorBase = {1.0f, 1.0f, 1.0f, 1.0f}; - material.factorMetallic = 0.0f; - material.factorRoughness = 1.0f; - material.factorOcclusion = 1.0f; - - //UF_MSG_INFO("Material found: {}", matName); } // read lightmaps @@ -685,6 +709,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co { bool parsing = false; uf::stl::unordered_map dict; + uf::stl::vector connections; uf::stl::string string( (const char*) context.entities.data() ); uf::stl::string line; @@ -693,6 +718,7 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co if ( line.find("{") != uf::stl::string::npos ) { parsing = true; dict.clear(); + connections.clear(); } else if ( line.find("}") != uf::stl::string::npos ) { parsing = false; @@ -700,13 +726,35 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co auto nodeID = graph.nodes.size(); auto& node = graph.nodes.emplace_back(); auto& metadata = node.metadata["valve"]; - for ( const auto& [k, v] : dict ) metadata[k] = v; // store as metadata for later parsing + for ( const auto& [k, v] : dict ) metadata[k] = impl::processValue( v ); // store as metadata for later parsing + + // insert IO + if ( !connections.empty() ) { + if ( ext::json::isNull( metadata["connections"] ) ) { + ext::json::reserve( metadata["connections"], connections.size() ); + } + for ( auto& io : connections ) { + auto& entry = metadata["connections"].emplace_back(); + entry["output"] = io.output; + entry["target"] = io.target; + entry["input"] = io.input; + entry["parameter"] = io.parameter; + entry["delay"] = io.delay; + entry["times"] = io.timesToFire; + } + } // add node as child graph.root.children.emplace_back( nodeID ); } else if ( parsing ) { uf::stl::string key, value; - if ( impl::parseKeyValue(line, key, value) ) dict[key] = value; + if ( impl::parseKeyValue(line, key, value) ) { + if ( key.starts_with("On") ) { + connections.emplace_back( impl::parseIO( key, value ) ); + continue; + } + dict[key] = value; + } } } } @@ -786,19 +834,19 @@ void ext::valve::loadBsp( pod::Graph& graph, const uf::stl::string& filename, co material.factorRoughness = vmt["$roughness"].as(1.0f); if ( vmt["$envmap"].as() != "" ) material.factorRoughness = 0.3f; - if ( vmt["$phong"].as("0") == "1" ) material.factorRoughness = std::min(material.factorRoughness, 0.5f); + if ( vmt["$phong"].as(0) == 1 ) material.factorRoughness = std::min(material.factorRoughness, 0.5f); - if ( vmt["$translucent"].as("0") == "1" ) { + if ( vmt["$translucent"].as(0) == 1 ) { material.modeAlpha = 1; // BLEND - } else if ( vmt["$alphatest"].as("0") == "1" ) { + } else if ( vmt["$alphatest"].as(0) == 1 ) { material.modeAlpha = 2; // MASK material.factorAlphaCutoff = vmt["$alphatestreference"].as(0.5f); } - if ( vmt["$nocull"].as("0") == "1" ) material.modeCull = 0; + if ( vmt["$nocull"].as(0) == 1 ) material.modeCull = 0; // VMTs usually define emissive masks in the albedo's alpha channel or a separate mask // set it to a white glow for now until I can patch the shader - if ( vmt["$selfillum"].as("0") == "1" ) material.colorEmissive = { 1.0f, 1.0f, 1.0f, 1.0f }; + if ( vmt["$selfillum"].as(0) == 1 ) material.colorEmissive = { 1.0f, 1.0f, 1.0f, 1.0f }; if ( !vmt["$basetexture"].is() ) goto PEETAH; vtfPath = ::fmt::format("materials/{}.vtf", vmt["$basetexture"].as()); diff --git a/engine/src/ext/valve/common.cpp b/engine/src/ext/valve/common.cpp index 0a737054..0bed424f 100644 --- a/engine/src/ext/valve/common.cpp +++ b/engine/src/ext/valve/common.cpp @@ -16,4 +16,39 @@ uf::stl::string impl::readString( std::ifstream& file ) { char c; while ( file.get(c) && c != '\0' ) str += c; return str; +} + +pod::Material& impl::addMaterial( pod::Graph& graph, const uf::stl::string& name ) { + auto& storage = uf::graph::getStorage( graph ); + + size_t imageID = graph.images.size(); + auto imgKeyName = graph.images.emplace_back(name); + auto& image = storage.images[imgKeyName].data; + + size_t textureID = graph.textures.size(); + auto texKeyName = graph.textures.emplace_back(name); + storage.textures[texKeyName].index = imageID; + + auto matKeyName = graph.materials.emplace_back(name); + auto& material = storage.materials[matKeyName]; + material.indexAlbedo = textureID; + material.colorBase = {1.0f, 1.0f, 1.0f, 1.0f}; + material.factorMetallic = 0.0f; + material.factorRoughness = 1.0f; + material.factorOcclusion = 1.0f; + + return material; +} + +ext::json::Value impl::processValue( const uf::stl::string& v ) { + if ( v.empty() ) return ext::json::Value(v); + + char* end = nullptr; + long intVal = std::strtol( v.c_str(), &end, 10 ); + if ( end == v.c_str() + v.size() ) return ext::json::Value( intVal ); + + float floatVal = std::strtof( v.c_str(), &end ); + if ( end == v.c_str() + v.size() ) return ext::json::Value( floatVal ); + + return ext::json::Value( v ); } \ No newline at end of file diff --git a/engine/src/ext/valve/mdl.cpp b/engine/src/ext/valve/mdl.cpp index b4250e5d..13899834 100644 --- a/engine/src/ext/valve/mdl.cpp +++ b/engine/src/ext/valve/mdl.cpp @@ -285,23 +285,8 @@ bool ext::valve::loadMdl( pod::Graph& graph, const uf::stl::string& filename ) { } } else { - // does not exist, register - size_t imageID = graph.images.size(); - auto imgKeyName = graph.images.emplace_back(matName); - auto& image = storage.images[imgKeyName].data; - - size_t textureID = graph.textures.size(); - auto texKeyName = graph.textures.emplace_back(matName); - storage.textures[texKeyName].index = imageID; - materialID = graph.materials.size(); - auto matKeyName = graph.materials.emplace_back(matName); - auto& material = storage.materials[matKeyName]; - material.indexAlbedo = textureID; - material.colorBase = {1.0f, 1.0f, 1.0f, 1.0f}; - material.factorMetallic = 0.0f; - material.factorRoughness = 1.0f; - material.factorOcclusion = 1.0f; + auto& material = impl::addMaterial( graph, matName ); } meshlet.primitive.instance.materialID = materialID; diff --git a/engine/src/ext/valve/vpk.cpp b/engine/src/ext/valve/vpk.cpp index 0a825d16..19279349 100644 --- a/engine/src/ext/valve/vpk.cpp +++ b/engine/src/ext/valve/vpk.cpp @@ -127,7 +127,7 @@ pod::Mount ext::valve::createVpkMount( const uf::stl::string& uri, int priority auto ptr = userdata->get(); if ( !ptr ) return 0; auto it = ptr->files.find( uf::string::lowercase( p ) ); - return it != ptr->files.end() ? it->second.metadata.entryLength : 0; + return it != ptr->files.end() ? (it->second.metadata.preloadBytes + it->second.metadata.entryLength) : 0; }; mount.mtime = [](const uf::stl::string&) -> size_t { return 0; }, mount.read = [userdata](const uf::stl::string& p, uf::stl::vector& buffer) { @@ -300,6 +300,15 @@ bool ext::valve::readVpkRange( const pod::VpkArchive& vpk, const uf::stl::string if ( file ) { file.seekg(fileOffset + diskStart, std::ios::beg); file.read((char*)(buffer.data() + bufferOffset), len); + + size_t actuallyRead = static_cast(file.gcount()); + + if (actuallyRead < len) { + buffer.resize(bufferOffset + actuallyRead); + if (actuallyRead == 0 && buffer.empty()) { + return false; + } + } } else { buffer.clear(); UF_MSG_ERROR("Failed to open VPK chunk for ranged read: {}", archivePath); diff --git a/engine/src/ext/valve/vtf.cpp b/engine/src/ext/valve/vtf.cpp index 6ea99f7f..2c599480 100644 --- a/engine/src/ext/valve/vtf.cpp +++ b/engine/src/ext/valve/vtf.cpp @@ -138,7 +138,7 @@ bool ext::valve::loadVmt( uf::Serializer& dict, const uf::stl::string& filename if ( key == "include" ) { ext::valve::loadVmt( dict, value ); } else { - dict[key] = value; + dict[key] = impl::processValue( value ); } } } diff --git a/engine/src/ext/vulkan/graphic.cpp b/engine/src/ext/vulkan/graphic.cpp index b1b3f0ab..3ca5c491 100644 --- a/engine/src/ext/vulkan/graphic.cpp +++ b/engine/src/ext/vulkan/graphic.cpp @@ -1949,11 +1949,11 @@ void ext::vulkan::Graphic::record( VkCommandBuffer commandBuffer, const GraphicD } #endif if ( !hasPipeline( descriptor ) ) { - UF_MSG_DEBUG("{} has no valid pipeline: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline); + // UF_MSG_DEBUG("{} has no valid pipeline: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline); return; } if ( !hasDescriptorSet( descriptor ) ) { - UF_MSG_DEBUG("{} has no valid descriptor set: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline); + // UF_MSG_DEBUG("{} has no valid descriptor set: renderMode={}, pipeline={}", (void*) this, descriptor.renderMode, descriptor.pipeline); return; } diff --git a/engine/src/utils/audio/audio.cpp b/engine/src/utils/audio/audio.cpp index 1e85e541..6e45a045 100644 --- a/engine/src/utils/audio/audio.cpp +++ b/engine/src/utils/audio/audio.cpp @@ -293,4 +293,8 @@ const uf::stl::string& uf::Audio::getFilename() const { } float uf::Audio::getDuration() const { return this->m_metadata ? this->m_metadata->info.duration : 0; +} + +bool uf::Audio::hasLoops() const { + return this->m_metadata ? this->m_metadata->info.loop.has : false; } \ No newline at end of file diff --git a/engine/src/utils/debug/draw.cpp b/engine/src/utils/debug/draw.cpp index 4aa99e62..52764b55 100644 --- a/engine/src/utils/debug/draw.cpp +++ b/engine/src/utils/debug/draw.cpp @@ -382,7 +382,8 @@ void uf::debug::drawTexts( float dt ) { if ( rebuild ) uf::renderer::states::rebuild = true; // to-do: rebuild the defer mode only } } +// think this has issues in OpenGL void uf::debug::draw( float dt ) { -// uf::debug::drawLines( dt ); -// uf::debug::drawTexts( dt ); + uf::debug::drawLines( dt ); + uf::debug::drawTexts( dt ); } \ No newline at end of file diff --git a/engine/src/utils/string/ext.cpp b/engine/src/utils/string/ext.cpp index 4c055b11..49341bd0 100644 --- a/engine/src/utils/string/ext.cpp +++ b/engine/src/utils/string/ext.cpp @@ -59,6 +59,10 @@ uf::stl::vector uf::string::split( const uf::stl::string& str, if ( tokens.empty() ) tokens.emplace_back(str); return tokens; } +uf::stl::vector uf::string::split( const uf::stl::string& str, char delim ) { + uf::stl::string d = ::fmt::format("{}", delim); // because it's such a pain apparently to convert a char to str + return uf::string::split( str, d ); +} /* uf::stl::string uf::string::join( const uf::stl::vector& strings, const uf::stl::string& delim, bool trailing ) { uf::stl::stringstream ss;