diff --git a/bin/data/config.json b/bin/data/config.json index a3aa95f5..89f6a27f 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -354,7 +354,7 @@ "ngs": true, "percent": 0.2, "slop": 0.01, // 0.005 - "max": 0.2 // 0.2 + "max": 0.1 // 0.2 }, "debug draw": { "static": false, diff --git a/bin/data/entities/burger.json b/bin/data/entities/burger.json index af3855ad..3153797a 100644 --- a/bin/data/entities/burger.json +++ b/bin/data/entities/burger.json @@ -1,7 +1,7 @@ { "type": "Object", "name": "Burger", - "ignore": true, + "ignore": false, "import": "/model.json", "assets": [ // "/burger/burger.glb" @@ -11,7 +11,7 @@ ], "behaviors": [], "transform": { - "position": [ -0.574743, 2.3547, -5.05161 ], + "position": [ 25.7525, 5.17746, 16.5508 ], // "position": [ -4.66561, 0.0736207, -5.98057 ], "rotation": { "axis": [ 0, 1, 0 ], diff --git a/bin/data/entities/player.json b/bin/data/entities/player.json index 7d37b1b7..b84df050 100644 --- a/bin/data/entities/player.json +++ b/bin/data/entities/player.json @@ -1,26 +1,13 @@ { "name": "Player", - "type": "Object", + "type": "Player", "behaviors": [ - "PlayerBehavior", "AudioEmitterBehavior" ], - "ignore": false, - /* - "transform": { - "position": [ 0, 0, 0 ], - "rotation": { - "axis": [ 0, 1, 0 ], - "angle": 0 - }, - "scale": [ 1, 1, 1 ] - }, - */ "assets": [ { "filename": "./playerModel.json", "delay": 1 }, // "./playerModel.json", "./playerLight.json", - // "./playerHands.json", "./gui/hud/hud.json", "./scripts/player.lua" ], @@ -30,78 +17,37 @@ } }, "metadata": { - "overlay": { - "transform": { - "position": [ 0, 0, -3 ], - "scale": [ 1.77778, -1, 1 ], - "orientation": [ 0, 0, 0, 1 ] - }, - "floating": false, - "enabled": true, - "alpha": 1.0, - "cursor": { - "type": "mouse", - "radius": 0.05, - "color": [ 0.2, 0.2, 1.0, 1.0 ], - "enabled": false - } - }, - "audio": { - "footstep": { - "volume": 0.5, - "list": [ - "/footstep/1.ogg", - "/footstep/2.ogg" - ] - } - }, "movement": { - "walk": 8, - "move": 8, - "run": 20, - // "rotate": 1.5, - "rotate": 6, + "walk": 6, + "move": 12, + "run": 18, + "rotate": 6.0, "air": 0.1, - "crouch": 1, + "crouch": 0.9, "jump": [ 0, 8, 0 ], "look": 1, "floored": { "feet": [ 0, -1, 0 ], - // "floor": [ 0, -0.5, 0 ], - "floor": [ 0, -1, 0 ], - "print": false + "floor": [ 0, -1, 0 ] }, "strafe": true }, "physics": { - "gravity": [ 0, -9.81, 0 ], + "gravity": [ 0, -14.0, 0 ], "inertia": false, - "offset": [ 0, 0, 0 ], - - // "type": "sphere", - // "radius": 2, - "type": "capsule", "radius": 1, "height": 2, "category": "player", "mask": "player", - - // "type": "bounding box", - // "min": [ -1, -1, -1 ], - // "max": [ 1, 1, 1 ], "mass": 100, - "friction": 0.95, - "restitution": 0.0, - - "shared": false + "friction": 0.90, + "restitution": 0.0 }, "camera": { - // "offset": [ 0, 10, 6 ], - // "orientation": [ 0, 0.894427, -0.447214, 0 ], "position" : [ 0, 1.8, 0 ], "scale": [ 1, 1, 1 ], "invert": [ false, false, false ], diff --git a/bin/data/entities/scripts/player.lua b/bin/data/entities/scripts/player.lua index 847afeee..a49d9b7a 100644 --- a/bin/data/entities/scripts/player.lua +++ b/bin/data/entities/scripts/player.lua @@ -1,22 +1,19 @@ local ent = ent local scene = entities.currentScene() local graph = scene:getComponent("Graph") -local metadataJson = ent:getComponent("Metadata") -local transform = ent:getComponent("Transform") local physicsBody = ent:getComponent("PhysicsBody") +local metadataJson = ent:getComponent("Metadata") local camera = ent:getComponent("Camera") -local cameraTransform = camera:getTransform() -local metadata = ent:getComponent("PlayerBehavior::Metadata") -local fixedCamera = metadataJson["camera"]["settings"]["fixed"] +local cameraMetadata = ent:getComponent("PlayerCameraBehavior::Metadata") +local moveStates = ent:getComponent("PlayerMovementBehavior::Metadata") +local fixedCamera = cameraMetadata and cameraMetadata.fixed or false -- setup all timers local timers = { - use = Timer.new(), holp = Timer.new(), flashlight = Timer.new(), physcannon = Timer.new() } -if not timers.use:running() then timers.use:start(); end if not timers.holp:running() then timers.holp:start(); end if not timers.flashlight:running() then timers.flashlight:start(); end if not timers.physcannon:running() then timers.physcannon:start(); end @@ -30,70 +27,42 @@ local heldObject = { momentum = Vector3f(0,0,0), rotate = false, } --- setup light locals -local light = { - entity = nil, -} + +-- setup light +local light = { entity = nil } for k, v in pairs(ent:getChildren()) do - if type(v) == "number" then - goto continue - end - if v:name() == "Light" then - light.entity = v - end - ::continue:: + if type(v) == "table" and v:name() == "Light" then light.entity = v end +end +if light.entity == nil then + light.entity = ent:loadChild("ent://playerLight.json",true) end -if light.entity == nil then - light.entity = ent:loadChild("./playerLight.json",true) -end light.metadata = light.entity:getComponent("LightBehavior::Metadata") light.transform = light.entity:getComponent("Transform") light.power = light.metadata.power -light.origin = Vector3f(light.transform.position) light.metadata.power = 0 ---light.entity:setComponent("Metadata", { light = { power = 0 } }) --- sound emitter -local playSound = function( path, loop ) - if not loop then loop = false end - local uri = string.resolveURI(path) - ent:callHook("sound:Emit.%UID%", { - filename = uri, - spatial = true, - streamed = true, - volume = "sfx", - loop = loop - }, 0) -end -local stopSound = function( path ) - ent:callHook("sound:Stop.%UID%", { - filename = string.resolveURI(path, metadataJson["system"]["root"]) - }, 0) -end +-- UI sound helpers local playUiSound = function( key, loop ) - return playSound("/ui/" .. key .. ".ogg", loop) -end -local stopUiSound = function( key ) - return stopSound("/ui/" .. key .. ".ogg", loop) + ent:callHook("sound:Emit.%UID%", { + filename = string.resolveURI("/ui/" .. key .. ".ogg"), + spatial = true, streamed = true, volume = "sfx", loop = loop or false + }, 0) end -local useDistance = 6 -local pullDistance = useDistance * 4 +local pullDistance = 24 -local function tickFlashlight( transform, axes, inputs ) - -- update light position +local function tickFlashlight( transform, axes, inputState ) if light.enabled then local center = transform.position local direction = axes.forward * 8 - local offset = 0.25 local _, depth = physicsBody:rayCast(center, direction) depth = math.clamp(depth, 0, 0.5) - light.transform.position = center + direction * (depth - offset) + light.transform.position = center + direction * (depth - 0.25) end - -- toggle - if timers.flashlight:elapsed() > 0.5 and inputs["F"] then + -- Flashlight uses 'F' key (we can keep this manual since it's game-specific, or add it to C++ input state) + if timers.flashlight:elapsed() > 0.5 and inputs.key("F") then timers.flashlight:reset() light.enabled = (light.metadata.power ~= light.power) light.metadata.power = light.enabled and light.power or 0 @@ -101,135 +70,47 @@ local function tickFlashlight( transform, axes, inputs ) end end -local function onUse( payload ) - local validUse = false - -- not currently holding anything, and hit something - if heldObject.uid == 0 and payload.depth > 0 then - local prop = entities.get( payload.uid ) - local propMetadata = prop:getComponent("Metadata") - -- entity is holdable, pick it up - if propMetadata["holdable"] then - validUse = true - local heldObjectTransform = prop:getComponent("Transform") - local heldObjectFlattened = heldObjectTransform:flatten() - local offset = transform.position - heldObjectFlattened.position +local function tickGravGun( transform, axes, inputState ) + local mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER") + local mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER") + local mouse3 = inputs.key("Mouse3") + local wheel = inputs.analog("MouseWheel") - heldObject.uid = payload.uid - heldObject.distance = offset:norm() - - local heldObjectPhysicsBody = prop:getComponent("PhysicsBody") - heldObjectPhysicsBody:enableGravity(false) - else - validUse = not string.matched( prop:name(), "/^worldspawn/" ) - end - -- currently holding something, drop it - elseif heldObject.uid ~= 0 then - validUse = true - local prop = entities.get( heldObject.uid ) - local heldObjectPhysicsBody = prop:getComponent("PhysicsBody") - heldObjectPhysicsBody:enableGravity(true) - heldObjectPhysicsBody:applyImpulse( heldObject.momentum ) - - heldObject.uid = 0 - heldObject.distance = 0 - heldObject.momentum = Vector3f(0,0,0) - end - - playUiSound(validUse and "select" or "deny") -end - -local function tickUse( transform, axes, inputs ) - -- trigger use - if timers.use:elapsed() > 0.5 and inputs["E"] then - timers.use:reset() - local center = transform.position - local direction = axes.forward * useDistance - local hit, depth = physicsBody:rayCast(center, direction) - - local payload = { - user = ent:uid(), - uid = hit and hit:uid() or 0, - depth = depth, - } - if hit then hit:lazyCallHook("entity:Use.%UID%", payload) end - ent:lazyCallHook("entity:Use.%UID%", payload) - end -end - -local function tickGravGun( transform, axes, inputs ) - -- not holding anything if heldObject.uid == 0 then - -- try and launch object in sights - if inputs["mouse2"] then - local center = transform.position - local direction = axes.forward * pullDistance - local prop, depth = physicsBody:rayCast( center, direction ) + if mouse2 then + local prop, depth = physicsBody:rayCast( transform.position, axes.forward * pullDistance ) if depth >= 0 and prop and not string.matched( prop:name(), "/^worldspawn/" ) then - local heldObjectTransform = prop:getComponent("Transform") - local heldObjectPhysicsBody = prop:getComponent("PhysicsBody") + local propPhysics = prop:getComponent("PhysicsBody") + local distanceSquared = (prop:getComponent("Transform").position - transform.position):magnitude() + propPhysics:applyImpulse( axes.forward * -propPhysics:getMass() * 500 / distanceSquared ) - local strength = 500 - local distanceSquared = (heldObjectTransform.position - transform.position):magnitude() - - heldObjectPhysicsBody:applyImpulse( axes.forward * -heldObjectPhysicsBody:getMass() * strength / distanceSquared ) if timers.physcannon:elapsed() > 1.0 then timers.physcannon:reset() - playUiSound("phys_tooHeavy") end end end - -- holding something else - -- adjust hold distance - if inputs["wheel"] ~= 0 then - heldObject.distance = heldObject.distance + (inputs["wheel"] / 120 * heldObject.scrollSpeed) * time.delta() - end - -- update rotation mode - if inputs["mouse3"] then - heldObject.rotate = not heldObject.rotate - end + -- Held object logic (unchanged from your original, just cleaner scope) + if wheel ~= 0 then heldObject.distance = heldObject.distance + (wheel / 120 * heldObject.scrollSpeed) * time.delta() end + if mouse3 then heldObject.rotate = not heldObject.rotate end local prop = entities.get( heldObject.uid ) - local heldObjectTransform = prop:getComponent("Transform") - local heldObjectPhysicsBody = prop:getComponent("PhysicsBody") + local propTransform = prop:getComponent("Transform") + local propPhysics = prop:getComponent("PhysicsBody") - -- launch held object - if inputs["mouse1"] and timers.physcannon:elapsed() > 0.5 then + if mouse1 and timers.physcannon:elapsed() > 0.5 then timers.physcannon:reset() - heldObject.uid = 0 - heldObjectPhysicsBody:enableGravity(true) - heldObjectPhysicsBody:applyImpulse( axes.forward * heldObjectPhysicsBody:getMass() * 50 ) - + propPhysics:enableGravity(true) + propPhysics:applyImpulse( axes.forward * propPhysics:getMass() * 50 ) playUiSound("phys_launch"..math.random(1,4)) else - -- update rotation - if heldObject.rotate then - --heldObjectTransform.orientation = Quaternion.lookAt( (heldObjectTransform.position - transform.position):normalize(), axes.up ) - heldObjectTransform.orientation = cameraTransform:flatten().orientation - end - - -- move held object - local forward = axes.forward * heldObject.distance - if heldObject.smoothSpeed ~= 0 then - local heldObjectFlattened = heldObjectTransform:flatten() + if heldObject.rotate then propTransform.orientation = camera:getTransform():flatten().orientation end - local target = transform.position + forward - local offset = target - heldObjectFlattened.position - - local stiffness = 15.0 - local damping = 2.0 - local currentVelocity = heldObjectPhysicsBody:getVelocity() - local mass = heldObjectPhysicsBody:getMass() - - local springForce = offset * stiffness - local dampingForce = currentVelocity * -damping - - heldObjectPhysicsBody:applyImpulse((springForce + dampingForce) * mass * time.delta()) - else - heldObjectTransform.position = transform.position + forward - end + local target = transform.position + (axes.forward * heldObject.distance) + local offset = target - propTransform:flatten().position + propPhysics:applyImpulse((offset * 15.0 + propPhysics:getVelocity() * -2.0) * propPhysics:getMass() * time.delta()) end end end @@ -275,23 +156,7 @@ local playFootstepSound = function( surface, isCrouching ) }, 0) end -local tickFootsteps = function() - local isWalking = metadata.states.walking - local isRunning = metadata.states.running - local isCrouching = metadata.states.crouching - local isFloored = metadata.states.floored - local isNoclipped = metadata.states.noclipped - - if not isFloored or isNoclipped then return end - - if not isWalking then - footstepTimer = 0.0 - return - end - - footstepTimer = footstepTimer - time.delta() - if footstepTimer > 0.0 then return end - +local function getFloorSurface() local surface = "concrete" local collisionEvents = physicsBody:getCollisionEvents() for i, event in ipairs( collisionEvents ) do @@ -305,20 +170,59 @@ local tickFootsteps = function() local drawCommand = mesh:fetchDrawCommand( tri ) local instance = graph:getInstance( drawCommand.instanceID ) local materialName = string.lower( graph:getMaterialName( instance.materialID ) ) + for _, key in ipairs(surfaceTypes) do if string.find(materialName, key) then - surface = key - break + return key end end - - break end + break end end + return surface +end - playFootstepSound( surface, isCrouching ) +local airTime = 0.0 +local tickFootsteps = function( inputState ) + local isWalking = moveStates.walking + local isRunning = moveStates.running + local isCrouching = moveStates.crouching + local isFloored = moveStates.floored + local isNoclipped = moveStates.noclipped + + if not isNoclipped then + if not isFloored then + airTime = airTime + time.delta() + end + + if isFloored and not wasFloored then + if airTime > 0.15 then + playFootstepSound( getFloorSurface(), isCrouching ) + footstepTimer = isRunning and 0.3 or 0.45 + end + airTime = 0.0 + end + + if not isFloored and wasFloored and inputState.jump then + playFootstepSound( getFloorSurface(), isCrouching ) + end + end + wasFloored = isFloored + + if not isWalking or isNoclipped then + footstepTimer = 0.0 + return + end + + footstepTimer = footstepTimer - time.delta() + + if not isFloored then return end + + if footstepTimer > 0.0 then return end + + playFootstepSound( getFloorSurface(), isCrouching ) if isRunning then footstepTimer = 0.3 @@ -329,71 +233,43 @@ local tickFootsteps = function() end end ---[[ -local tickCollisionEvents = function() - local collisionEvents = physicsBody:getCollisionEvents() - for i, event in ipairs(collisionEvents) do - -- print( event.state, event.a, event.b, event.point, event.normal, event.impulse, event.featureA ) - local tri = event.featureA or event.featureB - local other = event.a == ent and event.a or event.b - local collider = other:getCollider() - -- technically will always return a triangle ID if there's a mesh collider - if tri ~= nil and collider.type == ShapeType.MESH then - local mesh = collider:asMesh() - local drawCommand = mesh:fetchDrawCommand( tri ) - local instance = graph:getInstance( drawCommand.instanceID ) - local material = graph:getMaterial( instance.materialID ) - local materialName = graph:getMaterialName( instance.materialID ) - if not materialName:find("tools/") and event.normal.y < -0.7 then - local soundKey = getSurfaceSound(materialName) - playSound("valve://sound/" .. soundKey .. ".wav", false) - end +ent:addHook( "entity:Use.%UID%", function( payload ) + if payload.user ~= ent:uid() then return end + + local validUse = false + if heldObject.uid == 0 and payload.depth > 0 then + local prop = entities.get( payload.uid ) + if prop:getComponent("Metadata")["holdable"] then + validUse = true + heldObject.uid = payload.uid + heldObject.distance = (ent:getComponent("Transform").position - prop:getComponent("Transform"):flatten().position):norm() + prop:getComponent("PhysicsBody"):enableGravity(false) + else + validUse = not string.matched( prop:name(), "/^worldspawn/" ) end + elseif heldObject.uid ~= 0 then + validUse = true + local prop = entities.get( heldObject.uid ) + prop:getComponent("PhysicsBody"):enableGravity(true) + heldObject.uid = 0 end -end -]] + playUiSound(validUse and "select" or "deny") +end ) -- on tick ent:bind( "tick", function(self) - local inControl = scene:globalFindByName("Gui: Menu"):uid() == 0 + local inputs = ent:getComponent("PlayerInputBehavior::Metadata") + if not inputs or not inputs.control then return end - local inputs = { - E = inputs.key("E") or inputs.key("R_Y"), - F = inputs.key("F"), - mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER"), - mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER"), - mouse3 = inputs.key("Mouse3"), - wheel = inputs.analog("MouseWheel"), - } - - if not inControl then - inputs["E"] = false - inputs["F"] = false - inputs["mouse1"] = false - inputs["mouse2"] = false - inputs["mouse3"] = false - inputs["wheel"] = 0 - end - - -- eye transform - local flattenedTransform = fixedCamera and transform:flatten() or cameraTransform:flatten() + local flattenedTransform = fixedCamera and ent:getComponent("Transform"):flatten() or camera:getTransform():flatten() local axes = flattenedTransform:axes() -- update flashlight tickFlashlight( flattenedTransform, axes, inputs ) - -- update use - tickUse( flattenedTransform, axes, inputs ) - -- update HOLP tickGravGun( flattenedTransform, axes, inputs ) -- play footsteps - tickFootsteps() -end ) - --- on use -ent:addHook( "entity:Use.%UID%", function( payload ) - if payload.user ~= ent:uid() then return end - onUse( payload ) + tickFootsteps( inputs ) end ) \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index 14e178a3..99bf107d 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -1,7 +1,8 @@ { "import": "./base_sourceengine.json", "assets": [ - { "filename": "./maps/mcdonalds-mds.bsp" } - // { "filename": "./maps/mcdonalds-mds/graph.json" } + // { "filename": "./maps/mcdonalds-mds.bsp" } + { "filename": "./maps/mcdonalds-mds/graph.json" }, + { "filename": "ent://burger.json", "delay": 1 } ] } \ No newline at end of file diff --git a/engine/inc/uf/ext/lua/lua.h b/engine/inc/uf/ext/lua/lua.h index 18ea190e..82892737 100644 --- a/engine/inc/uf/ext/lua/lua.h +++ b/engine/inc/uf/ext/lua/lua.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace pod { struct UF_API LuaScript { @@ -57,8 +58,8 @@ namespace ext { sol::table createTable(); uf::stl::string sanitize( const uf::stl::string& dirty, int index = -1 ); - std::optional encode( sol::table table ); - std::optional decode( const uf::stl::string& string ); + std::optional encode( sol::table table ); + std::optional decode( const ext::json::Value& string ); } } diff --git a/engine/src/engine/ext/player/behavior.cpp b/engine/src/engine/ext/player/behavior.cpp index f1c0c663..6fc5e1da 100644 --- a/engine/src/engine/ext/player/behavior.cpp +++ b/engine/src/engine/ext/player/behavior.cpp @@ -1,828 +1,17 @@ -#include "behavior.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "../scene/behavior.h" -// #include "../../gui/manager/behavior.h" - -#define ONE_OVER_SIXTY 0.016666f - -UF_BEHAVIOR_REGISTER_CPP(ext::PlayerBehavior) -UF_BEHAVIOR_TRAITS_CPP(ext::PlayerBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName) -#define this (&self) -void ext::PlayerBehavior::initialize( uf::Object& self ) { - auto& transform = this->getComponent>(); - - auto& metadata = this->getComponent(); - auto& metadataJson = this->getComponent(); - - auto& camera = this->getComponent(); - auto& cameraTransform = camera.getTransform(); - - auto& scene = uf::scene::getCurrentScene(); - { - camera.setStereoscopic(true); - - cameraTransform.position = uf::vector::decode( metadataJson["camera"]["position"], cameraTransform.position ); - cameraTransform.scale = uf::vector::decode( metadataJson["camera"]["scale"], cameraTransform.scale ); - cameraTransform.orientation = uf::vector::decode( metadataJson["camera"]["orientation"], cameraTransform.orientation ); - - cameraTransform.reference = metadata.camera.fixed ? NULL : &transform; - - auto cameraSettingsJson = metadataJson["camera"]["settings"]; - - metadata.camera.offset = uf::vector::decode( metadataJson["camera"]["offset"], metadata.camera.offset ); - - if ( metadataJson["camera"]["ortho"].as() ) { - float l = cameraSettingsJson["left"].as(); - float r = cameraSettingsJson["right"].as(); - float b = cameraSettingsJson["bottom"].as(); - float t = cameraSettingsJson["top"].as(); - float n = cameraSettingsJson["near"].as(); - float f = cameraSettingsJson["far"].as(); - - camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) ); - } else { - float fov = cameraSettingsJson["fov"].as(120) * (3.14159265358f / 180.0f); - pod::Vector2f range = uf::vector::decode( cameraSettingsJson["clip"], pod::Vector2f{ 0.1, 64.0f } ); - pod::Vector2ui size = uf::vector::decode( cameraSettingsJson["size"], pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height } ); - float raidou = (float) size.x / (float) size.y; - - if ( size.x == 0 || size.y == 0 ) { - size = uf::vector::decode( uf::config["window"]["size"], pod::Vector2ui{} ); - raidou = (float) size.x / (float) size.y; - #if 0 - this->addHook( "window:Resized", [&, fov, range](pod::payloads::windowResized& payload){ - float width = uf::renderer::settings:: - float raidou = (float) payload.window.size.x / (float) payload.window.size.y; - camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) ); - } ); - #endif - } - camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) ); - } - camera.update(); - } - - // sloppy - metadata.mouse.sensitivity = uf::vector::decode( uf::config["window"]["mouse"]["sensitivity"], metadata.mouse.sensitivity ); - metadata.mouse.smoothing = uf::vector::decode( uf::config["window"]["mouse"]["smoothing"], metadata.mouse.smoothing ); - - this->addHook( "window:Mouse.CursorVisibility", [&](pod::payloads::windowMouseCursorVisibility& payload){ - metadata.system.control = !payload.mouse.visible; - }); - this->addHook( "system:Control.%UID%", [&]( ext::json::Value& value ){ - metadata.system.control = value["control"].as(!metadata.system.control); - }); - - // Rotate Camera -#if !UF_INPUT_USE_ENUM_MOUSE - #if !UF_ENV_DREAMCAST - this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload ){ - const pod::Vector2ui deadZone{0, 0}; - if ( (payload.mouse.delta.x == 0 && payload.mouse.delta.y == 0) || !metadata.system.control ) return; - - if (abs(payload.mouse.delta.x) > deadZone.x) metadata.mouse.accum.x += payload.mouse.delta.x * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/ / payload.window.size.x; - if (abs(payload.mouse.delta.y) > deadZone.y) metadata.mouse.accum.y += payload.mouse.delta.y * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/ / payload.window.size.y; - }); - #endif -#endif - -#if UF_USE_DISCORD - // Discord Integration - this->addHook( "discord:Activity.Update.%UID%", [&](ext::json::Value& json){ - uf::stl::string leaderId = metadataJson[""]["party"][0].as(); - uf::Serializer cardData = masterDataGet("Card", leaderId); - uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].as()); - uf::stl::string leader = charaData["name"].as(); - - ext::json::Value payload = json; - payload["details"] = "Leader: " + leader; - uf::hooks.call( "discord:Activity.Update", payload ); - }); - this->queueHook("discord:Activity.Update.%UID%", ext::json::null(), 1.0); -#endif - - { - ext::json::Value payload; - payload["uid"] = this->getUid(); - this->queueHook("controller:Ready", payload, 0.0f ); - } - - UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); -} -void ext::PlayerBehavior::tick( uf::Object& self ) { - auto& transform = this->getComponent>(); - - auto& camera = this->getComponent(); - auto& cameraTransform = camera.getTransform(); - - auto cameraAxes = uf::transform::axes( cameraTransform ); - auto axes = uf::transform::axes( transform ); - - auto& scene = uf::scene::getCurrentScene(); - auto& graph = scene.getComponent(); - - auto& metadata = this->getComponent(); - auto& metadataJson = this->getComponent(); - - auto& physicsBody = this->getComponent(); - -#if UF_ENTITY_METADATA_USE_JSON - metadata.deserialize(self, metadataJson); -#endif - - struct { - bool forward = false; - bool backwards = false; - bool left = false; - bool right = false; - - bool lookLeft = false; - bool lookRight = false; - bool lookUp = false; - bool lookDown = false; - bool running = false; - bool walk = false; - bool jump = false; - bool crouch = false; - bool paused = false; - bool console = false; - bool vee = false; - bool use = false; - } keys; - - - ext::PlayerBehavior::Metadata::States stats; - - struct { - float move = 4; - float walk = 1; - float run = 8; - float rotate = 1; - float friction = 0.8f; - float air = 1.0f; - } speed; - - if ( uf::Window::focused ) { - keys = { - .forward = uf::inputs::kbm::states::W, - .backwards = uf::inputs::kbm::states::S, - .left = uf::inputs::kbm::states::A, - .right = uf::inputs::kbm::states::D, - .lookLeft = uf::inputs::kbm::states::Left, - .lookRight = uf::inputs::kbm::states::Right, - .lookUp = uf::inputs::kbm::states::Up, - .lookDown = uf::inputs::kbm::states::Down, - .running = uf::inputs::kbm::states::LShift, - .walk = uf::inputs::kbm::states::LAlt, - .jump = uf::inputs::kbm::states::Space, - .crouch = uf::inputs::kbm::states::LControl, - .paused = uf::inputs::kbm::states::Escape, - .console = uf::inputs::kbm::states::Tilde, - .vee = uf::inputs::kbm::states::V, - .use = uf::inputs::kbm::states::E, - }; - if ( spec::controller::connected() ) { - #if UF_USE_OPENVR - if ( uf::inputs::controller::states::R_DPAD_UP ) keys.forward = true; - if ( uf::inputs::controller::states::R_DPAD_DOWN ) keys.backwards = true; - if ( uf::inputs::controller::states::R_DPAD_LEFT ) keys.lookLeft = true; // keys.left = true; - if ( uf::inputs::controller::states::R_DPAD_RIGHT ) keys.lookRight = true; // keys.right = true; - if ( uf::inputs::controller::states::R_JOYSTICK ) keys.running = true; - if ( uf::inputs::controller::states::R_A ) keys.jump = true; - - if ( uf::inputs::controller::states::L_DPAD_UP ) keys.forward = true; - if ( uf::inputs::controller::states::L_DPAD_DOWN ) keys.backwards = true; - if ( uf::inputs::controller::states::L_DPAD_LEFT ) keys.lookLeft = true; - if ( uf::inputs::controller::states::L_DPAD_RIGHT ) keys.lookRight = true; - if ( uf::inputs::controller::states::L_JOYSTICK ) keys.crouch = true, keys.walk = true; - if ( uf::inputs::controller::states::L_A ) keys.paused = true; - #else - if ( uf::inputs::controller::states::L_DPAD_UP ) keys.forward = true; - if ( uf::inputs::controller::states::L_DPAD_DOWN ) keys.backwards = true; - if ( uf::inputs::controller::states::L_DPAD_LEFT ) keys.left = true; - if ( uf::inputs::controller::states::L_DPAD_RIGHT ) keys.right = true; - if ( uf::inputs::controller::states::A ) keys.jump = true; - if ( uf::inputs::controller::states::B ) keys.running = true; - if ( uf::inputs::controller::states::X ) keys.crouch = true, keys.walk = true; - // if ( uf::inputs::controller::states::Y ) keys.vee = true; - if ( uf::inputs::controller::states::Y ) keys.use = true; - if ( uf::inputs::controller::states::L_TRIGGER ) keys.lookLeft = true; - if ( uf::inputs::controller::states::R_TRIGGER ) keys.lookRight = true; - if ( uf::inputs::controller::states::START ) keys.paused = true; - #endif - float deadzone = 0.01f; - if ( uf::inputs::controller::states::L_JOYSTICK.x < -deadzone ) { - keys.left = true; - speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - } else if ( uf::inputs::controller::states::L_JOYSTICK.x > deadzone ) { - keys.right = true; - speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.x); - } - - if ( uf::inputs::controller::states::L_JOYSTICK.y < -deadzone ) { - keys.forward = true; - speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - } else if ( uf::inputs::controller::states::L_JOYSTICK.y > deadzone ) { - keys.backwards = true; - speed.move *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - speed.run *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - speed.walk *= abs(uf::inputs::controller::states::L_JOYSTICK.y); - } - } - } - -#if 1 - if ( uf::renderer::states::resized && uf::renderer::settings::width > 0 && uf::renderer::settings::height > 0 ) { - auto cameraSettingsJson = metadataJson["camera"]["settings"]; - - float fov = cameraSettingsJson["fov"].as(120) * (3.14159265358f / 180.0f); - float raidou = (float) uf::renderer::settings::width / (float) uf::renderer::settings::height; - pod::Vector2f range = uf::vector::decode( cameraSettingsJson["clip"], pod::Vector2f{ 0.1, 64.0f } ); - - camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) ); - } -#endif - - bool wasFloored = stats.floored; - stats.menu = metadata.system.menu; - stats.noclipped = metadata.system.noclipped; - stats.floored = stats.noclipped; - if ( !stats.floored ) { - if ( physicsBody.activity.grounded ) { - stats.floored = true; - } - /* - pod::Vector3f origin = transform.position + metadata.movement.floored.feet; - pod::Vector3f direction = metadata.movement.floored.floor; - pod::RayQuery query = uf::physics::rayCast( pod::Ray{origin, direction}, physicsBody, 1.0f ); - - if ( query.hit ) { - if ( metadata.movement.floored.print ) UF_MSG_DEBUG("{}: {} | {}", query.contact.penetration, uf::string::toString(*query.body->object), uf::vector::toString(physicsBody.velocity)); - stats.floored = true; - //if ( physicsBody.velocity.y < 0.0f ) physicsBody.velocity.y = 0.0f; - } - */ - } -#if 0 - TIMER(0.25, keys.use ) { - size_t uid = 0; - float depth = -1; - uf::Object* pointer = NULL; - float length = metadata.use.length; - // pod::Vector3f center = transform.position + cameraTransform.position; - // pod::Vector3f direction = uf::vector::normalize( axes.forward + pod::Vector3f{ 0, cameraAxes.forward.y, 0 } ) * length; - auto flattened = uf::transform::flatten( cameraTransform ); - pod::Vector3f center = flattened.position; - pod::Vector3f direction = flattened.forward * length; - - pointer = uf::physics::rayCast( center, direction, this, depth ); - ext::json::Value payload; - if ( pointer ) { - payload["user"] = this->getUid(); - payload["uid"] = pointer->getUid(); - payload["depth"] = uf::vector::norm(direction * depth); - pointer->lazyCallHook( "entity:Use.%UID%", payload ); - } else { - payload["user"] = this->getUid(); - payload["uid"] = 0; - payload["depth"] = -1; - } - this->lazyCallHook( "entity:Use.%UID%", payload ); - /* - auto& emitter = this->getComponent(); - uf::stl::string filename = pointer ? "./ui/select.ogg" : "./ui/deny.ogg"; - uf::Audio& sfx = emitter.has(filename) ? emitter.get(filename) : emitter.load(filename); - - bool playing = false; - if ( !sfx.playing() ) { - #if UF_AUDIO_MAPPED_VOLUMES - sfx.setVolume(uf::audio::volumes.count("sfx") > 0 ? uf::audio::volumes.at("sfx") : 1.0); - #else - sfx.setVolume(uf::audio::volumes::sfx); - #endif - sfx.setPosition( transform.position ); - sfx.setTime( 0 ); - sfx.play(); - } - */ - } -#endif - - if ( physicsBody.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true; - - { - speed.rotate = metadata.movement.rotate * uf::physics::time::delta; - speed.move = metadata.movement.move; - speed.run = metadata.movement.run; - speed.walk = metadata.movement.walk; - speed.friction = metadata.movement.friction; - speed.air = metadata.movement.air; - - if ( stats.noclipped ) { - speed.move *= 1.5; - speed.run *= 1.5; - } - if ( !stats.floored || stats.noclipped ) speed.friction = 1; - if ( stats.noclipped ) physicsBody.velocity = {}; - } - if ( keys.running ) speed.move = speed.run; - else if ( keys.walk ) speed.move = speed.walk; - - { - uf::Object* menu = (uf::Object*) scene.globalFindByName("Gui: Menu"); - if ( !menu ) stats.menu = ""; - // make assumptions - if ( !metadata.system.control ) { - } else if ( stats.menu == "" && keys.paused ) { - stats.menu = "paused"; - metadata.system.control = false; - - pod::payloads::menuOpen payload; - payload.name = "pause"; - uf::hooks.call("menu:Open", payload); - /* - } else if ( stats.menu == "" && keys.dialogue ) { - stats.menu = "dialogue"; - metadata.system.control = false; - pod::payloads::menuOpen payload; - payload.name = "dialogue"; - payload.metadata["dialogue"]["name"] = "Test"; - payload.metadata["dialogue"]["text"] = "The quick brown fox\njumped over the lazy dog."; - payload.metadata["dialogue"]["name color"] = uf::vector::encode( pod::Vector4f{ 0.8, 1.0, 0.8, 1.0 } ); - payload.metadata["dialogue"]["text color"] = uf::vector::encode( pod::Vector4f{ 0.8, 1.0, 0.8, 1.0 } ); - uf::hooks.call("menu:Open", payload); - // stats.menu = "menu"; - */ - } else { - metadata.system.control = stats.menu == ""; - } - metadata.system.menu = stats.menu; - } - - pod::Axes translator = uf::transform::axes( transform ); -#if UF_USE_OPENVR - // use the orientation of our controller to determine our target - /*if ( ext::openvr::context ) { - bool useController = true; - translator.orientation = uf::quaternion::multiply( transform.orientation, useController ? ext::openvr::controllerQuaternion( vr::Controller_Hand::Hand_Right ) : ext::openvr::hmdQuaternion() ); - translator = uf::transform::reorient( translator ); - - // flatten if not noclipped - if ( !stats.noclipped ) { - translator.forward *= { 1, 0, 1 }; - translator.right *= { 1, 0, 1 }; - } - - translator.forward = uf::vector::normalize( translator.forward ); - translator.right = uf::vector::normalize( translator.right ); - } else*/ -#endif - // un-flatted if noclipped - - if ( metadata.camera.fixed ) { - translator = uf::transform::axes( cameraTransform ); - translator.forward.y = 0; - translator.forward = uf::vector::normalize( translator.forward ); - } - else if ( stats.noclipped || physicsBody.gravity == pod::Vector3f{0,0,0} ){ - translator.forward.y += cameraAxes.forward.y; - translator.forward = uf::vector::normalize( translator.forward ); - } - - if ( metadata.system.control ) { - // noclip handler - TIMER(0.25, keys.vee ) { - bool state = !stats.noclipped; - metadata.system.noclipped = state; - if ( !state ) { - uf::physics::setGravity( physicsBody ); - uf::physics::setColliderCategory( physicsBody, "PLAYER"); - uf::physics::setColliderMask( physicsBody, "PLAYER"); - } else { - uf::physics::setGravity( physicsBody, pod::Vector3f{0,0,0}); - uf::physics::setColliderCategory( physicsBody, "NONE"); - uf::physics::setColliderMask( physicsBody, "NONE"); - } - - stats.noclipped = state; - UF_MSG_DEBUG( "{}abled noclip: {}", (state ? "En" : "Dis"), uf::vector::toString(transform.position)); - } - // movement handler - // setup desired direction - pod::Vector3f target = {}; - if ( keys.forward ^ keys.backwards ) target += translator.forward * (keys.forward ? 1 : -1); - if ( keys.left ^ keys.right ) target += translator.right * (keys.right ? 1 : -1); - target = uf::vector::normalize( target ); - - physicsBody.velocity *= { speed.friction, 1, speed.friction }; - - stats.walking = (keys.forward ^ keys.backwards) || (keys.left ^ keys.right); - stats.running = keys.running; - - if ( stats.walking ) { - float factor = stats.floored ? 1.0f : speed.air; - if ( stats.noclipped ) { - physicsBody.velocity += target * speed.move * 50 * ONE_OVER_SIXTY; - } else { - physicsBody.velocity += target * std::clamp( speed.move * factor - uf::vector::dot( physicsBody.velocity, target ), 0.0f, speed.move * 10 * ONE_OVER_SIXTY /*uf::physics::time::delta*/ ); - } - - auto dot = uf::vector::dot( axes.forward, target ); - if ( !metadata.movement.strafe && dot < 1.0f ) { - // auto cross = uf::vector::normalize( uf::vector::cross( axes.forward, target ) ); - // auto axis = cross == pod::Vector3f{0, 0, 0} ? axes.up : cross; - auto axis = axes.up; - float angle = uf::vector::signedAngle( axes.forward, target, axis ) * ONE_OVER_SIXTY /*uf::physics::time::delta*/ * 4; // speed.rotate; - - if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axis, angle ); else - uf::transform::rotate( transform, axis, angle ); - } - } - if ( !stats.floored ) stats.walking = false; - } - TIMER(0.0625, stats.floored && keys.jump && !stats.noclipped ) { - physicsBody.velocity += translator.up * metadata.movement.jump; - } - if ( stats.floored && keys.jump && stats.noclipped ) transform.position += translator.up * metadata.movement.jump * uf::physics::time::delta * 4.0f; - if ( keys.crouch ) { - if ( stats.noclipped ) transform.position -= translator.up * metadata.movement.jump * uf::physics::time::delta * 4.0f; - else { - if ( !metadata.system.crouching ) stats.deltaCrouch = true; - metadata.system.crouching = true; - } - } else { - if ( metadata.system.crouching ) stats.deltaCrouch = true; - metadata.system.crouching = false; - } - - // - #if UF_INPUT_USE_ENUM_MOUSE && !UF_ENV_DREAMCAST - { - const pod::Vector2ui deadZone{0, 0}; - const auto& mouseDelta = uf::inputs::kbm::states::Mouse; - bool shouldnt = (mouseDelta.x == 0 && mouseDelta.y == 0) || !metadata.system.control || metadata.camera.fixed; - if ( !shouldnt ) { - if (abs(mouseDelta.x) > deadZone.x) metadata.mouse.accum.x += mouseDelta.x * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/; - if (abs(mouseDelta.y) > deadZone.y) metadata.mouse.accum.y += mouseDelta.y * (ONE_OVER_SIXTY)/* uf::physics::time::delta*/; - } - } - #endif - if ( !metadata.camera.fixed ) { - if ( metadata.mouse.accum.x != 0 && metadata.mouse.accum.y != 0 ) { - metadata.camera.queued.x += metadata.mouse.accum.x * metadata.mouse.sensitivity.x; - metadata.camera.queued.y += metadata.mouse.accum.y * metadata.mouse.sensitivity.y; - - metadata.mouse.accum = {}; - } - - - if ( metadata.camera.queued.x != 0 || metadata.camera.queued.y != 0 ) { - auto lookDelta = metadata.camera.queued; - metadata.camera.queued -= lookDelta * metadata.mouse.smoothing; - //metadata.camera.queued = {}; - - if ( lookDelta.x != 0 ) { - if ( metadata.camera.invert.x ) lookDelta.x *= -1; - metadata.camera.limit.current.x += lookDelta.x; - if ( metadata.camera.limit.current.x != metadata.camera.limit.current.x || ( metadata.camera.limit.current.x < metadata.camera.limit.max.x && metadata.camera.limit.current.x > metadata.camera.limit.min.x ) ) { - if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, lookDelta.x ); else - uf::transform::rotate( transform, axes.up, lookDelta.x ); - } else metadata.camera.limit.current.x -= lookDelta.x; - } - if ( lookDelta.y != 0 ) { - if ( metadata.camera.invert.y ) lookDelta.y *= -1; - metadata.camera.limit.current.y += lookDelta.y; - if ( metadata.camera.limit.current.y != metadata.camera.limit.current.y || ( metadata.camera.limit.current.y < metadata.camera.limit.max.y && metadata.camera.limit.current.y > metadata.camera.limit.min.y ) ) { - // if ( physicsBody.object && !physicsBody.shared ) uf::physics::applyRotation( physicsBody, cameraAxes.right, lookDelta.y ); else - uf::transform::rotate( cameraTransform, cameraAxes.right, lookDelta.y ); - } else metadata.camera.limit.current.y -= lookDelta.y; - } - } else if ( metadata.system.control ) { - if ( keys.lookRight ^ keys.lookLeft ) { - if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else - uf::transform::rotate( transform, axes.up, speed.rotate * (keys.lookRight ? 1 : -1) ); - } - if ( keys.lookUp ^ keys.lookDown ) { - float direction = keys.lookUp ? 1 : -1; - if ( metadata.camera.invert.y ) direction *= -1; - uf::transform::rotate( cameraTransform, cameraAxes.right, speed.rotate * direction ); - } - } - } else { - if ( keys.lookRight ^ keys.lookLeft ) { - // auto rotation = uf::quaternion::axisAngle( cameraAxes.up, uf::physics::time::delta * (keys.lookRight ? 1 : -1) ); - // cameraTransform.position = uf::quaternion::rotate( rotation, cameraTransform.position - transform.position ); - } - if ( keys.lookUp ^ keys.lookDown ) { - // if ( physicsBody.object && !physicsBody.shared ) uf::physics::applyRotation( physicsBody, cameraAxes.right, lookDelta.y ); else - float direction = keys.lookUp ? 1 : -1; - if ( metadata.camera.invert.y ) direction *= -1; - uf::transform::rotate( cameraTransform, cameraAxes.right, speed.rotate * direction ); - } - } - { - if ( physicsBody.object ) uf::physics::setVelocity( physicsBody, physicsBody.velocity ); else - transform.position += physicsBody.velocity * ONE_OVER_SIXTY /*uf::physics::time::delta*/; - // if ( uf::vector::magnitude( physicsBody.velocity ) > 1.0e-6 ) UF_MSG_DEBUG("Velocity: {}", uf::vector::toString( physicsBody.velocity )); - } - - - if ( metadata.camera.fixed ) { - cameraTransform.reference = NULL; - cameraTransform.position = transform.position + metadata.camera.offset; - // cameraTransform.orientation = uf::vector::decode( metadataJson["camera"]["orientation"], uf::quaternion::identity() ); - cameraAxes = uf::transform::axes( cameraTransform ); - } else { - if ( metadata.camera.offset != pod::Vector3f{0,0,0} ) { - //auto flattened = uf::transform::flatten( cameraTransform ); - auto& flattened = transform; - metadata.camera.intermediary.position = uf::quaternion::rotate( flattened.orientation, metadata.camera.offset ); - - metadata.camera.intermediary.reference = &transform; - cameraTransform.reference = &metadata.camera.intermediary; - } - if ( stats.deltaCrouch ) { - float delta = metadata.movement.crouch; - if ( metadata.system.crouching ) cameraTransform.position.y -= delta; - else cameraTransform.position.y += delta; - } - } - -#if 0 - { - int count = 0; - auto events = uf::physics::getCollisionEvents( physicsBody ); - for ( const auto& event : events ) { - // do something - } - UF_MSG_DEBUG("count={}", events.size()); - } -#endif - - metadata.states = stats; - metadata.states.crouching = metadata.system.crouching; - -#if 1 && UF_USE_OPENAL - if ( false && stats.floored && !stats.noclipped ) { - if ( stats.walking ) { - auto& emitter = this->getComponent(); - metadata.audio.footstep.timer -= uf::physics::time::delta; - if ( metadata.audio.footstep.timer <= 0.0f ) { - // player/footsteps - static uf::stl::vector surfaces = { - "chainlink", - "concrete", - "dirt", - "duct", - "grass", - "gravel", - "ladder", - "metal", - "metalgrate", - "mud", - "sand", - "slosh", - "tile", - "wade", - "wood", - "woodpanel", - }; - uf::stl::string surface = "concrete"; - - auto events = uf::physics::getCollisionEvents( physicsBody ); - for ( const auto& event : events ) { - if ( event.normal.y > -0.7f ) continue; // to-do: reorient? - auto materialName = uf::physics::getCollisionMaterialName( event ); - - for ( auto& key : surfaces ) { - if ( !uf::string::contains( materialName, key ) ) continue; - surface = key; - break; - } - break; - } - - uf::stl::string filename = ::fmt::format("valve://sound/player/footsteps/{}{}.wav", surface, (rand() % 3) + 1 ); - if ( !uf::asset::has(filename) ) { - auto payload = uf::asset::resolveToPayload(filename, ""); - payload.type = uf::asset::Type::AUDIO; - uf::asset::load( payload ); - } - pod::AudioClip* clip = &uf::asset::get( filename ); - - pod::AudioSource& footstep = emitter.emit(filename, clip, false); - - float pitch = 0.95f + ((rand() % 11) / 100.0f); - float volume = metadata.audio.footstep.volume; - - if ( metadata.system.crouching ) { - volume *= 0.5f; - } else if ( keys.running ) { - volume *= 1.0f; - } - - uf::audio::pitch( footstep, pitch ); - uf::audio::gain( footstep, volume ); - uf::audio::position( footstep, transform.position ); - uf::audio::play( footstep ); - - if ( keys.running ) { - metadata.audio.footstep.timer = 0.3f; - } else if ( metadata.system.crouching ) { - metadata.audio.footstep.timer = 0.6f; - } else { - metadata.audio.footstep.timer = 0.45f; - } - } - // set animation to walk - stats.targetAnimation = "walk"; - } else if ( !keys.jump ) { - stats.targetAnimation = "idle"; - metadata.audio.footstep.timer = 0; - } - } -#endif -#if !UF_ENV_DREAMCAST - // set animation to idle - if ( stats.targetAnimation != "" ) { - auto* playerModel = (uf::Object*) this->findByName("Player: Model"); - if ( playerModel && playerModel->hasComponent() ) { - pod::payloads::QueueAnimationPayload payload; - - payload.name = stats.targetAnimation; - playerModel->queueHook("animation:Set.%UID%", payload); - stats.targetAnimation = ""; - } - /* - if ( playerModel && playerModel->hasComponent() ) { - auto& graph = playerModel->getComponent(); - uf::graph::queueAnimation( graph, stats.targetAnimation ); - } - */ - /* - if ( playerModel && playerModel->hasComponent() ) { - auto& graph = playerModel->getComponent(); - bool should = true; - if ( graph.sequence.empty() && graph.sequence.front() == stats.targetAnimation ) should = false; - if ( should ) { - graph.settings.animations.loop = true; - uf::graph::animate( graph, stats.targetAnimation ); - } - } - */ - } -#endif -#if 0 && UF_USE_LUA && !UF_ENV_DREAMCAST - #define TRACK_ORIENTATION(ORIENTATION) {\ - static pod::Quaternion<> storedCameraOrientation = ORIENTATION;\ - const pod::Quaternion<> prevCameraOrientation = storedCameraOrientation;\ - const pod::Quaternion<> curCameraOrientation = ORIENTATION;\ - const pod::Quaternion<> deltaOrientation = uf::quaternion::multiply( curCameraOrientation, uf::quaternion::inverse( prevCameraOrientation ) ) ;\ - const pod::Vector3f deltaAngles = uf::quaternion::eulerAngles( deltaOrientation );\ - combinedDeltaAngles = uf::vector::add( combinedDeltaAngles, deltaAngles );\ - combinedDeltaOrientation = uf::quaternion::multiply( deltaOrientation, combinedDeltaOrientation );\ - storedCameraOrientation = ORIENTATION;\ - } - // this causes bigly memory leaks - { - pod::Quaternion<> combinedDeltaOrientation = {0,0,0,1}; - pod::Vector3f combinedDeltaAngles = {}; - TRACK_ORIENTATION(transform.orientation); - TRACK_ORIENTATION(camera.getTransform().orientation); - float magnitude = uf::quaternion::magnitude( combinedDeltaOrientation ); - if ( magnitude > 0.0001f ) { - ext::json::Value payload; - payload["delta"] = uf::vector::encode( combinedDeltaOrientation ); - payload["angle"]["pitch"] = combinedDeltaAngles.x; - payload["angle"]["yaw"] = combinedDeltaAngles.y; - payload["angle"]["roll"] = combinedDeltaAngles.z; - payload["magnitude"] = magnitude; - this->callHook("controller:Camera.Rotated", payload); - } - } -#endif - camera.update(); - -#if UF_ENTITY_METADATA_USE_JSON - metadata.serialize(self, metadataJson); -#endif +#include "./input/behavior.h" +#include "./movement/behavior.h" +#include "./interaction/behavior.h" +#include "./camera/behavior.h" +#include "./model/behavior.h" + +namespace ext { + class Player : public uf::Object { + }; } -void ext::PlayerBehavior::render( uf::Object& self ){} -void ext::PlayerBehavior::destroy( uf::Object& self ){} -void ext::PlayerBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ - auto& serializerSystem = serializer["system"]; - auto& serializerPhysics = serializer["physics"]; - auto& serializerMovement = serializer["movement"]; - auto& serializerAudioFootstep = serializer["audio"]["footstep"]; - auto& serializerCamera = serializer["camera"]; - auto& serializerCameraLimit = serializerCamera["limit"]; - auto& serializerCameraSettings = serializerCamera["settings"]; - - serializerSystem["menu"] = /*this->*/system.menu; - serializerSystem["control"] = /*this->*/system.control; - serializerSystem["crouching"] = /*this->*/system.crouching; - serializerSystem["noclipped"] = /*this->*/system.noclipped; - serializerPhysics["friction"] = /*this->*/movement.friction; - serializerMovement["rotate"] = /*this->*/movement.rotate; - serializerMovement["move"] = /*this->*/movement.move; - serializerMovement["run"] = /*this->*/movement.run; - serializerMovement["walk"] = /*this->*/movement.walk; - serializerMovement["air"] = /*this->*/movement.air; - serializerMovement["strafe"] = /*this->*/movement.strafe; - serializerMovement["jump"] = uf::vector::encode(/*this->*/movement.jump); - serializerMovement["floored"]["feet"] = uf::vector::encode(/*this->*/movement.floored.feet); - serializerMovement["floored"]["floor"] = uf::vector::encode(/*this->*/movement.floored.floor); - serializerMovement["floored"]["print"] = /*this->*/movement.floored.print; - serializerMovement["crouch"] = /*this->*/movement.crouch; -// serializerMovement["look"] = /*this->*/movement.look; - serializerAudioFootstep["list"] = /*this->*/audio.footstep.list; - serializerAudioFootstep["volume"] = /*this->*/audio.footstep.volume; - serializerCamera["invert"] = uf::vector::encode(/*this->*/camera.invert); - serializerCameraLimit["current"] = uf::vector::encode(/*this->*/camera.limit.current); - serializerCameraLimit["minima"] = uf::vector::encode(/*this->*/camera.limit.min); - serializerCameraLimit["maxima"] = uf::vector::encode(/*this->*/camera.limit.max); - serializerCameraSettings["fixed"] = camera.fixed; - - serializer["use"]["length"] = /*this->*/use.length; -} -void ext::PlayerBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ - auto& serializerSystem = serializer["system"]; - auto& serializerAudioFootstep = serializer["audio"]["footstep"]; - auto& serializerPhysics = serializer["physics"]; - auto& serializerMovement = serializer["movement"]; - auto& serializerCamera = serializer["camera"]; - auto& serializerCameraLimit = serializerCamera["limit"]; - auto& serializerCameraSettings = serializerCamera["settings"]; - - /*this->*/system.menu = serializerSystem["menu"].as(/*this->*/system.menu); - /*this->*/system.control = serializerSystem["control"].as(/*this->*/system.control); - /*this->*/system.crouching = serializerSystem["crouching"].as(/*this->*/system.crouching); - /*this->*/system.noclipped = serializerSystem["noclipped"].as(/*this->*/system.noclipped); - /*this->*/movement.friction = serializerPhysics["friction"].as(/*this->*/movement.friction); - /*this->*/movement.rotate = serializerMovement["rotate"].as(/*this->*/movement.rotate); - /*this->*/movement.move = serializerMovement["move"].as(/*this->*/movement.move); - /*this->*/movement.run = serializerMovement["run"].as(/*this->*/movement.run); - /*this->*/movement.walk = serializerMovement["walk"].as(/*this->*/movement.walk); - /*this->*/movement.air = serializerMovement["air"].as(/*this->*/movement.air); - /*this->*/movement.strafe = serializerMovement["strafe"].as(/*this->*/movement.strafe); - /*this->*/movement.jump = uf::vector::decode(serializerMovement["jump"], /*this->*/movement.jump); - /*this->*/movement.floored.feet = uf::vector::decode(serializerMovement["floored"]["feet"], /*this->*/movement.floored.feet); - /*this->*/movement.floored.floor = uf::vector::decode(serializerMovement["floored"]["floor"], /*this->*/movement.floored.floor); - /*this->*/movement.floored.print = serializerMovement["floored"]["print"].as(/*this->*/movement.floored.print); - /*this->*/movement.crouch = serializerMovement["crouch"].as(/*this->*/movement.crouch); -// /*this->*/movement.look = serializerMovement["look"].as(1.0f); - ext::json::forEach( serializerAudioFootstep["list"], [&]( const ext::json::Value& value ){ - /*this->*/audio.footstep.list.emplace_back(value); - }); - /*this->*/audio.footstep.volume = serializerAudioFootstep["volume"].as(); - - /*this->*/camera.invert = uf::vector::decode( serializerCamera["invert"], /*this->*/camera.invert ); - /*this->*/camera.limit.current = uf::vector::decode( serializerCameraLimit["current"], /*this->*/camera.limit.current ); - /*this->*/camera.limit.min = uf::vector::decode( serializerCameraLimit["minima"], /*this->*/camera.limit.min ); - /*this->*/camera.limit.max = uf::vector::decode( serializerCameraLimit["maxima"], /*this->*/camera.limit.max ); - /*this->*/camera.fixed = serializerCameraSettings["fixed"].as( /*this->*/camera.fixed ); - - /*this->*/use.length = serializer["use"]["length"].as(/*this->*/use.length); -} - -// yikes -#include -UF_LUA_REGISTER_USERTYPE(ext::PlayerBehavior::Metadata::States, - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::walking), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::running), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::crouching), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::floored), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::noclipped), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::deltaCrouch), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::menu), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::targetAnimation), - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::States::previous) -) - -UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerBehavior::Metadata, - UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerBehavior::Metadata::states) -) -#undef this \ No newline at end of file +UF_OBJECT_REGISTER_BEGIN(ext::Player) + UF_OBJECT_BIND_BEHAVIOR(ext::PlayerInputBehavior) + UF_OBJECT_BIND_BEHAVIOR(ext::PlayerMovementBehavior) + UF_OBJECT_BIND_BEHAVIOR(ext::PlayerInteractionBehavior) + UF_OBJECT_BIND_BEHAVIOR(ext::PlayerCameraBehavior) +UF_OBJECT_REGISTER_END() \ No newline at end of file diff --git a/engine/src/engine/ext/player/behavior.h b/engine/src/engine/ext/player/behavior.h deleted file mode 100644 index 6f230014..00000000 --- a/engine/src/engine/ext/player/behavior.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace ext { - namespace PlayerBehavior { - UF_BEHAVIOR_DEFINE_TYPE(); - EXT_BEHAVIOR_DEFINE_TRAITS(); - EXT_BEHAVIOR_DEFINE_FUNCTIONS(); - UF_BEHAVIOR_DEFINE_METADATA( - struct { - bool control = true; - uf::stl::string menu = ""; - bool crouching = false; - bool noclipped = false; - } system; - struct { - float crouch = -1.0f; - float rotate = 1.0f; - float move = 1.0f; - float run = 1.0f; - float walk = 1.0f; - float friction = 0.8f; - float air = 1.0f; - bool strafe = true; - pod::Vector3f jump = {0,8,0}; - struct { - pod::Vector3f feet = {0,-1.5,0}; - pod::Vector3f floor = {0,-1,0}; - bool print = false; - } floored; - } movement; - struct { - struct { - pod::Vector3f current = {NAN, NAN, NAN}; - pod::Vector3f min = {NAN, NAN, NAN}; - pod::Vector3f max = {NAN, NAN, NAN}; - } limit; - pod::Vector3t invert; - pod::Vector2f queued; - - pod::Vector3f offset; - pod::Transform<> intermediary; - - bool fixed = false; - } camera; - struct { - pod::Vector2f sensitivity = {1,1}; - pod::Vector2f smoothing = {0,0}; - - pod::Vector2f accum = {}; - } mouse; - struct { - struct { - uf::stl::vector list; - float volume = 1; - float timer = 0; - } footstep; - } audio; - struct { - float length = 4.0f; - } use; - struct States { - bool walking = false; - bool running = false; - bool crouching = false; - bool floored = true; - bool noclipped = false; - bool deltaCrouch = false; - uf::stl::string menu = ""; - uf::stl::string targetAnimation = ""; - - pod::Matrix4f previous; - } states; - ); - } -} \ No newline at end of file diff --git a/engine/src/engine/ext/player/camera/behavior.cpp b/engine/src/engine/ext/player/camera/behavior.cpp new file mode 100644 index 00000000..79f802fc --- /dev/null +++ b/engine/src/engine/ext/player/camera/behavior.cpp @@ -0,0 +1,224 @@ +#include "behavior.h" +#include "../input/behavior.h" +#include "../movement/behavior.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../scene/behavior.h" + +#define ONE_OVER_SIXTY 0.016666f + +UF_BEHAVIOR_REGISTER_CPP(ext::PlayerCameraBehavior) +UF_BEHAVIOR_TRAITS_CPP(ext::PlayerCameraBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName) +#define this (&self) + +void ext::PlayerCameraBehavior::initialize( uf::Object& self ) { + auto& transform = this->getComponent>(); + auto& metadata = this->getComponent(); + auto& metadataJson = this->getComponent(); + + auto& camera = this->getComponent(); + auto& cameraTransform = camera.getTransform(); + + camera.setStereoscopic(true); + + cameraTransform.position = uf::vector::decode(metadataJson["camera"]["position"], cameraTransform.position); + cameraTransform.scale = uf::vector::decode(metadataJson["camera"]["scale"], cameraTransform.scale); + cameraTransform.orientation = uf::vector::decode(metadataJson["camera"]["orientation"], cameraTransform.orientation); + + cameraTransform.reference = metadata.fixed ? NULL : &transform; + + auto cameraSettingsJson = metadataJson["camera"]["settings"]; + if ( metadataJson["camera"]["ortho"].as() ) { + float l = cameraSettingsJson["left"].as(); + float r = cameraSettingsJson["right"].as(); + float b = cameraSettingsJson["bottom"].as(); + float t = cameraSettingsJson["top"].as(); + float n = cameraSettingsJson["near"].as(); + float f = cameraSettingsJson["far"].as(); + + camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) ); + } else { + float fov = cameraSettingsJson["fov"].as(120) * (3.14159265358f / 180.0f); + pod::Vector2f range = uf::vector::decode(cameraSettingsJson["clip"], pod::Vector2f{0.1, 64.0f}); + pod::Vector2ui size = uf::vector::decode(cameraSettingsJson["size"], pod::Vector2ui{uf::renderer::settings::width, uf::renderer::settings::height}); + float raidou = (float) size.x / (float) size.y; + + if ( size.x == 0 || size.y == 0 ) { + size = uf::vector::decode(uf::config["window"]["size"], pod::Vector2ui{}); + raidou = (float) size.x / (float) size.y; + } + camera.setProjection( uf::matrix::perspective( fov, raidou, range.x, range.y ) ); + } + camera.update(); + + metadata.mouse.sensitivity = uf::vector::decode(uf::config["window"]["mouse"]["sensitivity"], metadata.mouse.sensitivity); + metadata.mouse.smoothing = uf::vector::decode(uf::config["window"]["mouse"]["smoothing"], metadata.mouse.smoothing); + + UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); +} + +void ext::PlayerCameraBehavior::tick( uf::Object& self ) { + auto& metadata = this->getComponent(); + auto& transform = this->getComponent>(); + auto& camera = this->getComponent(); + auto& cameraTransform = camera.getTransform(); + + auto axes = uf::transform::axes(transform); + auto cameraAxes = uf::transform::axes(cameraTransform); + + if ( uf::renderer::states::resized && uf::renderer::settings::width > 0 && uf::renderer::settings::height > 0 ) { + auto& metadataJson = this->getComponent(); + auto cameraSettingsJson = metadataJson["camera"]["settings"]; + + float fov = cameraSettingsJson["fov"].as(120) * (3.14159265358f / 180.0f); + float raidou = (float) uf::renderer::settings::width / (float) uf::renderer::settings::height; + pod::Vector2f range = uf::vector::decode(cameraSettingsJson["clip"], pod::Vector2f{0.1, 64.0f}); + + camera.setProjection(uf::matrix::perspective(fov, raidou, range.x, range.y)); + } + + if ( this->hasComponent() ) { + auto& input = this->getComponent(); + + if ( input.look.x != 0 && input.look.y != 0 ) { + metadata.queued.x += input.look.x * metadata.mouse.sensitivity.x; + metadata.queued.y += input.look.y * metadata.mouse.sensitivity.y; + + // Note: ensure you reset input.look = {0,0} at the start of PlayerInputBehavior::tick! + input.look = {0, 0}; + } + + if ( metadata.queued.x != 0 || metadata.queued.y != 0 ) { + auto lookDelta = metadata.queued; + metadata.queued -= lookDelta * metadata.mouse.smoothing; + + if ( lookDelta.x != 0 ) { + if ( metadata.invert.x ) lookDelta.x *= -1; + metadata.limit.current.x += lookDelta.x; + + if ( metadata.limit.current.x != metadata.limit.current.x || (metadata.limit.current.x < metadata.limit.max.x && metadata.limit.current.x > metadata.limit.min.x) ) { + if ( this->hasComponent() ) { + auto& physicsBody = this->getComponent(); + if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axes.up, lookDelta.x ); + else uf::transform::rotate( transform, axes.up, lookDelta.x ); + } else { + uf::transform::rotate(transform, axes.up, lookDelta.x); + } + } else metadata.limit.current.x -= lookDelta.x; + } + + if ( lookDelta.y != 0 ) { + if ( metadata.invert.y ) lookDelta.y *= -1; + metadata.limit.current.y += lookDelta.y; + + if ( metadata.limit.current.y != metadata.limit.current.y || (metadata.limit.current.y < metadata.limit.max.y && metadata.limit.current.y > metadata.limit.min.y) ) { + uf::transform::rotate( cameraTransform, cameraAxes.right, lookDelta.y ); + } else metadata.limit.current.y -= lookDelta.y; + } + } + } + + if ( metadata.fixed ) { + cameraTransform.reference = NULL; + cameraTransform.position = transform.position + metadata.offset; + } else { + if ( metadata.offset != pod::Vector3f{0,0,0} ) { + metadata.intermediary.position = uf::quaternion::rotate( transform.orientation, metadata.offset ); + metadata.intermediary.reference = &transform; + cameraTransform.reference = &metadata.intermediary; + } + if ( this->hasComponent() && this->hasComponent() ) { + auto& input = this->getComponent(); + auto& movement = this->getComponent(); + + float targetRoll = -input.movement.x * 0.02f; + metadata.viewRoll = std::lerp(metadata.viewRoll, targetRoll, 8.0f * ONE_OVER_SIXTY); + + float rollDelta = metadata.viewRoll - metadata.previousRoll; + metadata.previousRoll = metadata.viewRoll; + + uf::transform::rotate(cameraTransform, cameraAxes.forward, rollDelta); + + if ( this->hasComponent() ) { + auto& physicsBody = this->getComponent(); + float currentYVel = physicsBody.velocity.y; + + if ( movement.floored && !metadata.wasFloored ) { + if ( metadata.lastYVelocity < -4.0f ) { + metadata.punchVelocity += metadata.lastYVelocity * 0.025f; + } + } + + metadata.punchVelocity += (0.0f - metadata.viewPunch) * 0.2f; + metadata.punchVelocity *= 0.7f; + metadata.viewPunch += metadata.punchVelocity; + + metadata.viewPunch = std::clamp(metadata.viewPunch, -0.5f, 0.0f); + + float punchDelta = metadata.viewPunch - metadata.previousPunch; + metadata.previousPunch = metadata.viewPunch; + + float lerpSpeed = (metadata.stairOffset > 0.0f) ? 5.0f : 15.0f; + metadata.stairOffset = std::lerp(metadata.stairOffset, 0.0f, lerpSpeed * ONE_OVER_SIXTY); + + if ( std::abs(metadata.stairOffset) < 0.0001f ) metadata.stairOffset = 0.0f; + + float stairDelta = metadata.stairOffset - metadata.previousStairOffset; + metadata.previousStairOffset = metadata.stairOffset; + + cameraTransform.position.y += punchDelta + stairDelta; + + metadata.wasFloored = movement.floored; + metadata.lastYVelocity = currentYVel; + } + } + } + + + camera.update(); +} + +void ext::PlayerCameraBehavior::render( uf::Object& self ) {} +void ext::PlayerCameraBehavior::destroy( uf::Object& self ) {} +void ext::PlayerCameraBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ + serializer["camera"]["invert"] = uf::vector::encode(invert); + serializer["camera"]["limit"]["current"] = uf::vector::encode(limit.current); + serializer["camera"]["limit"]["minima"] = uf::vector::encode(limit.min); + serializer["camera"]["limit"]["maxima"] = uf::vector::encode(limit.max); + serializer["camera"]["settings"]["fixed"] = fixed; + serializer["camera"]["offset"] = uf::vector::encode(offset); +} +void ext::PlayerCameraBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ + invert = uf::vector::decode(serializer["camera"]["invert"], invert); + limit.current = uf::vector::decode(serializer["camera"]["limit"]["current"], limit.current); + limit.min = uf::vector::decode(serializer["camera"]["limit"]["minima"], limit.min); + limit.max = uf::vector::decode(serializer["camera"]["limit"]["maxima"], limit.max); + fixed = serializer["camera"]["settings"]["fixed"].as(fixed); + offset = uf::vector::decode(serializer["camera"]["offset"], offset); +} +#undef this + +#include +UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerCameraBehavior::Metadata, + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::fixed), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::offset), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::viewRoll), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::viewPunch), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::punchVelocity), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::lastYVelocity), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::wasFloored), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerCameraBehavior::Metadata::stairOffset) +) \ No newline at end of file diff --git a/engine/src/engine/ext/player/camera/behavior.h b/engine/src/engine/ext/player/camera/behavior.h new file mode 100644 index 00000000..4117dd1a --- /dev/null +++ b/engine/src/engine/ext/player/camera/behavior.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ext { + namespace PlayerCameraBehavior { + UF_BEHAVIOR_DEFINE_TYPE(); + EXT_BEHAVIOR_DEFINE_TRAITS(); + EXT_BEHAVIOR_DEFINE_FUNCTIONS(); + UF_BEHAVIOR_DEFINE_METADATA( + struct { + pod::Vector3f current = {NAN, NAN, NAN}; + pod::Vector3f min = {NAN, NAN, NAN}; + pod::Vector3f max = {NAN, NAN, NAN}; + } limit; + pod::Vector3t invert; + pod::Vector2f queued = {}; + + pod::Vector3f offset = {}; + pod::Transform<> intermediary; + + bool fixed = false; + + struct { + pod::Vector2f sensitivity = {1, 1}; + pod::Vector2f smoothing = {0, 0}; + } mouse; + + float viewRoll = 0.0f; + float previousRoll = 0.0f; + float viewPunch = 0.0f; + float previousPunch = 0.0f; + float punchVelocity = 0.0f; + float lastYVelocity = 0.0f; + bool wasFloored = true; + float stairOffset = 0.0f; + float previousStairOffset = 0.0f; + ); + } +} \ No newline at end of file diff --git a/engine/src/engine/ext/player/input/behavior.cpp b/engine/src/engine/ext/player/input/behavior.cpp new file mode 100644 index 00000000..2f8ca662 --- /dev/null +++ b/engine/src/engine/ext/player/input/behavior.cpp @@ -0,0 +1,145 @@ +#include "behavior.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ONE_OVER_SIXTY 0.016666f + +UF_BEHAVIOR_REGISTER_CPP(ext::PlayerInputBehavior) +UF_BEHAVIOR_TRAITS_CPP(ext::PlayerInputBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName) +#define this (&self) +void ext::PlayerInputBehavior::initialize(uf::Object& self) { + auto& transform = this->getComponent>(); + + auto& state = this->getComponent(); + auto& metadataJson = this->getComponent(); + + auto& scene = uf::scene::getCurrentScene(); + + this->addHook( "window:Mouse.CursorVisibility", [&](pod::payloads::windowMouseCursorVisibility& payload){ + state.control = !payload.mouse.visible; + }); + this->addHook( "system:Control.%UID%", [&]( ext::json::Value& value ){ + state.control = value["control"].as(!state.control); + }); + + ext::json::Value payload; + payload["uid"] = this->getUid(); + this->queueHook("controller:Ready", payload, 0.0f ); + + UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(state, metadataJson); +} + +void ext::PlayerInputBehavior::tick(uf::Object& self) { + auto& state = this->getComponent(); + + // reset state + state.movement = {}; + state.look = {}; + state.magnitude = 1.0f; + state.jump = false; + state.crouch = false; + state.run = false; + state.walk = false; + state.use = false; + state.noclipToggle = false; + state.menuToggle = false; + + // not in control + if ( !state.control ) return; + + if ( uf::Window::focused) { + if ( uf::inputs::kbm::states::W ) state.movement.y += 1.0f; + if ( uf::inputs::kbm::states::S ) state.movement.y -= 1.0f; + if ( uf::inputs::kbm::states::D ) state.movement.x += 1.0f; + if ( uf::inputs::kbm::states::A ) state.movement.x -= 1.0f; + + state.run = uf::inputs::kbm::states::LShift; + state.walk = uf::inputs::kbm::states::LAlt; + state.jump = uf::inputs::kbm::states::Space; + state.crouch = uf::inputs::kbm::states::LControl; + state.menuToggle = uf::inputs::kbm::states::Escape; + state.noclipToggle = uf::inputs::kbm::states::V; + state.use = uf::inputs::kbm::states::E; + } + + if ( spec::controller::connected() ) { + float deadzone = 0.01f; + auto stick = uf::inputs::controller::states::L_JOYSTICK; + + if ( abs(stick.x) > deadzone || abs(stick.y) > deadzone ) { + state.movement.x = stick.x; + state.movement.y = stick.y; + state.magnitude = uf::vector::norm( stick ); + } + + if ( uf::inputs::controller::states::A ) state.jump = true; + if ( uf::inputs::controller::states::B ) state.run = true; + if ( uf::inputs::controller::states::X ) { state.crouch = true; state.walk = true; } + if ( uf::inputs::controller::states::Y ) state.use = true; + if ( uf::inputs::controller::states::START ) state.menuToggle = true; + } + + if ( state.movement.x != 0 && state.movement.y != 0 && state.magnitude == 1.0f ) { + state.movement = uf::vector::normalize( state.movement ); + } + + { + auto& scene = uf::scene::getCurrentScene(); + auto* menu = scene.globalFindByName("Gui: Menu"); + if ( !menu ) state.menu = ""; + if ( state.menu == "" && state.menuToggle ) { + state.menu = "paused"; + state.control = false; + + pod::payloads::menuOpen payload; + payload.name = "pause"; + uf::hooks.call("menu:Open", payload); + } else { + state.control = state.menu == ""; + } + } + +#if UF_INPUT_USE_ENUM_MOUSE && !UF_ENV_DREAMCAST + const auto& mouseDelta = uf::inputs::kbm::states::Mouse; + state.look.x += mouseDelta.x * ONE_OVER_SIXTY; + state.look.y += mouseDelta.y * ONE_OVER_SIXTY; +#endif +} + +void ext::PlayerInputBehavior::render(uf::Object& self) {} +void ext::PlayerInputBehavior::destroy(uf::Object& self) {} +void ext::PlayerInputBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ + serializer["system"]["control"] = control; + serializer["system"]["menu"] = menu; +} +void ext::PlayerInputBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ + control = serializer["system"]["control"].as(control); + menu = serializer["system"]["menu"].as(menu); +} +#undef this + +#include +UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerInputBehavior::Metadata, + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::control), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::menu), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::movement), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::look), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::jump), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::crouch), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::run), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::walk), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::use), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerInputBehavior::Metadata::magnitude) +) \ No newline at end of file diff --git a/engine/src/engine/ext/player/input/behavior.h b/engine/src/engine/ext/player/input/behavior.h new file mode 100644 index 00000000..94b0340c --- /dev/null +++ b/engine/src/engine/ext/player/input/behavior.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ext { + namespace PlayerInputBehavior { + UF_BEHAVIOR_DEFINE_TYPE(); + EXT_BEHAVIOR_DEFINE_TRAITS(); + EXT_BEHAVIOR_DEFINE_FUNCTIONS(); + UF_BEHAVIOR_DEFINE_METADATA( + pod::Vector2f movement = {}; + pod::Vector2f look = {}; + + bool control = true; + uf::stl::string menu = ""; + + bool jump = false; + bool crouch = false; + bool run = false; + bool walk = false; + bool use = false; + bool noclipToggle = false; + bool menuToggle = false; + + float magnitude = 1.0f; + ); + }; +} \ No newline at end of file diff --git a/engine/src/engine/ext/player/interaction/behavior.cpp b/engine/src/engine/ext/player/interaction/behavior.cpp new file mode 100644 index 00000000..b92259a5 --- /dev/null +++ b/engine/src/engine/ext/player/interaction/behavior.cpp @@ -0,0 +1,72 @@ +#include "behavior.h" +#include "../input/behavior.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +UF_BEHAVIOR_REGISTER_CPP(ext::PlayerInteractionBehavior) +UF_BEHAVIOR_TRAITS_CPP(ext::PlayerInteractionBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName) +#define this (&self) + +void ext::PlayerInteractionBehavior::initialize(uf::Object& self) { + auto& metadata = this->getComponent(); + auto& metadataJson = this->getComponent(); + + UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS( metadata, metadataJson ); +} + +void ext::PlayerInteractionBehavior::tick(uf::Object& self) { + if (!this->hasComponent()) return; + if (!this->hasComponent()) return; // We need a physics body for the raycast! + + auto& input = this->getComponent(); + auto& metadata = this->getComponent(); + auto& physicsBody = this->getComponent(); + + TIMER(0.25, input.use) { + auto& camera = this->getComponent(); + auto cameraTransform = camera.getTransform(); + auto flattened = uf::transform::flatten(cameraTransform); + auto axes = uf::transform::axes( flattened ); + + pod::Vector3f center = flattened.position; + pod::Vector3f direction = axes.forward; + + pod::RayQuery query = uf::physics::rayCast( pod::Ray{center, direction}, physicsBody, metadata.length ); + + uf::Object* pointer = query.hit ? query.body->object : NULL; + float depth = query.hit ? query.contact.penetration : -1; + + ext::json::Value payload; + payload["user"] = this->getUid(); + payload["uid"] = pointer ? pointer->getUid() : 0; + payload["depth"] = depth; + + if ( pointer ) { + pointer->lazyCallHook("entity:Use.%UID%", payload); + } + this->lazyCallHook("entity:Use.%UID%", payload); + } +} + +void ext::PlayerInteractionBehavior::render(uf::Object& self) {} +void ext::PlayerInteractionBehavior::destroy(uf::Object& self) {} + +void ext::PlayerInteractionBehavior::Metadata::serialize(uf::Object& self, uf::Serializer& serializer) { + serializer["use"]["length"] = length; +} +void ext::PlayerInteractionBehavior::Metadata::deserialize(uf::Object& self, uf::Serializer& serializer) { + length = serializer["use"]["length"].as(length); +} +#undef this \ No newline at end of file diff --git a/engine/src/engine/ext/player/interaction/behavior.h b/engine/src/engine/ext/player/interaction/behavior.h new file mode 100644 index 00000000..6ad6b66b --- /dev/null +++ b/engine/src/engine/ext/player/interaction/behavior.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ext { + namespace PlayerInteractionBehavior { + UF_BEHAVIOR_DEFINE_TYPE(); + EXT_BEHAVIOR_DEFINE_TRAITS(); + EXT_BEHAVIOR_DEFINE_FUNCTIONS(); + UF_BEHAVIOR_DEFINE_METADATA( + float length = 4.0f; + ); + } +} \ No newline at end of file diff --git a/engine/src/engine/ext/player/movement/behavior.cpp b/engine/src/engine/ext/player/movement/behavior.cpp new file mode 100644 index 00000000..f333c3db --- /dev/null +++ b/engine/src/engine/ext/player/movement/behavior.cpp @@ -0,0 +1,339 @@ +#include "behavior.h" +#include "../input/behavior.h" +#include "../camera/behavior.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ONE_OVER_SIXTY 0.016666f + +UF_BEHAVIOR_REGISTER_CPP(ext::PlayerMovementBehavior) +UF_BEHAVIOR_TRAITS_CPP(ext::PlayerMovementBehavior, ticks = true, renders = false, thread = uf::thread::asyncThreadName) +#define this (&self) + +void ext::PlayerMovementBehavior::initialize(uf::Object& self) { + auto& metadata = this->getComponent(); + auto& metadataJson = this->getComponent(); + UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); +} + +void ext::PlayerMovementBehavior::tick(uf::Object& self) { + if ( !this->hasComponent() ) return; + + auto& input = this->getComponent(); + auto& metadata = this->getComponent(); + auto& transform = this->getComponent>(); + auto& physicsBody = this->getComponent(); + + auto& camera = this->getComponent(); + auto& cameraTransform = camera.getTransform(); + + auto cameraAxes = uf::transform::axes( cameraTransform ); + auto axes = uf::transform::axes( transform ); + + /*if ( metadata.camera.fixed ) { + axes = uf::transform::axes( cameraTransform ); + axes.forward.y = 0; + axes.forward = uf::vector::normalize( axes.forward ); + } + else*/ if ( metadata.noclipped || physicsBody.gravity == pod::Vector3f{0,0,0} ){ + axes.forward.y += cameraAxes.forward.y; + axes.forward = uf::vector::normalize( axes.forward ); + } + + bool wasFloored = metadata.floored; + metadata.deltaCrouch = false; + metadata.floored = metadata.noclipped; + if ( !metadata.floored ) { + if ( physicsBody.activity.grounded ) { + metadata.floored = true; + } + } + if ( physicsBody.gravity == pod::Vector3f{0,0,0} ) metadata.noclipped = true; + + { + TIMER(0.25, input.noclipToggle) { + bool state = !metadata.noclipped; + metadata.noclipped = state; + if (!state) { + uf::physics::setGravity(physicsBody); + uf::physics::setColliderCategory(physicsBody, "PLAYER"); + uf::physics::setColliderMask(physicsBody, "PLAYER"); + } else { + uf::physics::setGravity(physicsBody, pod::Vector3f{0,0,0}); + uf::physics::setColliderCategory(physicsBody, "NONE"); + uf::physics::setColliderMask(physicsBody, "NONE"); + } + UF_MSG_DEBUG("{}abled noclip: {}", (state ? "En" : "Dis"), uf::vector::toString(transform.position)); + } + } + + float currentSpeed = metadata.settings.move; + if ( input.run ) currentSpeed = metadata.settings.run; + else if ( input.walk ) currentSpeed = metadata.settings.walk; + + currentSpeed *= input.magnitude; + + float currentFriction = metadata.settings.friction; + if ( metadata.noclipped ) currentSpeed *= 1.5f; + if ( !metadata.floored || metadata.noclipped ) currentFriction = 1.0f; + if ( metadata.noclipped ) physicsBody.velocity = {}; + + pod::Vector3f target = (axes.forward * input.movement.y) + (axes.right * input.movement.x); + if (uf::vector::norm(target) > 0) { + target = uf::vector::normalize(target); + } + + physicsBody.velocity *= { currentFriction, 1.0f, currentFriction }; + + metadata.walking = (input.movement.x != 0 || input.movement.y != 0); + metadata.running = input.run; + + if ( metadata.walking && !metadata.noclipped && physicsBody.object ) { + float stepHeight = 0.65f; + float lookAhead = 0.15f; + + float radius = 0.5f; + float cylHalfHeight = 1.0f; + if ( physicsBody.collider.type == pod::ShapeType::CAPSULE ) { + radius = physicsBody.collider.capsule.radius; + cylHalfHeight = uf::vector::norm(physicsBody.collider.capsule.up); + } + + pod::Vector3f centerPos = transform.position + physicsBody.offsetPosition; + float feetY = centerPos.y - (cylHalfHeight + radius); + + pod::Vector3f checkDir = target; + checkDir.y = 0; + if ( uf::vector::norm(checkDir) > 0.0f ) { + checkDir = uf::vector::normalize(checkDir); + } + + bool steppedThisFrame = false; + + if ( uf::vector::norm(checkDir) > 0.0f ) { + pod::Ray highFwdRay; + highFwdRay.origin = centerPos; + highFwdRay.origin.y = feetY + stepHeight + 0.1f; + highFwdRay.direction = checkDir; + + auto highFwdHit = uf::physics::rayCast( highFwdRay, physicsBody, radius + lookAhead + 0.1f ); + + if ( highFwdHit.contact.penetration > (radius + lookAhead) ) { + pod::Ray downRay; + downRay.origin = centerPos + (checkDir * (radius + lookAhead)); + downRay.origin.y = feetY + stepHeight + 0.1f; + downRay.direction = -axes.up; + + auto downHit = uf::physics::rayCast( downRay, physicsBody, stepHeight * 1.5f ); + + if ( downHit.contact.penetration <= stepHeight * 1.5f ) { + float floorDot = uf::vector::dot(downHit.contact.normal, axes.up); + float stairYDifference = downHit.contact.point.y - feetY; + + if ( floorDot > uf::physics::settings.groundedThreshold ) { + if ( stairYDifference > 0.05f && stairYDifference <= stepHeight ) { + transform.position.y += stairYDifference; + + if ( this->hasComponent() ) { + this->getComponent().stairOffset -= stairYDifference; + } + + if ( physicsBody.velocity.y < 0.0f ) physicsBody.velocity.y = 0.0f; + metadata.floored = true; + physicsBody.activity.grounded = true; + steppedThisFrame = true; + } + } + } + } + } + + // commenting this out makes the camera smooth + bool allowedToSnap = wasFloored || (physicsBody.velocity.y > -3.0f && physicsBody.velocity.y < 0.0f); + + if ( !steppedThisFrame && allowedToSnap && !physicsBody.activity.grounded && physicsBody.velocity.y <= 0.1f ) { + pod::Vector3f rayOffsets[3] = { + pod::Vector3f{0, 0, 0}, + checkDir * (radius * 0.4f), + checkDir * -(radius * 0.4f) + }; + + bool snappedThisFrame = false; + + for ( int i = 0; i < 3; ++i ) { + if ( snappedThisFrame ) break; + + pod::Ray stickRay; + stickRay.origin = centerPos + rayOffsets[i]; + stickRay.direction = -axes.up; + + float castDist = (cylHalfHeight + radius) + stepHeight + 0.1f; + auto stickHit = uf::physics::rayCast( stickRay, physicsBody, castDist ); + + if ( stickHit.contact.penetration > 0.0f && stickHit.contact.penetration <= castDist ) { + float floorDot = uf::vector::dot(stickHit.contact.normal, axes.up); + float floorY = stickHit.contact.point.y; + float dropDist = feetY - floorY; + + if ( floorDot > uf::physics::settings.groundedThreshold ) { + + if ( dropDist > 0.01f && dropDist <= stepHeight ) { + bool shouldSnapDown = true; + + if ( uf::vector::norm(checkDir) > 0.0f ) { + pod::Ray fwdDownRay; + fwdDownRay.origin = centerPos + (checkDir * (radius + 0.1f)); + fwdDownRay.origin.y = feetY + stepHeight + 0.1f; + fwdDownRay.direction = -axes.up; + + float fwdCastDist = stepHeight * 1.5f; + auto fwdHit = uf::physics::rayCast(fwdDownRay, physicsBody, fwdCastDist); + + if ( fwdHit.contact.penetration > 0.0f && fwdHit.contact.penetration <= fwdCastDist ) { + float fwdDropDist = feetY - fwdHit.contact.point.y; + + if ( fwdDropDist >= -0.25f && fwdDropDist <= 0.05f ) { + shouldSnapDown = false; + } + } + } + + if ( shouldSnapDown ) { + float hSpeed = uf::vector::norm(pod::Vector3f{physicsBody.velocity.x, 0.0f, physicsBody.velocity.z}); + if ( hSpeed < 1.0f ) hSpeed = currentSpeed; + physicsBody.velocity.y = -hSpeed * 1.5f; + + if ( uf::vector::norm(target) > 0.0f ) { + physicsBody.velocity.x = target.x * currentSpeed; + physicsBody.velocity.z = target.z * currentSpeed; + } + + metadata.floored = true; + physicsBody.activity.grounded = true; + snappedThisFrame = true; + } + } + } + } + } + } + } + + if ( metadata.walking ) { + float factor = metadata.floored ? 1.0f : metadata.settings.air; + if ( metadata.noclipped ) { + physicsBody.velocity += target * currentSpeed * 50.0f * ONE_OVER_SIXTY; + } else { + physicsBody.velocity += target * std::clamp( + currentSpeed * factor - uf::vector::dot(physicsBody.velocity, target), + 0.0f, + currentSpeed * 10.0f * ONE_OVER_SIXTY + ); + } + + auto dot = uf::vector::dot( axes.forward, target ); + if ( !metadata.settings.strafe && dot < 1.0f ) { + auto axis = axes.up; + float angle = uf::vector::signedAngle( axes.forward, target, axis ) * ONE_OVER_SIXTY /*uf::physics::time::delta*/ * 4; + + if ( physicsBody.object ) uf::physics::applyRotation( physicsBody, axis, angle ); else + uf::transform::rotate( transform, axis, angle ); + } + } + + { + TIMER( 0.0625, metadata.floored && input.jump && !metadata.noclipped ) { + physicsBody.velocity += axes.up * metadata.settings.jump; + } + } + if ( metadata.floored && input.jump && metadata.noclipped ) { + transform.position += axes.up * metadata.settings.jump * uf::physics::time::delta * 4.0f; + } + + if ( input.crouch ) { + if ( metadata.noclipped ) transform.position -= axes.up * metadata.settings.jump * uf::physics::time::delta * 4.0f; + else { + if ( !metadata.crouching ) metadata.deltaCrouch = true; + metadata.crouching = true; + } + } else { + if ( metadata.crouching ) metadata.deltaCrouch = true; + metadata.crouching = false; + } + + if ( metadata.deltaCrouch && !metadata.noclipped && physicsBody.object ) { + float halfCrouch = metadata.settings.crouch * 0.5f; + if ( physicsBody.collider.type == pod::ShapeType::CAPSULE ) { + if ( metadata.crouching ) { + physicsBody.collider.capsule.up.y -= halfCrouch; + physicsBody.offsetPosition.y += halfCrouch; + + if ( metadata.floored ) { + transform.position.y -= metadata.settings.crouch; + } + } else { + physicsBody.collider.capsule.up.y += halfCrouch; + physicsBody.offsetPosition.y -= halfCrouch; + } + } + } + + if ( physicsBody.object ) uf::physics::setVelocity(physicsBody, physicsBody.velocity); + else transform.position += physicsBody.velocity * ONE_OVER_SIXTY; +} + +void ext::PlayerMovementBehavior::render(uf::Object& self) {} +void ext::PlayerMovementBehavior::destroy(uf::Object& self) {} +void ext::PlayerMovementBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ + serializer["system"]["crouching"] = crouching; + serializer["system"]["noclipped"] = noclipped; + + serializer["physics"]["friction"] = settings.friction; + + serializer["movement"]["rotate"] = settings.rotate; + serializer["movement"]["move"] = settings.move; + serializer["movement"]["run"] = settings.run; + serializer["movement"]["walk"] = settings.walk; + serializer["movement"]["air"] = settings.air; + serializer["movement"]["strafe"] = settings.strafe; + serializer["movement"]["crouch"] = settings.crouch; + serializer["movement"]["jump"] = uf::vector::encode(settings.jump); +} +void ext::PlayerMovementBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ + crouching = serializer["system"]["crouching"].as(crouching); + noclipped = serializer["system"]["noclipped"].as(noclipped); + + settings.friction = serializer["physics"]["friction"].as(settings.friction); + + settings.rotate = serializer["movement"]["rotate"].as(settings.rotate); + settings.move = serializer["movement"]["move"].as(settings.move); + settings.run = serializer["movement"]["run"].as(settings.run); + settings.walk = serializer["movement"]["walk"].as(settings.walk); + settings.air = serializer["movement"]["air"].as(settings.air); + settings.strafe = serializer["movement"]["strafe"].as(settings.strafe); + settings.crouch = serializer["movement"]["crouch"].as(settings.crouch); + settings.jump = uf::vector::decode(serializer["movement"]["jump"], settings.jump); +} +#undef this + +#include +UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::PlayerMovementBehavior::Metadata, + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::walking), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::running), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::crouching), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::floored), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::noclipped), + UF_LUA_REGISTER_USERTYPE_MEMBER(ext::PlayerMovementBehavior::Metadata::deltaCrouch) +) \ No newline at end of file diff --git a/engine/src/engine/ext/player/movement/behavior.h b/engine/src/engine/ext/player/movement/behavior.h new file mode 100644 index 00000000..93cf698e --- /dev/null +++ b/engine/src/engine/ext/player/movement/behavior.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ext { + namespace PlayerMovementBehavior { + UF_BEHAVIOR_DEFINE_TYPE(); + EXT_BEHAVIOR_DEFINE_TRAITS(); + EXT_BEHAVIOR_DEFINE_FUNCTIONS(); + UF_BEHAVIOR_DEFINE_METADATA( + struct Settings { + float crouch = -1.0f; + float rotate = 1.0f; + float move = 4.0f; + float run = 8.0f; + float walk = 1.0f; + float friction = 0.8f; + float air = 1.0f; + float stepHeight = 0.35f; + bool strafe = true; + pod::Vector3f jump = {0, 8, 0}; + } settings; + + bool walking = false; + bool running = false; + bool crouching = false; + bool floored = true; + bool noclipped = false; + bool deltaCrouch = false; + ); + } +} \ No newline at end of file diff --git a/engine/src/ext/lua/lua.cpp b/engine/src/ext/lua/lua.cpp index 48f9de63..aa067958 100644 --- a/engine/src/ext/lua/lua.cpp +++ b/engine/src/ext/lua/lua.cpp @@ -20,30 +20,85 @@ uf::stl::unordered_map ext::lua::modules; #include #include -struct vfs_reader { - uf::stl::vector buffer; - bool read_once; +namespace { + struct vfs_reader { + uf::stl::vector buffer; + bool read_once; - vfs_reader(const uf::stl::string& filename) : read_once(false) { - // Use your VFS abstraction to load the entire file into the buffer - uf::io::readAsBuffer(buffer, filename); - } - - static const char* read(lua_State*, void* data, size_t* size) { - vfs_reader* reader = static_cast(data); - - // If we haven't yielded the buffer to Lua yet, do it now. - if (!reader->read_once && !reader->buffer.empty()) { - *size = reader->buffer.size(); - reader->read_once = true; - return reinterpret_cast(reader->buffer.data()); + vfs_reader(const uf::stl::string& filename) : read_once(false) { + uf::io::readAsBuffer(buffer, filename); } - // Yield 0 to signify EOF - *size = 0; - return nullptr; + static const char* read(lua_State*, void* data, size_t* size) { + vfs_reader* reader = static_cast(data); + + if (!reader->read_once && !reader->buffer.empty()) { + *size = reader->buffer.size(); + reader->read_once = true; + return reinterpret_cast(reader->buffer.data()); + } + + *size = 0; + return nullptr; + } + }; + + ext::json::Value encodeNode( sol::object obj ) { + if ( obj.get_type() == sol::type::boolean ) return ext::json::Value(obj.as()); + if ( obj.get_type() == sol::type::number ) return ext::json::Value(obj.as()); + if ( obj.get_type() == sol::type::string ) return ext::json::Value(obj.as()); + + if ( obj.get_type() == sol::type::table ) { + sol::table t = obj.as(); + ext::json::Value json; + + bool isArray = true; + size_t expectedIndex = 1; + for (auto& kv : t) { + if (kv.first.get_type() != sol::type::number || kv.first.as() != expectedIndex++) { + isArray = false; + break; + } + } + + if ( isArray ) { + for ( auto& kv : t ) json.emplace_back( encodeNode(kv.second) ); + } else { + for ( auto& kv : t ) { + if ( kv.first.get_type() == sol::type::string ) { + json[kv.first.as()] = encodeNode( kv.second ); + } + } + } + return json; + } + return ext::json::Value(); } -}; + + sol::object decodeNode(const ext::json::Value& json) { + if ( json.is_null() ) return sol::lua_nil; + if ( json.is_boolean() ) return sol::make_object(ext::lua::state, json.as()); + if ( json.is_number() ) return sol::make_object(ext::lua::state, json.as()); + if ( json.is_string() ) return sol::make_object(ext::lua::state, json.as()); + + if ( json.is_array() ) { + sol::table t = ext::lua::state.create_table(); + ext::json::forEach(json, [&](size_t i, const ext::json::Value& val) { + t[i + 1] = decodeNode(val); + }); + return t; + } + + if ( json.is_object() ) { + sol::table t = ext::lua::state.create_table(); + ext::json::forEach(json, [&](const uf::stl::string& key, const ext::json::Value& val) { + t[key] = decodeNode(val); + }); + return t; + } + return sol::lua_nil; + } +} sol::table ext::lua::createTable() { return sol::table(ext::lua::state, sol::create); @@ -55,6 +110,8 @@ uf::stl::string ext::lua::sanitize( const uf::stl::string& dirty, int index ) { part = uf::string::replace( part, "<>", "" ); return part; } + +/* std::optional ext::lua::encode( sol::table table ) { LUA_FUN fun = ext::lua::state["json"]["encode"]; auto result = fun( table ); @@ -79,6 +136,16 @@ std::optional ext::lua::decode( const uf::stl::string& string ) { #endif return result; } +*/ + +std::optional ext::lua::encode(sol::table table) { + return ::encodeNode(table); +} +std::optional ext::lua::decode(const ext::json::Value& json) { + sol::object obj = ::decodeNode(json); + if ( obj.is() ) return obj.as(); + return ext::lua::state.create_table(); +} uf::stl::vector>* ext::lua::onInitializationFunctions = NULL; void ext::lua::onInitialization( const std::function& function ) { @@ -94,7 +161,7 @@ namespace binds { namespace hook { void add( const uf::stl::string& name, LUA_FUN function ) { uf::hooks.addHook( name, [function](ext::json::Value& json){ - sol::table table = ext::lua::state["json"]["decode"]( json.dump() ); + sol::table table = ext::lua::decode(json).value_or(ext::lua::state.create_table()); auto result = function( table ); // ??? #if UF_LUA_PCALLS @@ -188,8 +255,7 @@ namespace binds { sol::table readFromFile( const uf::stl::string& filename ){ uf::Serializer serializer; serializer.readFromFile( filename ); - uf::stl::string string = serializer.serialize(); - auto decoded = ext::lua::decode( string ); + auto decoded = ext::lua::decode( serializer ); return decoded ? decoded.value() : ext::lua::createTable(); }; bool writeToFile( sol::table table, const uf::stl::string& path ) { diff --git a/engine/src/ext/lua/usertypes/object.cpp b/engine/src/ext/lua/usertypes/object.cpp index 708068a7..20debc79 100644 --- a/engine/src/ext/lua/usertypes/object.cpp +++ b/engine/src/ext/lua/usertypes/object.cpp @@ -155,22 +155,21 @@ namespace binds { #if UF_LUA_PCALLS if ( !result.valid() ) { sol::error err = result; - uf::iostream << err.what() << "\n"; + UF_MSG_ERROR("{}", err.what()); return; } #endif return; } - uf::stl::string payload = json.dump(); - auto decoded = ext::lua::decode( payload ); + auto decoded = ext::lua::decode( json ); if ( !decoded ) return; sol::table table = decoded.value(); auto result = fun( table ); #if UF_LUA_PCALLS if ( !result.valid() ) { sol::error err = result; - uf::iostream << err.what() << "\n"; + UF_MSG_ERROR("{}", err.what()); return; } #endif diff --git a/engine/src/utils/math/physics/impl.cpp b/engine/src/utils/math/physics/impl.cpp index acf5db50..3673d338 100644 --- a/engine/src/utils/math/physics/impl.cpp +++ b/engine/src/utils/math/physics/impl.cpp @@ -223,7 +223,8 @@ void uf::physics::step( pod::World& world, float dt ) { if ( a.activity.awake && !b.activity.awake ) impl::wakeBody( b ); if ( b.activity.awake && !a.activity.awake ) impl::wakeBody( a ); // mark as grounded - for ( auto& c : manifold.points ) { + bool isTrigger = (a.collider.category & pod::Collider::CATEGORY_TRIGGER) || (b.collider.category & pod::Collider::CATEGORY_TRIGGER); + if ( !isTrigger ) for ( auto& c : manifold.points ) { if ( std::fabs(uf::vector::dot(c.normal, pod::Vector3f{0,1,0})) > uf::physics::settings.groundedThreshold ) { // only mark if contact point is below body if ( c.point.y < impl::getPosition(a).y ) a.activity.grounded = true;