refractored player behavior into not-monolithic code, better player handling, stair stepping going up and down, lua serialization/deserialization should be more optimal by virtue of not stringifying
This commit is contained in:
parent
a834adca13
commit
2944335904
@ -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,
|
||||
|
||||
@ -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 ],
|
||||
|
||||
@ -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 ],
|
||||
|
||||
@ -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 )
|
||||
@ -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 }
|
||||
]
|
||||
}
|
||||
@ -28,6 +28,7 @@
|
||||
#include <uf/utils/memory/unordered_map.h>
|
||||
#include <uf/utils/singletons/pre_main.h>
|
||||
#include <uf/utils/string/ext.h>
|
||||
#include <uf/ext/json/json.h>
|
||||
|
||||
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<uf::stl::string> encode( sol::table table );
|
||||
std::optional<sol::table> decode( const uf::stl::string& string );
|
||||
std::optional<ext::json::Value> encode( sol::table table );
|
||||
std::optional<sol::table> decode( const ext::json::Value& string );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,828 +1,17 @@
|
||||
#include "behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
#include <uf/utils/userdata/userdata.h>
|
||||
#include <uf/utils/window/window.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/ext/openvr/openvr.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/spec/controller/controller.h>
|
||||
#include <uf/utils/io/inputs.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#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<pod::Transform<>>();
|
||||
|
||||
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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<bool>() ) {
|
||||
float l = cameraSettingsJson["left"].as<float>();
|
||||
float r = cameraSettingsJson["right"].as<float>();
|
||||
float b = cameraSettingsJson["bottom"].as<float>();
|
||||
float t = cameraSettingsJson["top"].as<float>();
|
||||
float n = cameraSettingsJson["near"].as<float>();
|
||||
float f = cameraSettingsJson["far"].as<float>();
|
||||
|
||||
camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) );
|
||||
} else {
|
||||
float fov = cameraSettingsJson["fov"].as<float>(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<bool>(!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::stl::string>();
|
||||
uf::Serializer cardData = masterDataGet("Card", leaderId);
|
||||
uf::Serializer charaData = masterDataGet("Chara", cardData["character_id"].as<uf::stl::string>());
|
||||
uf::stl::string leader = charaData["name"].as<uf::stl::string>();
|
||||
|
||||
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<pod::Transform<>>();
|
||||
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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<pod::Graph>();
|
||||
|
||||
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
|
||||
|
||||
#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<float>(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::AudioEmitter>();
|
||||
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<uf::AudioEmitter>();
|
||||
metadata.audio.footstep.timer -= uf::physics::time::delta;
|
||||
if ( metadata.audio.footstep.timer <= 0.0f ) {
|
||||
// player/footsteps
|
||||
static uf::stl::vector<uf::stl::string> 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<pod::AudioClip>( 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::Graph>() ) {
|
||||
pod::payloads::QueueAnimationPayload payload;
|
||||
|
||||
payload.name = stats.targetAnimation;
|
||||
playerModel->queueHook("animation:Set.%UID%", payload);
|
||||
stats.targetAnimation = "";
|
||||
}
|
||||
/*
|
||||
if ( playerModel && playerModel->hasComponent<pod::Graph>() ) {
|
||||
auto& graph = playerModel->getComponent<pod::Graph>();
|
||||
uf::graph::queueAnimation( graph, stats.targetAnimation );
|
||||
}
|
||||
*/
|
||||
/*
|
||||
if ( playerModel && playerModel->hasComponent<pod::Graph>() ) {
|
||||
auto& graph = playerModel->getComponent<pod::Graph>();
|
||||
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<float>(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<float>();
|
||||
|
||||
/*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/ext/lua/component.h>
|
||||
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
|
||||
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()
|
||||
@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
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<bool> 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<uf::stl::string> 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;
|
||||
);
|
||||
}
|
||||
}
|
||||
224
engine/src/engine/ext/player/camera/behavior.cpp
Normal file
224
engine/src/engine/ext/player/camera/behavior.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
#include "behavior.h"
|
||||
#include "../input/behavior.h"
|
||||
#include "../movement/behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
#include <uf/utils/userdata/userdata.h>
|
||||
#include <uf/utils/window/window.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/ext/openvr/openvr.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/spec/controller/controller.h>
|
||||
#include <uf/utils/io/inputs.h>
|
||||
|
||||
#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<pod::Transform<>>();
|
||||
auto& metadata = this->getComponent<ext::PlayerCameraBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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<bool>() ) {
|
||||
float l = cameraSettingsJson["left"].as<float>();
|
||||
float r = cameraSettingsJson["right"].as<float>();
|
||||
float b = cameraSettingsJson["bottom"].as<float>();
|
||||
float t = cameraSettingsJson["top"].as<float>();
|
||||
float n = cameraSettingsJson["near"].as<float>();
|
||||
float f = cameraSettingsJson["far"].as<float>();
|
||||
|
||||
camera.setProjection( uf::matrix::orthographic( l, r, b, t, n, f ) );
|
||||
} else {
|
||||
float fov = cameraSettingsJson["fov"].as<float>(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<ext::PlayerCameraBehavior::Metadata>();
|
||||
auto& transform = this->getComponent<pod::Transform<>>();
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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<uf::Serializer>();
|
||||
auto cameraSettingsJson = metadataJson["camera"]["settings"];
|
||||
|
||||
float fov = cameraSettingsJson["fov"].as<float>(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<ext::PlayerInputBehavior::Metadata>() ) {
|
||||
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
|
||||
|
||||
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<pod::PhysicsBody>() ) {
|
||||
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
|
||||
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<ext::PlayerInputBehavior::Metadata>() && this->hasComponent<ext::PlayerMovementBehavior::Metadata>() ) {
|
||||
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
|
||||
auto& movement = this->getComponent<ext::PlayerMovementBehavior::Metadata>();
|
||||
|
||||
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<pod::PhysicsBody>() ) {
|
||||
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
|
||||
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/ext/lua/component.h>
|
||||
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)
|
||||
)
|
||||
44
engine/src/engine/ext/player/camera/behavior.h
Normal file
44
engine/src/engine/ext/player/camera/behavior.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
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<bool> 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;
|
||||
);
|
||||
}
|
||||
}
|
||||
145
engine/src/engine/ext/player/input/behavior.cpp
Normal file
145
engine/src/engine/ext/player/input/behavior.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include "behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
#include <uf/utils/userdata/userdata.h>
|
||||
#include <uf/utils/window/window.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/ext/openvr/openvr.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/spec/controller/controller.h>
|
||||
#include <uf/utils/io/inputs.h>
|
||||
|
||||
#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<pod::Transform<>>();
|
||||
|
||||
auto& state = this->getComponent<ext::PlayerInputBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
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<bool>(!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<ext::PlayerInputBehavior::Metadata>();
|
||||
|
||||
// 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/ext/lua/component.h>
|
||||
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)
|
||||
)
|
||||
32
engine/src/engine/ext/player/input/behavior.h
Normal file
32
engine/src/engine/ext/player/input/behavior.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
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;
|
||||
);
|
||||
};
|
||||
}
|
||||
72
engine/src/engine/ext/player/interaction/behavior.cpp
Normal file
72
engine/src/engine/ext/player/interaction/behavior.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "behavior.h"
|
||||
#include "../input/behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
#include <uf/utils/userdata/userdata.h>
|
||||
#include <uf/utils/window/window.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/ext/openvr/openvr.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/spec/controller/controller.h>
|
||||
#include <uf/utils/io/inputs.h>
|
||||
|
||||
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<ext::PlayerInteractionBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS( metadata, metadataJson );
|
||||
}
|
||||
|
||||
void ext::PlayerInteractionBehavior::tick(uf::Object& self) {
|
||||
if (!this->hasComponent<ext::PlayerInputBehavior::Metadata>()) return;
|
||||
if (!this->hasComponent<pod::PhysicsBody>()) return; // We need a physics body for the raycast!
|
||||
|
||||
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
|
||||
auto& metadata = this->getComponent<ext::PlayerInteractionBehavior::Metadata>();
|
||||
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
|
||||
|
||||
TIMER(0.25, input.use) {
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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
|
||||
18
engine/src/engine/ext/player/interaction/behavior.h
Normal file
18
engine/src/engine/ext/player/interaction/behavior.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
namespace ext {
|
||||
namespace PlayerInteractionBehavior {
|
||||
UF_BEHAVIOR_DEFINE_TYPE();
|
||||
EXT_BEHAVIOR_DEFINE_TRAITS();
|
||||
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
|
||||
UF_BEHAVIOR_DEFINE_METADATA(
|
||||
float length = 4.0f;
|
||||
);
|
||||
}
|
||||
}
|
||||
339
engine/src/engine/ext/player/movement/behavior.cpp
Normal file
339
engine/src/engine/ext/player/movement/behavior.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
#include "behavior.h"
|
||||
#include "../input/behavior.h"
|
||||
#include "../camera/behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/serialize/serializer.h>
|
||||
#include <uf/utils/userdata/userdata.h>
|
||||
#include <uf/utils/window/window.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/ext/openvr/openvr.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/spec/controller/controller.h>
|
||||
#include <uf/utils/io/inputs.h>
|
||||
|
||||
#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<ext::PlayerMovementBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
|
||||
}
|
||||
|
||||
void ext::PlayerMovementBehavior::tick(uf::Object& self) {
|
||||
if ( !this->hasComponent<ext::PlayerInputBehavior::Metadata>() ) return;
|
||||
|
||||
auto& input = this->getComponent<ext::PlayerInputBehavior::Metadata>();
|
||||
auto& metadata = this->getComponent<ext::PlayerMovementBehavior::Metadata>();
|
||||
auto& transform = this->getComponent<pod::Transform<>>();
|
||||
auto& physicsBody = this->getComponent<pod::PhysicsBody>();
|
||||
|
||||
auto& camera = this->getComponent<uf::Camera>();
|
||||
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<ext::PlayerCameraBehavior::Metadata>() ) {
|
||||
this->getComponent<ext::PlayerCameraBehavior::Metadata>().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/ext/lua/component.h>
|
||||
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)
|
||||
)
|
||||
36
engine/src/engine/ext/player/movement/behavior.h
Normal file
36
engine/src/engine/ext/player/movement/behavior.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
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;
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -20,30 +20,85 @@ uf::stl::unordered_map<uf::stl::string, uf::stl::string> ext::lua::modules;
|
||||
#include <uf/utils/io/inputs.h>
|
||||
#include <uf/utils/io/fmt.h>
|
||||
|
||||
struct vfs_reader {
|
||||
uf::stl::vector<uint8_t> buffer;
|
||||
bool read_once;
|
||||
namespace {
|
||||
struct vfs_reader {
|
||||
uf::stl::vector<uint8_t> 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<vfs_reader*>(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<const char*>(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<vfs_reader*>(data);
|
||||
|
||||
if (!reader->read_once && !reader->buffer.empty()) {
|
||||
*size = reader->buffer.size();
|
||||
reader->read_once = true;
|
||||
return reinterpret_cast<const char*>(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<bool>());
|
||||
if ( obj.get_type() == sol::type::number ) return ext::json::Value(obj.as<double>());
|
||||
if ( obj.get_type() == sol::type::string ) return ext::json::Value(obj.as<uf::stl::string>());
|
||||
|
||||
if ( obj.get_type() == sol::type::table ) {
|
||||
sol::table t = obj.as<sol::table>();
|
||||
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<size_t>() != 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<uf::stl::string>()] = 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<bool>());
|
||||
if ( json.is_number() ) return sol::make_object(ext::lua::state, json.as<double>());
|
||||
if ( json.is_string() ) return sol::make_object(ext::lua::state, json.as<uf::stl::string>());
|
||||
|
||||
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<uf::stl::string> ext::lua::encode( sol::table table ) {
|
||||
LUA_FUN fun = ext::lua::state["json"]["encode"];
|
||||
auto result = fun( table );
|
||||
@ -79,6 +136,16 @@ std::optional<sol::table> ext::lua::decode( const uf::stl::string& string ) {
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
|
||||
std::optional<ext::json::Value> ext::lua::encode(sol::table table) {
|
||||
return ::encodeNode(table);
|
||||
}
|
||||
std::optional<sol::table> ext::lua::decode(const ext::json::Value& json) {
|
||||
sol::object obj = ::decodeNode(json);
|
||||
if ( obj.is<sol::table>() ) return obj.as<sol::table>();
|
||||
return ext::lua::state.create_table();
|
||||
}
|
||||
|
||||
uf::stl::vector<std::function<void()>>* ext::lua::onInitializationFunctions = NULL;
|
||||
void ext::lua::onInitialization( const std::function<void()>& 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 ) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user