Wire quest flow and combat across all levels.
Connect bear-save, massage, farm paywall, cheese catapults, Dressler hijack, final boss phases, fat-stim UI, and scene fixes so the satire path plays through.
This commit is contained in:
parent
938cc4bc82
commit
1d9e0a6532
51 changed files with 1899 additions and 251 deletions
|
|
@ -1,38 +1,92 @@
|
|||
extends CharacterBody3D
|
||||
class_name PlayerController
|
||||
## Simple top-down-ish 3D movement for billboard levels.
|
||||
## Third-person controller: camera follows player, movement is camera-relative.
|
||||
## Billboard sprites face the camera (Mario-tree style).
|
||||
|
||||
@export var move_speed: float = 6.0
|
||||
@export var attack_range: float = 1.8
|
||||
@export var attack_damage: int = 1
|
||||
@export var mouse_sensitivity: float = 0.0025
|
||||
@export var min_pitch_deg: float = -35.0
|
||||
@export var max_pitch_deg: float = 55.0
|
||||
@export var spring_length: float = 5.5
|
||||
@export var camera_height: float = 1.4
|
||||
|
||||
signal interacted
|
||||
signal attacked
|
||||
|
||||
@onready var _billboard: BillboardSprite = $BillboardSprite
|
||||
@onready var _yaw_pivot: Node3D = $CameraYaw
|
||||
@onready var _spring: SpringArm3D = $CameraYaw/SpringArm3D
|
||||
@onready var _camera: Camera3D = $CameraYaw/SpringArm3D/Camera3D
|
||||
|
||||
var _locked: bool = false
|
||||
var _pitch: float = deg_to_rad(-18.0)
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
if _spring:
|
||||
_spring.spring_length = spring_length
|
||||
_spring.collision_mask = 1
|
||||
_spring.position = Vector3(0.0, camera_height, 0.0)
|
||||
if _camera:
|
||||
_camera.current = true
|
||||
_camera.fov = 70.0
|
||||
_apply_pitch()
|
||||
_set_mouse_captured(true)
|
||||
if _billboard and _billboard.texture == null:
|
||||
var tex: Texture2D = load("res://assets/sprites/placeholders/player.png")
|
||||
if tex:
|
||||
_billboard.set_texture(tex)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_set_mouse_captured(false)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if _locked or get_tree().paused:
|
||||
return
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
var motion := event as InputEventMouseMotion
|
||||
_yaw_pivot.rotate_y(-motion.relative.x * mouse_sensitivity)
|
||||
_pitch = clampf(
|
||||
_pitch - motion.relative.y * mouse_sensitivity,
|
||||
deg_to_rad(min_pitch_deg),
|
||||
deg_to_rad(max_pitch_deg)
|
||||
)
|
||||
_apply_pitch()
|
||||
elif event is InputEventMouseButton and event.pressed:
|
||||
_set_mouse_captured(true)
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if _locked or get_tree().paused:
|
||||
velocity = Vector3.ZERO
|
||||
return
|
||||
|
||||
var input := Vector2(
|
||||
Input.get_axis("move_left", "move_right"),
|
||||
Input.get_axis("move_up", "move_down")
|
||||
)
|
||||
var dir := Vector3(input.x, 0.0, input.y)
|
||||
var basis_yaw := _yaw_pivot.global_transform.basis
|
||||
var forward := -basis_yaw.z
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized()
|
||||
var right := basis_yaw.x
|
||||
right.y = 0.0
|
||||
right = right.normalized()
|
||||
var dir := (right * input.x) + (forward * -input.y)
|
||||
if dir.length() > 1.0:
|
||||
dir = dir.normalized()
|
||||
velocity = dir * move_speed
|
||||
var speed := move_speed
|
||||
if GameState.is_fat():
|
||||
speed *= 0.15
|
||||
elif GameState.is_glitched_slow():
|
||||
speed *= 0.55
|
||||
if GameState.is_shocked():
|
||||
velocity = Vector3.ZERO
|
||||
return
|
||||
velocity = dir * speed
|
||||
move_and_slide()
|
||||
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
_try_interact()
|
||||
if Input.is_action_just_pressed("attack"):
|
||||
|
|
@ -40,6 +94,28 @@ func _physics_process(_delta: float) -> void:
|
|||
|
||||
func lock_input(locked: bool) -> void:
|
||||
_locked = locked
|
||||
if locked:
|
||||
_set_mouse_captured(false)
|
||||
elif not get_tree().paused:
|
||||
_set_mouse_captured(true)
|
||||
|
||||
func set_gameplay_focused(focused: bool) -> void:
|
||||
## Called by pause UI: release mouse in menus, recapture in gameplay.
|
||||
if focused and not _locked:
|
||||
_set_mouse_captured(true)
|
||||
else:
|
||||
_set_mouse_captured(false)
|
||||
|
||||
func _apply_pitch() -> void:
|
||||
if _spring == null:
|
||||
return
|
||||
_spring.rotation.x = _pitch
|
||||
|
||||
func _set_mouse_captured(captured: bool) -> void:
|
||||
if captured:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
else:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func _try_interact() -> void:
|
||||
interacted.emit()
|
||||
|
|
@ -52,7 +128,14 @@ func _try_interact() -> void:
|
|||
func _try_attack() -> void:
|
||||
attacked.emit()
|
||||
AudioDirector.play_hit()
|
||||
var dmg := attack_damage
|
||||
if Inventory.has_item("tooth"):
|
||||
dmg = max(dmg, 2)
|
||||
if Inventory.has_item("moon_sword"):
|
||||
dmg = max(dmg, 99)
|
||||
if Inventory.has_item("wartchen"):
|
||||
dmg = max(dmg, 3)
|
||||
for body in get_tree().get_nodes_in_group("enemy"):
|
||||
if body is Node3D and global_position.distance_to(body.global_position) <= attack_range:
|
||||
if body.has_method("take_hit"):
|
||||
body.take_hit(attack_damage)
|
||||
body.take_hit(dmg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue