extends DamageableBody enum Equipment { AUTOCANNON, RIFLE, } const BASE_SPEED: float = 5.0 const BOOST_FORCE: float = 20.0 const JUMP_FORCE: float = 8.0 const INPUT_SENSITIVITY: float = 0.7 const TURN_SENSITIVITY: float = 0.04 @export var _equipment: Equipment = Equipment.AUTOCANNON var _weapon: Weapon = null var _heat: float = 0.0 var _damage: float = 0.0 @onready var camera_root: ThirdPersonCamera = $CameraRoot @onready var mesh: Node3D = $Mesh @onready var animation_tree: AnimationTree = $Mesh/Mech/AnimationTree @onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var shadow_listener: ShadowListener = $ShadowListener @onready var spark_shower: GPUParticles3D = $SparkShower @onready var autocannon: Weapon = %WeaponSlot/Autocannon @onready var rifle: Weapon = %WeaponSlot/Rifle @onready var _crosshair: Crosshair = get_tree().get_first_node_in_group("CrosshairGroup") @onready var _overlay: OverlayEffects = get_tree().get_first_node_in_group("OverlayEffectsGroup") @onready var _heat_meter: ProgressBar = get_tree().get_first_node_in_group("HeatMeterGroup") @onready var _damage_meter: ProgressBar = get_tree().get_first_node_in_group("DamageMeterGroup") @onready var _structure_meter: StructureMeter = get_tree().get_first_node_in_group("StructureMeterGroup") # gdlint: disable=class-definitions-order var structure: int: set(value): _structure_meter.structure_count = clamp( value, StructureMeter.MIN_STRUCTURE, StructureMeter.MAX_STRUCTURE ) get: return _structure_meter.structure_count # gdlint: enable=class-definitions-order func _ready() -> void: set_equipment(_equipment) func deal_damage(delta: float) -> void: _damage = clamp(_damage + delta, 0.0, 100.0) _damage_meter.value = _damage func deal_heat(delta: float) -> void: _heat = clamp(_heat + delta, 0.0, 100.0) _heat_meter.value = _heat func deal_structure(delta: int) -> void: structure += delta func set_equipment(value: Equipment) -> void: if _weapon: _weapon.deactivate() _weapon.visible = false _weapon = null match value: Equipment.AUTOCANNON: _weapon = autocannon _weapon.visible = true Equipment.RIFLE: _weapon = rifle _weapon.visible = true func is_boosting() -> bool: return animation_tree["parameters/boost/active"] func shutdown() -> void: animation_tree["parameters/online_state/transition_request"] = "offline" spark_shower.emitting = true func _physics_process(delta: float) -> void: var delta_factor: float = delta * GameState.TARGET_FPS # DEBUG BLOCK {{{ if Input.is_action_just_pressed("ui_left"): deal_structure(-1) if Input.is_action_just_pressed("ui_right"): deal_structure(1) if Input.is_action_just_pressed("ui_up"): deal_heat(1) deal_damage(-1) if Input.is_action_just_pressed("ui_down"): deal_heat(-1) deal_damage(1) if Input.is_action_just_pressed("ui_cancel"): shutdown() # DEBUG BLOCK }}} var effective_gravity: float = GameState.gravity if is_boosting(): _crosshair.jostle(Vector2(randf_range(-3, 3), randf_range(-3, 3))) effective_gravity *= 0.1 # Add the gravity. if not is_on_floor(): velocity.y -= effective_gravity * delta animation_tree["parameters/jump_state/transition_request"] = "in_air" animation_tree["parameters/anim_state/transition_request"] = "air" elif animation_tree["parameters/anim_state/current_state"] == "air": # If not on floor but still in jump state, transition back to landing animation_tree["parameters/jump_state/transition_request"] = "end" # Handle jump. if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y = JUMP_FORCE if is_boosting(): # Jump cancels boost animation_tree["parameters/boost/request"] = 2 # Abort # Get the input direction and handle the movement/deceleration. var input_dir: Vector2 = Input.get_vector("left", "right", "forward", "backward") if input_dir: # Transform movement based on camera angle var movement: Vector3 = ( ( camera_root.global_transform.basis * Vector3(input_dir.x, 0.0, input_dir.y) * Vector3(-1.0, 0.0, -1.0) ) . normalized() ) if not is_boosting(): velocity.x = lerpf( velocity.x, movement.x * BASE_SPEED, delta_factor * INPUT_SENSITIVITY ) velocity.z = lerpf( velocity.z, movement.z * BASE_SPEED, delta_factor * INPUT_SENSITIVITY ) if Input.is_action_just_pressed("boost"): velocity.x += movement.x * BOOST_FORCE velocity.z += movement.z * BOOST_FORCE velocity.y = 0.0 mesh.rotation.y = atan2(velocity.x, velocity.z) animation_tree["parameters/boost/request"] = 1 _overlay.play_boost() animation_player.play("boost_fov") animation_player.seek(0) # Slowly turn mesh towards camera vector when moving on ground if is_on_floor(): mesh.rotation.y = lerp_angle( mesh.rotation.y, camera_root.rotation.y, delta_factor * TURN_SENSITIVITY ) animation_tree["parameters/walk_space/blend_position"] = lerp( animation_tree["parameters/walk_space/blend_position"], input_dir, delta_factor * TURN_SENSITIVITY ) var drag: float = ( GameState.FRICTION if (is_on_floor() and not is_boosting()) else GameState.AIR_DRAG ) velocity.x = lerpf(velocity.x, 0.0, delta_factor * drag) velocity.z = lerpf(velocity.z, 0.0, delta_factor * drag) if Input.is_action_just_pressed("weapon_trigger"): _weapon.trigger() if Input.is_action_just_released("weapon_trigger"): _weapon.release() # Take heat if exposed to the sun: if not shadow_listener.in_shadow(): deal_heat(GameState.SOLAR_HEAT_RATE) move_and_slide() func _on_mech_stomp() -> void: _crosshair.jostle(Vector2(randf_range(-1, 1), randf_range(-1, 5))) # camera_root.jostle(randf_range(-2, 2))