extends CharacterBody3D class_name PlayerCharacter #Movement var aceleration: float = 4.32 #Current aceleration var maxSpeed: float = 6.2 #Maximum Speed var sprintSpeed: float = 4.54 #Additional Speed gained while Sprinting var isMoving: bool = false var slowed: bool = false var momentum: float = 0 var newMoveDirection: Vector3 = Vector3(0,0,0) var moveDirection: Vector3 = Vector3(0,0,0) var deceleration: float = 5.56 var turnSpeed: float = PI #Speed at wich body turn sideways var handling: float = 0.67 var facingDirection = Vector2(0,1) #Where the legs are facing var sprinting = false #While Player is sprinting var sprintPowerInitialUse: float = 4.5 #How much % sprint energy is used immediatly when initiating a sprint var sprintPowerUse: float = 17.5 #How much % energy is used per 1 sec of sprint #Power (Energy bar) var power: float = 100 #Energy used for Sprinting var passivePowerGain: float = 20 #How much % energy is gained per 1 sec of recovery #Falling var fallingSpeed: float = 10 #max falling speed, 8 would be rougly earth gravity #Jump var jumpingStart: bool = false #Keeps Track of first frame of Jump to handle on jump start behavior var jumping: bool = false var jumpPowerUse: float = 35 #How much Power is used by the Jump var jumpEnergyChargeSpeed: float = 125 #How quickly Jump is charged (in %, meaning that 100 = 1 sec chrage time) var jumpEnergyDischargeSpeed: float = 150 #How quickly Jump is discharged (in %, meaning that 100 = 1 sec chrage time), basically jump duration var jumpForce: float = 14 #This is multiplied with jump charge from 0 - 100 to get jump velocity #Dash var dashForce: float = 1350 #How strong is the dashes forward propelling force var dashDuration: float = 0.67 # How long does the dash propell in seconds var currentDashDuration: float = 0.8 #How much of current dash is still left var isDashing: bool = false #true Wihle airdash is active var dashPowerUse: float = 30 #How much power is used by airdash #Control States var currentControlState: int = 0 enum controls {DEFAULT,STANDARD_MINIGAME} #Health var health: float = 100.0 var invoulnerable: bool = false var alive: bool = true @onready var camera: Camera3D = $CameraPivot/Camera3D @onready var cameraPivot: Marker3D = $CameraPivot @onready var flashlight: SpotLight3D = $CameraPivot/Camera3D/flashlight @onready var backlightR := $pivot/BodyPivot/body/BacklightR @onready var backlightL := $pivot/BodyPivot/body/BacklightL @onready var pivot: = $pivot @onready var headPivot: = $pivot/HeadBinoculars/HeadPivot @onready var headBinoculars: = $pivot/HeadBinoculars @onready var legs := $pivot/BodyPivot/CaterpillarLegs @onready var body: = $pivot/BodyPivot/body @onready var hud = $/root/Main/Hud @onready var interactCross := $/root/Main/Hud/InteractCross @onready var sprintBar :TextureProgressBar = $/root/Main/Hud/TextureProgressBar @onready var jumpBar :ProgressBar = $/root/Main/Hud/ProgressBar @onready var grabRaycast: = $CameraPivot/Camera3D/GrabDetector @onready var grabPivot: = $pivot/BodyPivot/body/GrabPivot @onready var grabBox: = $pivot/BodyPivot/body/GrabBox var grabbedObject: GrabableObject @onready var interactRaycast = $CameraPivot/Camera3D/InteractDetector @onready var VoiceChat = $VoiceChat @onready var bodyPivot: Node3D = $pivot/BodyPivot var mouseSensetivity := 0.1 #How much mouse movement affects ingame camera movement var spectatorScene: PackedScene = preload("res://actors/Player/Spectator.tscn") var spectatorParent: Node3D var mapLogic: MapLogic #Camera Shake Stuff var traumaReductionRate:float = 0.34 var trauma: float = 0 @export var maxX = 12 @export var maxY = 12 @export var maxZ = 7 @export var shakeIntensety : float = 7.0 var time: float = 0 @export var noise: Noise var noiseSpeed: float = 50 var initialRotation = rotation_degrees as Vector3 # Camera Shake Stuff end func _enter_tree() -> void: set_multiplayer_authority(name.to_int()) if is_multiplayer_authority(): $CameraPivot/Camera3D.make_current() $pivot/HeadBinoculars.hide() Multiplayer.thisPlayer = self func _ready() -> void: Multiplayer.player_ready.emit(int(name)) Input.mouse_mode = Input.MOUSE_MODE_CAPTURED mapLogic = Multiplayer.currentMapLogic spectatorParent = get_node("/root/Main/Spectators") if mapLogic: mapLogic.onCollision.connect(onCollision) if is_multiplayer_authority(): position.y += 2 return func _process(delta: float) -> void: if not is_multiplayer_authority(): return if not alive: return if invoulnerable and trauma == 0.0: invoulnerable = false if Input.is_action_just_pressed("debug"): ##Debug die() updateMouseMode() match currentControlState: controls.DEFAULT: regularControlsIdle(delta) controls.STANDARD_MINIGAME: standardMinigameControlsIdle(delta) func _physics_process(delta: float) -> void: if not is_multiplayer_authority(): return if not alive: return match currentControlState: controls.DEFAULT: regularControlsPhysics(delta) controls.STANDARD_MINIGAME: endOfPhysicsCleanup(delta) func updateMouseMode() -> void: match currentControlState: controls.DEFAULT: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED controls.STANDARD_MINIGAME: Input.mouse_mode = Input.MOUSE_MODE_CONFINED func standardMinigameControlsIdle(_delta:float): pass #if Input.is_action_just_pressed("leaveMinigame"): #currentControlState = controls.DEFAULT func regularControlsIdle(_delta:float): #Interacting Logic var InteractCollider: InteractBox = interactRaycast.get_collider() if InteractCollider and Input.is_action_just_pressed("interact"): InteractCollider.playerRef = self InteractCollider.interact() if InteractCollider.type == "minigame": currentControlState = controls.STANDARD_MINIGAME momentum = 0 #Grabbing Logic var GrabCollider: GrabBox = grabRaycast.get_collider() var grabBoxCollider: Array[Area3D] = grabBox.get_overlapping_areas() if(grabBoxCollider and Input.is_action_just_pressed("interact") and !grabbedObject): if grabBoxCollider.has(GrabCollider): #If the player is looking at an object and it is in the grab box grabbedObject = GrabCollider.grab() else: #If the player is not looking at an object, grab the closet one in the grab box var closestObj: GrabBox var distanceToClosestObj: float = 100 for grabObj in grabBoxCollider: if (getDistance(self,grabObj) < distanceToClosestObj): closestObj = grabObj distanceToClosestObj = getDistance(self, grabObj) grabbedObject = closestObj.grab() if InteractCollider or grabBoxCollider: interactCross.show() else: interactCross.hide() if Input.is_action_just_pressed("flashlight"): flashlight.visible = !flashlight.visible if grabbedObject: grabbedObject.global_position = grabPivot.global_position# + grabbedObject.grabPositionPositionOffset grabbedObject.rotation = grabPivot.global_rotation + grabbedObject.grabPositionRotationOffset if grabbedObject.grabBox.heavy: slowed = true if Input.is_action_just_pressed("drop"): grabbedObject.release.rpc() grabbedObject = null slowed = false momentum = 0 if Input.is_action_just_pressed("throw"): grabbedObject.release.rpc() grabbedObject.throw.rpc_id(1,facingDirection.x,facingDirection.y,camera.rotation.x) grabbedObject = null slowed = false momentum = 0 func regularControlsPhysics(delta: float): #Drain energy on sprint start to prevent the player from pressing sprint every other frame to get infinity energy #if Input.is_action_just_pressed("Sprint") and is_on_floor(): #power = clamp(power - sprintPowerInitialUse, -(sprintPowerInitialUse*2) , 100) # Rest of sprint energy logic if Input.is_action_pressed("Sprint") and is_on_floor(): power = clamp(power - sprintPowerUse * delta, 0 , 100) # Drain sprint when holding the button if power > 0: sprinting = true else: sprinting = false else: sprinting = false power = clamp(power + passivePowerGain * delta, -(sprintPowerInitialUse*2) , 100) # Gain sprint when not holding the button isMoving = false newMoveDirection = Vector3(0,0,0) # Set moveDirection based on input if Input.is_action_pressed("moveUp") and !Input.is_action_pressed("moveDown"): isMoving = true newMoveDirection += Vector3(facingDirection.x,0,facingDirection.y) if Input.is_action_pressed("moveRight") and !Input.is_action_pressed("moveLeft"): isMoving = true if Input.is_action_pressed("moveDown"): newMoveDirection += Vector3(facingDirection.x,0,facingDirection.y).rotated(up_direction,PI/2) else: newMoveDirection -= Vector3(facingDirection.x,0,facingDirection.y).rotated(up_direction,PI/2) if Input.is_action_pressed("moveLeft") and !Input.is_action_pressed("moveRight"): isMoving = true if Input.is_action_pressed("moveDown"): newMoveDirection -= Vector3(facingDirection.x,0,facingDirection.y).rotated(up_direction,PI/2) else: newMoveDirection += Vector3(facingDirection.x,0,facingDirection.y).rotated(up_direction,PI/2) if Input.is_action_pressed("moveDown") and !Input.is_action_pressed("moveUp"): isMoving = true newMoveDirection += Vector3(facingDirection.x,0,facingDirection.y) showBacklights() else: hideBacklights() if newMoveDirection != Vector3(0,0,0): moveDirection = clampVectorLength(moveDirection + newMoveDirection*delta,0,handling) #if getAngleBetweenVectors(newMoveDirection,moveDirection) > deg_to_rad(135): #moveDirection = newMoveDirection.normalized() * moveDirection.length() #else: #moveDirection = clampVectorLength(moveDirection + newMoveDirection*delta,0,handling) endOfPhysicsCleanup(delta) #Momentum stuff if isMoving: if Input.is_action_pressed("moveDown"): momentum = clamp(momentum + -aceleration*2 * delta,-maxSpeed/2,maxSpeed) else: momentum = clamp(momentum + aceleration * delta,-maxSpeed/2,maxSpeed) else: if momentum >= 0: momentum = clamp(momentum - deceleration * delta,-maxSpeed/2,maxSpeed) if momentum < 0: momentum = clamp(momentum + deceleration * delta,-maxSpeed/2,maxSpeed) if abs(momentum-deceleration*delta) < deceleration * delta * 2: momentum = 0 if sprinting and isMoving and !Input.is_action_pressed("moveDown"): velocity = clampVectorLength(moveDirection.normalized() * (momentum + sprintSpeed),0,maxSpeed+sprintSpeed) else: velocity = clampVectorLength(moveDirection.normalized() * momentum,0,maxSpeed) bodyPivot.rotation.y = rotate_toward(bodyPivot.rotation.y, -atan2(moveDirection.z,moveDirection.x) + PI/2,delta*turnSpeed) if not is_on_floor(): velocity += get_gravity().normalized() * fallingSpeed #Jump Logic if Input.is_action_pressed("jump") and is_on_floor(): jumpBar.value = clampf(jumpBar.value + jumpEnergyChargeSpeed*delta,0,100) if jumpBar.value > 67: jumping = true elif jumpBar.value > 0 and jumping: if !jumpingStart: if power < jumpPowerUse/2: jumpBar.value = 0 else: jumpingStart = true power -= jumpPowerUse velocity -= get_gravity().normalized() * jumpForce * jumpBar.value * delta jumpBar.value = clampf(jumpBar.value - jumpEnergyDischargeSpeed*delta,0,100) else: jumpBar.value = 0 jumping = false jumpingStart = false #Dash Logic #if Input.is_action_just_pressed("Sprint") and !isDashing: #if power > dashPowerUse and !isDashing: #power -= dashPowerUse #isDashing = true # #if isDashing and currentDashDuration > 0: #currentDashDuration = clamp(currentDashDuration - delta,0,dashDuration) #velocity += Vector3(facingDirection.x,0,facingDirection.y) * dashForce * (currentDashDuration/dashDuration) * delta #else: #isDashing = false #currentDashDuration = dashDuration # move_and_slide() #applies movement func endOfPhysicsCleanup(delta: float): trauma = max(trauma-delta*traumaReductionRate,0) time += delta rotation_degrees.x = initialRotation.x + maxX * getShakeIntensity() * getNoiseFromSeed(343) rotation_degrees.y = initialRotation.y + maxY * getShakeIntensity() * getNoiseFromSeed(123) rotation_degrees.z = initialRotation.z + maxZ * getShakeIntensity() * getNoiseFromSeed(234) #Rotate head based on camera movement headPivot.rotation.y = cameraPivot.rotation.y headPivot.rotation.x = -camera.rotation.x if trauma == 0.0: headPivot.rotation.z = 0.0 headBinoculars.rotation.z = 0.0 sprintBar.value = power func showBacklights() -> void: backlightL.show() backlightR.show() func hideBacklights() -> void: backlightL.hide() backlightR.hide() func getAngleBetweenVectors(a: Vector3, b: Vector3) -> float: if a.length() == 0 or b. length() == 0: return 0 return acos((a.dot(b)/(a.length()*b.length()))) func getDistance(a: Node3D, b: Node3D) -> float: var distanceVector: Vector3 distanceVector = b.global_position - a.global_position return distanceVector.length() func clampVectorLength(Vector: Vector3, minLength: float, maxLength: float) -> Vector3: #scales Vector up/ down to the max/ min length givin. If the Vector has a length of 0 it will be returned without being scaled. if Vector.length() == 0: return Vector if Vector.length() < minLength: return Vector * minLength / Vector.length() elif Vector.length() > maxLength: return Vector * maxLength / Vector.length() return Vector func onCollision() -> void: trauma = 1 func addTrauma(traumaAmount: float): trauma = clamp(trauma+traumaAmount,0,1) func getShakeIntensity() -> float: return shakeIntensety * trauma * trauma func getNoiseFromSeed(seed_: int) -> float: noise.seed = seed_ return noise.get_noise_1d(time*noiseSpeed) func _input(event: InputEvent) -> void: #Camera movement with mouse if event is InputEventMouseMotion && Input.mouse_mode == 2: camera.rotation.x -= clamp(deg_to_rad(event.relative.y * mouseSensetivity),-180,180) cameraPivot.rotation.y -= deg_to_rad(event.relative.x * mouseSensetivity) facingDirection = facingDirection.rotated(deg_to_rad(event.relative.x * mouseSensetivity)) camera.rotation_degrees.x = clamp(camera.rotation_degrees.x, -70, 70) cameraPivot.rotation.z = 0 func _on_hurt_box_hit_taken(attack: Attack) -> void: if invoulnerable: return trauma = attack.trauma health -= attack.damage if health <= 0.0: die() invoulnerable = true func die() -> void: Multiplayer.alivePlayerDict.erase(int(name)) headBinoculars.show() headPivot.position = Vector3(0,0.382,0.063) headPivot.rotation = Vector3(0,0,0) #$"pivot/pivotRightLeg/pivotLeftLeg/body/Grabby Arms_L".rotation.x = deg_to_rad(90) #$"pivot/pivotRightLeg/pivotLeftLeg/body/Grabby Arms_R".rotation.x = deg_to_rad(90) var spectator = spectatorScene.instantiate() spectatorParent.add_child(spectator) camera.current = false alive = false