Recommiting glbs for LFS
Deploy Promiscuity Auth API / deploy (push) Successful in 1m59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m16s
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled

This commit is contained in:
2026-05-12 15:34:12 -05:00
parent a9e546a121
commit fecbefc15c
141 changed files with 7099 additions and 2601 deletions
+110
View File
@@ -0,0 +1,110 @@
extends Node3D
@export var model_path: NodePath = NodePath("AshlingSwarmerModel")
@export var default_animation: StringName = &"Idle"
@export var autoplay: bool = true
var _animation_player: AnimationPlayer
var _model_node: Node3D
var _current_animation: StringName = &""
func _ready() -> void:
call_deferred("_setup_animation")
func _setup_animation() -> void:
_model_node = get_node_or_null(model_path) as Node3D
_animation_player = _find_animation_player(_model_node)
if _animation_player == null:
if autoplay:
push_warning("AshlingSwarmer: no AnimationPlayer found under %s." % model_path)
return
if autoplay:
play_animation(default_animation)
func play_animation(animation_name: StringName = &"") -> void:
if _animation_player == null:
_animation_player = _find_animation_player(get_node_or_null(model_path))
if _animation_player == null:
return
var requested_animation := animation_name if animation_name != StringName() else default_animation
var resolved_animation := _resolve_animation_name(requested_animation)
if resolved_animation == StringName():
push_warning("AshlingSwarmer: animation '%s' not found. Available animations: %s" % [requested_animation, _animation_player.get_animation_list()])
return
var animation := _animation_player.get_animation(resolved_animation)
if animation != null:
animation.loop_mode = Animation.LOOP_LINEAR
_animation_player.play(resolved_animation)
_current_animation = _classify_animation(resolved_animation)
func play_idle() -> void:
play_animation(&"Idle")
func play_run() -> void:
play_animation(&"Run")
func play_attack() -> void:
play_animation(&"Attack_Leap")
func play_hit() -> void:
play_animation(&"Hit")
func play_death() -> void:
play_animation(&"Death_Explode")
func _resolve_animation_name(animation_name: StringName) -> StringName:
if _animation_player.has_animation(animation_name):
return animation_name
var desired := String(animation_name).to_lower()
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower() == desired:
return StringName(candidate)
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower().contains(desired):
return StringName(candidate)
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower().contains("idle"):
return StringName(candidate)
var animations := _animation_player.get_animation_list()
if animations.size() > 0:
return StringName(animations[0])
return StringName()
func _classify_animation(animation_name: StringName) -> StringName:
var text := String(animation_name).to_lower()
if text.contains("run"):
return &"Run"
if text.contains("idle"):
return &"Idle"
if text.contains("attack"):
return &"Attack_Leap"
if text.contains("hit"):
return &"Hit"
if text.contains("death"):
return &"Death_Explode"
return animation_name
func _find_animation_player(root: Node) -> AnimationPlayer:
if root == null:
return null
if root is AnimationPlayer:
return root
for child in root.get_children():
var found := _find_animation_player(child)
if found != null:
return found
return null
@@ -0,0 +1 @@
uid://dptxcarifu7e1
@@ -0,0 +1,36 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://scenes/Characters/ashling_swarmer.gd" id="1_script"]
[ext_resource type="PackedScene" path="res://assets/models/AshlingSwarmer.glb" id="2_model"]
[ext_resource type="Script" path="res://scenes/Interaction/dialog_trigger_area.gd" id="3_dialog"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.45
height = 1.6
[sub_resource type="SphereShape3D" id="SphereShape3D_interact"]
radius = 1.8
[node name="AshlingSwarmer" type="Node3D"]
script = ExtResource("1_script")
autoplay = false
[node name="Body" type="StaticBody3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
shape = SubResource("CapsuleShape3D_body")
[node name="AshlingSwarmerModel" parent="." instance=ExtResource("2_model")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
[node name="InteractArea" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
script = ExtResource("3_dialog")
collision_layer = 2
collision_mask = 1
prompt_text = "Press E to inspect"
dialog_text = "Ashling Swarmer: A small ember-born creature watches you."
[node name="CollisionShape3D" type="CollisionShape3D" parent="InteractArea"]
shape = SubResource("SphereShape3D_interact")
+103 -103
View File
@@ -19,15 +19,15 @@ extends Node3D
@export var vertical_unlock_height: float = 0.6
@export var vertical_lock_smooth_speed: float = 6.0
@export var vertical_lock_hold_time: float = 0.3
@export var max_look_angle_deg: float = 90.0
@export var eye_return_speed: float = 0.2
@export var max_offset: float = 0.08
@export var side_eye_boost: float = 1.4
@export var head_path: NodePath = NodePath("Body/HeadPivot")
@export var head_turn_speed: float = 16.0
@export var head_max_yaw_deg: float = 55.0
@export var head_max_pitch_deg: float = 22.0
@export var max_look_angle_deg: float = 90.0
@export var eye_return_speed: float = 0.2
@export var max_offset: float = 0.08
@export var side_eye_boost: float = 1.4
@export var head_path: NodePath = NodePath("Body/HeadPivot")
@export var head_turn_speed: float = 16.0
@export var head_max_yaw_deg: float = 55.0
@export var head_max_pitch_deg: float = 22.0
var _left_pupil: Node3D
var _right_pupil: Node3D
var _body: RigidBody3D
@@ -35,9 +35,9 @@ var _left_base: Vector3
var _right_base: Vector3
var _camera: Camera3D
var _look_origin: Node3D
var _head: Node3D
var _head_base_rot: Vector3
var _vertical_lock_factor: float = 1.0
var _head: Node3D
var _head_base_rot: Vector3
var _vertical_lock_factor: float = 1.0
var _vertical_hold_timer: float = 0.0
var _look_reference: Node3D
var _look_target: Node3D
@@ -50,9 +50,9 @@ func _ready() -> void:
_right_pupil = get_node_or_null(right_pupil_path) as Node3D
if _left_pupil:
_left_base = _left_pupil.position
if _right_pupil:
_right_base = _right_pupil.position
_camera = _resolve_camera()
if _right_pupil:
_right_base = _right_pupil.position
_camera = _resolve_camera()
_look_origin = get_node_or_null(look_origin_path) as Node3D
_look_reference = get_node_or_null(look_reference_path) as Node3D
_look_target = get_node_or_null(look_target_path) as Node3D
@@ -76,8 +76,8 @@ func _physics_process(delta: float) -> void:
func _process(_delta: float) -> void:
_sync_body_to_root()
_update_pupils()
func _update_pupils() -> void:
if _look_target == null and look_target_path != NodePath(""):
_look_target = get_node_or_null(look_target_path) as Node3D
@@ -93,56 +93,56 @@ func _update_pupils() -> void:
if origin == null:
origin = self
var target := _look_target.global_position if _look_target != null else _camera.global_position
var dir_world := target - origin.global_position
if dir_world.length_squared() <= 0.0001:
return
dir_world = dir_world.normalized()
var reference := _look_reference if _look_reference != null else origin
var forward := -reference.global_basis.z
var min_dot := cos(deg_to_rad(max_look_angle_deg))
var can_look := dir_world.dot(forward) >= min_dot
if not can_look:
var delta := get_process_delta_time()
if _left_pupil:
var target_left := Vector3(_left_base.x, _left_base.y, _left_base.z)
var pos_left := _left_pupil.position
pos_left.x = move_toward(pos_left.x, target_left.x, eye_return_speed * delta)
pos_left.y = target_left.y
pos_left.z = move_toward(pos_left.z, target_left.z, eye_return_speed * delta)
_left_pupil.position = pos_left
if _right_pupil:
var target_right := Vector3(_right_base.x, _right_base.y, _right_base.z)
var pos_right := _right_pupil.position
pos_right.x = move_toward(pos_right.x, target_right.x, eye_return_speed * delta)
pos_right.y = target_right.y
pos_right.z = move_toward(pos_right.z, target_right.z, eye_return_speed * delta)
_right_pupil.position = pos_right
if _head:
_head.rotation.x = _head_base_rot.x
_head.rotation.y = lerp_angle(_head.rotation.y, _head_base_rot.y, head_turn_speed * delta)
return
if lock_vertical:
var origin_y := origin.global_position.y
var target_offset := target.y - origin_y
var is_above := target_offset > vertical_unlock_height
if is_above:
_vertical_hold_timer = vertical_lock_hold_time
else:
_vertical_hold_timer = max(0.0, _vertical_hold_timer - get_process_delta_time())
if is_above:
_vertical_lock_factor = 1.0
else:
var unlock := 1.0 if _vertical_hold_timer > 0.0 else 0.0
_vertical_lock_factor = move_toward(_vertical_lock_factor, unlock, vertical_lock_smooth_speed * get_process_delta_time())
target.y = lerp(origin_y, target.y, _vertical_lock_factor)
dir_world = target - origin.global_position
if dir_world.length_squared() <= 0.0001:
return
dir_world = dir_world.normalized()
if _left_pupil:
_update_eye(_left_pupil, _left_base, dir_world)
if _right_pupil:
_update_eye(_right_pupil, _right_base, dir_world)
var dir_world := target - origin.global_position
if dir_world.length_squared() <= 0.0001:
return
dir_world = dir_world.normalized()
var reference := _look_reference if _look_reference != null else origin
var forward := -reference.global_basis.z
var min_dot := cos(deg_to_rad(max_look_angle_deg))
var can_look := dir_world.dot(forward) >= min_dot
if not can_look:
var delta := get_process_delta_time()
if _left_pupil:
var target_left := Vector3(_left_base.x, _left_base.y, _left_base.z)
var pos_left := _left_pupil.position
pos_left.x = move_toward(pos_left.x, target_left.x, eye_return_speed * delta)
pos_left.y = target_left.y
pos_left.z = move_toward(pos_left.z, target_left.z, eye_return_speed * delta)
_left_pupil.position = pos_left
if _right_pupil:
var target_right := Vector3(_right_base.x, _right_base.y, _right_base.z)
var pos_right := _right_pupil.position
pos_right.x = move_toward(pos_right.x, target_right.x, eye_return_speed * delta)
pos_right.y = target_right.y
pos_right.z = move_toward(pos_right.z, target_right.z, eye_return_speed * delta)
_right_pupil.position = pos_right
if _head:
_head.rotation.x = _head_base_rot.x
_head.rotation.y = lerp_angle(_head.rotation.y, _head_base_rot.y, head_turn_speed * delta)
return
if lock_vertical:
var origin_y := origin.global_position.y
var target_offset := target.y - origin_y
var is_above := target_offset > vertical_unlock_height
if is_above:
_vertical_hold_timer = vertical_lock_hold_time
else:
_vertical_hold_timer = max(0.0, _vertical_hold_timer - get_process_delta_time())
if is_above:
_vertical_lock_factor = 1.0
else:
var unlock := 1.0 if _vertical_hold_timer > 0.0 else 0.0
_vertical_lock_factor = move_toward(_vertical_lock_factor, unlock, vertical_lock_smooth_speed * get_process_delta_time())
target.y = lerp(origin_y, target.y, _vertical_lock_factor)
dir_world = target - origin.global_position
if dir_world.length_squared() <= 0.0001:
return
dir_world = dir_world.normalized()
if _left_pupil:
_update_eye(_left_pupil, _left_base, dir_world)
if _right_pupil:
_update_eye(_right_pupil, _right_base, dir_world)
if _head:
_update_head(dir_world)
@@ -247,39 +247,39 @@ func _resolve_active_camera() -> Camera3D:
func _resolve_camera() -> Camera3D:
if camera_path != NodePath(""):
var from_path := get_node_or_null(camera_path) as Camera3D
if from_path:
return from_path
var viewport_cam := get_viewport().get_camera_3d()
if viewport_cam:
return viewport_cam
var by_name := get_tree().get_root().find_child("Camera3D", true, false) as Camera3D
return by_name
func _update_eye(eye: Node3D, base_pos: Vector3, dir_world: Vector3) -> void:
var parent := eye.get_parent() as Node3D
if parent == null:
return
var dir_local := parent.global_basis.inverse() * dir_world
var flat := Vector2(dir_local.x, dir_local.y)
flat.x *= side_eye_boost
if flat.length() > 1.0:
flat = flat.normalized()
var offset := Vector3(flat.x, flat.y, 0.0) * max_offset
eye.position = base_pos + offset
func _update_head(dir_world: Vector3) -> void:
var parent := _head.get_parent() as Node3D
if parent == null:
return
var dir_local := parent.global_basis.inverse() * dir_world
var yaw := atan2(-dir_local.x, -dir_local.z)
var pitch := atan2(dir_local.y, -dir_local.z)
yaw = clamp(yaw, deg_to_rad(-head_max_yaw_deg), deg_to_rad(head_max_yaw_deg))
pitch = clamp(pitch, deg_to_rad(-head_max_pitch_deg), deg_to_rad(head_max_pitch_deg))
var target := Vector3(_head_base_rot.x + pitch, _head_base_rot.y + yaw, _head_base_rot.z)
_head.rotation.x = lerp_angle(_head.rotation.x, target.x, head_turn_speed * get_process_delta_time())
_head.rotation.y = lerp_angle(_head.rotation.y, target.y, head_turn_speed * get_process_delta_time())
if camera_path != NodePath(""):
var from_path := get_node_or_null(camera_path) as Camera3D
if from_path:
return from_path
var viewport_cam := get_viewport().get_camera_3d()
if viewport_cam:
return viewport_cam
var by_name := get_tree().get_root().find_child("Camera3D", true, false) as Camera3D
return by_name
func _update_eye(eye: Node3D, base_pos: Vector3, dir_world: Vector3) -> void:
var parent := eye.get_parent() as Node3D
if parent == null:
return
var dir_local := parent.global_basis.inverse() * dir_world
var flat := Vector2(dir_local.x, dir_local.y)
flat.x *= side_eye_boost
if flat.length() > 1.0:
flat = flat.normalized()
var offset := Vector3(flat.x, flat.y, 0.0) * max_offset
eye.position = base_pos + offset
func _update_head(dir_world: Vector3) -> void:
var parent := _head.get_parent() as Node3D
if parent == null:
return
var dir_local := parent.global_basis.inverse() * dir_world
var yaw := atan2(-dir_local.x, -dir_local.z)
var pitch := atan2(dir_local.y, -dir_local.z)
yaw = clamp(yaw, deg_to_rad(-head_max_yaw_deg), deg_to_rad(head_max_yaw_deg))
pitch = clamp(pitch, deg_to_rad(-head_max_pitch_deg), deg_to_rad(head_max_pitch_deg))
var target := Vector3(_head_base_rot.x + pitch, _head_base_rot.y + yaw, _head_base_rot.z)
_head.rotation.x = lerp_angle(_head.rotation.x, target.x, head_turn_speed * get_process_delta_time())
_head.rotation.y = lerp_angle(_head.rotation.y, target.y, head_turn_speed * get_process_delta_time())
+101 -101
View File
@@ -2,119 +2,119 @@
[ext_resource type="Script" path="res://scenes/Characters/repo_bot.gd" id="1_repo_bot"]
[ext_resource type="Script" path="res://scenes/Interaction/dialog_trigger_area.gd" id="2_dialog"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_body"]
albedo_color = Color(0.78, 0.8, 0.82, 1)
metallic = 0.2
roughness = 0.35
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_accent"]
albedo_color = Color(0.25, 0.82, 0.55, 1)
emission_enabled = true
emission = Color(0.25, 0.82, 0.55, 1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eye_white"]
albedo_color = Color(0.95, 0.95, 0.95, 1)
roughness = 0.2
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pupil"]
albedo_color = Color(0.02, 0.02, 0.02, 1)
roughness = 0.8
[sub_resource type="CapsuleMesh" id="CapsuleMesh_body"]
radius = 0.25
height = 0.6
material = SubResource("StandardMaterial3D_body")
[sub_resource type="SphereMesh" id="SphereMesh_head"]
radius = 0.22
height = 0.44
material = SubResource("StandardMaterial3D_body")
[sub_resource type="SphereMesh" id="SphereMesh_eye_white"]
radius = 0.075
height = 0.15
material = SubResource("StandardMaterial3D_eye_white")
[sub_resource type="SphereMesh" id="SphereMesh_pupil"]
radius = 0.028
height = 0.056
material = SubResource("StandardMaterial3D_pupil")
[sub_resource type="CylinderMesh" id="CylinderMesh_limb"]
top_radius = 0.06
bottom_radius = 0.06
height = 0.35
material = SubResource("StandardMaterial3D_body")
[sub_resource type="BoxMesh" id="BoxMesh_pack"]
size = Vector3(0.26, 0.3, 0.12)
material = SubResource("StandardMaterial3D_accent")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_body"]
albedo_color = Color(0.78, 0.8, 0.82, 1)
metallic = 0.2
roughness = 0.35
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_accent"]
albedo_color = Color(0.25, 0.82, 0.55, 1)
emission_enabled = true
emission = Color(0.25, 0.82, 0.55, 1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eye_white"]
albedo_color = Color(0.95, 0.95, 0.95, 1)
roughness = 0.2
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pupil"]
albedo_color = Color(0.02, 0.02, 0.02, 1)
roughness = 0.8
[sub_resource type="CapsuleMesh" id="CapsuleMesh_body"]
radius = 0.25
height = 0.6
material = SubResource("StandardMaterial3D_body")
[sub_resource type="SphereMesh" id="SphereMesh_head"]
radius = 0.22
height = 0.44
material = SubResource("StandardMaterial3D_body")
[sub_resource type="SphereMesh" id="SphereMesh_eye_white"]
radius = 0.075
height = 0.15
material = SubResource("StandardMaterial3D_eye_white")
[sub_resource type="SphereMesh" id="SphereMesh_pupil"]
radius = 0.028
height = 0.056
material = SubResource("StandardMaterial3D_pupil")
[sub_resource type="CylinderMesh" id="CylinderMesh_limb"]
top_radius = 0.06
bottom_radius = 0.06
height = 0.35
material = SubResource("StandardMaterial3D_body")
[sub_resource type="BoxMesh" id="BoxMesh_pack"]
size = Vector3(0.26, 0.3, 0.12)
material = SubResource("StandardMaterial3D_accent")
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.3
height = 1.1
[sub_resource type="SphereShape3D" id="SphereShape3D_interact"]
radius = 1.7
[node name="RepoBot" type="Node3D"]
script = ExtResource("1_repo_bot")
[node name="RepoBot" type="Node3D"]
script = ExtResource("1_repo_bot")
[node name="Body" type="RigidBody3D" parent="."]
mass = 1.5
axis_lock_angular_x = true
axis_lock_angular_z = true
angular_damp = 8.0
linear_damp = 0.5
[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.55, 0)
shape = SubResource("CapsuleShape3D_body")
[node name="Torso" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
mesh = SubResource("CapsuleMesh_body")
[node name="HeadPivot" type="Node3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.95, 0)
[node name="Head" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
mesh = SubResource("SphereMesh_head")
[node name="EyeLeft" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.09, 0, -0.205)
mesh = SubResource("SphereMesh_eye_white")
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeLeft"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
mesh = SubResource("SphereMesh_pupil")
[node name="EyeRight" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.09, 0, -0.205)
mesh = SubResource("SphereMesh_eye_white")
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeRight"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
mesh = SubResource("SphereMesh_pupil")
[node name="ArmLeft" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.32, 0.55, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="ArmRight" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.32, 0.55, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="LegLeft" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.12, 0.15, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="LegRight" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.12, 0.15, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.55, 0)
shape = SubResource("CapsuleShape3D_body")
[node name="Torso" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
mesh = SubResource("CapsuleMesh_body")
[node name="HeadPivot" type="Node3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.95, 0)
[node name="Head" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
mesh = SubResource("SphereMesh_head")
[node name="EyeLeft" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.09, 0, -0.205)
mesh = SubResource("SphereMesh_eye_white")
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeLeft"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
mesh = SubResource("SphereMesh_pupil")
[node name="EyeRight" type="MeshInstance3D" parent="Body/HeadPivot"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.09, 0, -0.205)
mesh = SubResource("SphereMesh_eye_white")
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeRight"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
mesh = SubResource("SphereMesh_pupil")
[node name="ArmLeft" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.32, 0.55, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="ArmRight" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.32, 0.55, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="LegLeft" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.12, 0.15, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="LegRight" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.12, 0.15, 0)
mesh = SubResource("CylinderMesh_limb")
[node name="Backpack" type="MeshInstance3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0.22)
mesh = SubResource("BoxMesh_pack")
+4 -2
View File
@@ -21,8 +21,10 @@ func _toggle_menu():
_animate_menu(false)
func _arrange_buttons():
var buttons = get_children()
var buttons = get_children().filter(func(child): return child is Control)
var count = buttons.size()
if count == 0:
return
for i in range(count):
# Calculate the angle for each button in radians
@@ -36,7 +38,7 @@ func _arrange_buttons():
func _animate_menu(opening: bool):
var tween = create_tween().set_parallel(true)
for button in get_children():
for button in get_children().filter(func(child): return child is Control):
var final_scale = Vector2.ONE if opening else Vector2.ZERO
tween.tween_property(button, "scale", final_scale, transition_speed).from(Vector2.ZERO if opening else Vector2.ONE)
@@ -16,6 +16,21 @@ extends Node3D
one_shot = value
_sync_teleporter()
@export var target_spawn_name: StringName = &"":
set(value):
target_spawn_name = value
_sync_teleporter()
@export var quest_event_name: StringName = &"":
set(value):
quest_event_name = value
_sync_teleporter()
@export var quest_id_filter: String = "":
set(value):
quest_id_filter = value
_sync_teleporter()
@onready var teleporter: Area3D = $Teleporter
@@ -29,3 +44,6 @@ func _sync_teleporter() -> void:
teleporter.set("target_scene_path", target_scene_path)
teleporter.set("target_group", target_group)
teleporter.set("one_shot", one_shot)
teleporter.set("target_spawn_name", target_spawn_name)
teleporter.set("quest_event_name", quest_event_name)
teleporter.set("quest_id_filter", quest_id_filter)
@@ -3,6 +3,9 @@ extends Area3D
@export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn"
@export var target_group: StringName = &"player"
@export var one_shot := true
@export var target_spawn_name: StringName = &""
@export var quest_event_name: StringName = &""
@export var quest_id_filter: String = ""
var _is_transitioning := false
@@ -22,6 +25,9 @@ func _on_body_entered(body: Node) -> void:
if not ResourceLoader.exists(target_scene_path):
push_warning("Teleporter target scene does not exist: %s" % target_scene_path)
return
_emit_quest_event(body)
if TeleportState != null:
TeleportState.request_spawn(target_spawn_name)
_is_transitioning = true
if one_shot:
@@ -29,6 +35,17 @@ func _on_body_entered(body: Node) -> void:
call_deferred("_deferred_change_scene")
func _emit_quest_event(body: Node) -> void:
if quest_event_name == StringName() or QuestManager == null:
return
if quest_id_filter.strip_edges() != "" and QuestManager.get_active_quest_id() != StringName(quest_id_filter):
return
QuestManager.emit_event(quest_event_name, {
"body": body,
"source": self,
})
func _deferred_change_scene() -> void:
var err := get_tree().change_scene_to_file(target_scene_path)
if err == OK:
+13
View File
@@ -0,0 +1,13 @@
extends Node
var _requested_spawn_name: StringName = &""
func request_spawn(spawn_name: StringName) -> void:
_requested_spawn_name = spawn_name
func consume_spawn_name() -> StringName:
var spawn_name := _requested_spawn_name
_requested_spawn_name = &""
return spawn_name
@@ -0,0 +1 @@
uid://ds35gqf0obsxf
+4 -2
View File
@@ -21,8 +21,10 @@ func _toggle_menu():
_animate_menu(false)
func _arrange_buttons():
var buttons = get_children()
var buttons = get_children().filter(func(child): return child is Control)
var count = buttons.size()
if count == 0:
return
for i in range(count):
# Calculate the angle for each button in radians
@@ -36,6 +38,6 @@ func _arrange_buttons():
func _animate_menu(opening: bool):
var tween = create_tween().set_parallel(true)
for button in get_children():
for button in get_children().filter(func(child): return child is Control):
var final_scale = Vector2.ONE if opening else Vector2.ZERO
tween.tween_property(button, "scale", final_scale, transition_speed).from(Vector2.ZERO if opening else Vector2.ONE)
+77
View File
@@ -0,0 +1,77 @@
extends Node3D
@export var player_spawn_position := Vector3(0.0, 0.0, 0.0)
@export var day_length := 120.0
@export var start_light_angle := -90.0
@onready var _player: RigidBody3D = get_node_or_null("Player") as RigidBody3D
@onready var _sun: DirectionalLight3D = $DirectionalLight3D
@onready var _quest_text: RichTextLabel = get_node_or_null("PhoneUI/Control/PhoneFrame/QuestText") as RichTextLabel
var _time := 0.0
func _ready() -> void:
_move_player_to_spawn()
_setup_quest_ui()
func _process(delta: float) -> void:
_update_day_night(delta)
func _move_player_to_spawn() -> void:
if _player == null:
return
var spawn_marker := _consume_spawn_marker()
if spawn_marker != null:
_player.call("teleport_to_spawn", spawn_marker.global_transform)
else:
_player.global_position = player_spawn_position
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
func _consume_spawn_marker() -> Node3D:
if TeleportState == null:
return null
var spawn_name: StringName = TeleportState.consume_spawn_name()
if spawn_name == StringName():
return null
return find_child(String(spawn_name), true, false) as Node3D
func _update_day_night(delta: float) -> void:
if _sun == null or day_length <= 0.0:
return
_time = fmod(_time + delta, day_length)
var t: float = _time / day_length
var angle: float = lerp(start_light_angle, start_light_angle + 360.0, t)
_sun.rotation_degrees.x = angle
var energy_curve: float = -sin((t * TAU) + (start_light_angle * PI / 180.0))
_sun.light_energy = clamp((energy_curve * 1.0) + 0.2, 0.0, 1.2)
func _setup_quest_ui() -> void:
if QuestManager == null:
return
if not QuestManager.is_connected("quest_state_changed", Callable(self, "_refresh_quest_ui")):
QuestManager.quest_state_changed.connect(_refresh_quest_ui)
_refresh_quest_ui()
func _refresh_quest_ui() -> void:
if _quest_text == null or QuestManager == null:
return
var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)):
_quest_text.text = "No active quest."
return
var title := String(state.get("title", "Quest"))
if bool(state.get("completed", false)):
_quest_text.text = "[b]%s[/b]\nComplete." % title
return
var step_index: int = int(state.get("current_step_index", 0))
var total_steps: int = int(state.get("total_steps", 0))
var step_text := String(state.get("current_step_text", ""))
_quest_text.text = "[b]%s[/b]\nStep %d/%d\n%s" % [title, step_index + 1, total_steps, step_text]
+1
View File
@@ -0,0 +1 @@
uid://dgqayogt2d14g
+134
View File
@@ -0,0 +1,134 @@
[gd_scene load_steps=11 format=3]
[ext_resource type="Script" path="res://scenes/Levels/ashling_level.gd" id="1_level"]
[ext_resource type="Script" path="res://scenes/player.gd" id="2_player"]
[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="3_model"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="4_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="5_ground_mat"]
[ext_resource type="PackedScene" uid="uid://dp6jk0k3o4v1u" path="res://scenes/UI/pause_menu.tscn" id="6_pause_menu"]
[ext_resource type="PackedScene" path="res://scenes/Characters/ashling_swarmer.tscn" id="7_ashling"]
[ext_resource type="PackedScene" uid="uid://bnwpu7p8sbsfa" path="res://scenes/Interaction/RadialCommandMenu.tscn" id="8_radial_menu"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
[sub_resource type="BoxShape3D" id="BoxShape3D_ground"]
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxMesh" id="BoxMesh_ground"]
material = ExtResource("5_ground_mat")
size = Vector3(1080, 2, 1080)
[node name="AshlingLevel" type="Node3D"]
script = ExtResource("1_level")
[node name="Ground" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"]
shape = SubResource("BoxShape3D_ground")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
mesh = SubResource("BoxMesh_ground")
[node name="Player" type="RigidBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
script = ExtResource("2_player")
camera_path = NodePath("Camera3D")
phone_path = NodePath("../PhoneUI")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("SphereShape3D_player")
[node name="TestCharAnimated" parent="Player" instance=ExtResource("3_model")]
transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(0.9989785, -4.651856e-10, -0.045188628, 0.006969331, 0.9880354, 0.15407, 0.044647958, -0.15422754, 0.9870261, 0.22036135, 1.8988357, 0.64972365)
current = true
fov = 49.0
[node name="SpotLight3D" type="SpotLight3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 0.906308, -0.422618, 0, 0.422618, 0.906308, 0, 1.7, -0.35)
visible = false
spot_range = 30.0
spot_angle = 25.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0)
shadow_enabled = true
[node name="EntrySpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
[node name="AshlingSwarmer" parent="." instance=ExtResource("7_ashling")]
transform = Transform3D(0.8660254, 0, -0.5, 0, 1, 0, 0.5, 0, 0.8660254, 0, 0, -4)
[node name="Label3D" type="Label3D" parent="AshlingSwarmer"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 2.6, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "ASHLING"
[node name="ReturnTeleporter" parent="." instance=ExtResource("4_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.5)
target_scene_path = "res://scenes/Levels/level.tscn"
target_spawn_name = &"AshlingReturnSpawn"
[node name="Label3D" type="Label3D" parent="ReturnTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "PLAYGROUND"
[node name="PauseMenu" parent="." instance=ExtResource("6_pause_menu")]
[node name="PhoneUI" type="CanvasLayer" parent="."]
layer = 5
visible = false
[node name="Control" type="Control" parent="PhoneUI"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -180.0
offset_top = -320.0
offset_right = 180.0
offset_bottom = 320.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.08, 0.08, 0.1, 1)
[node name="QuestTitle" type="Label" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 18.0
offset_right = 150.0
offset_bottom = 41.0
text = "Quest Log"
[node name="QuestText" type="RichTextLabel" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 52.0
offset_right = 344.0
offset_bottom = 613.0
bbcode_enabled = true
text = "No active quest."
scroll_active = false
[node name="RadialCommandMenu" parent="PhoneUI/Control" instance=ExtResource("8_radial_menu")]
layout_mode = 1
+24 -4
View File
@@ -22,9 +22,9 @@ const FIRST_QUEST := {
"description": "Get familiar with movement and vehicles.",
"steps": [
{
"id": "enter_vehicle",
"text": "Get in the car (E).",
"complete_event": "entered_vehicle",
"id": "enter_car_feature",
"text": "Walk through the car teleporter.",
"complete_event": "entered_car_feature",
},
{
"id": "reach_checkpoint",
@@ -35,6 +35,7 @@ const FIRST_QUEST := {
}
func _ready() -> void:
_move_player_to_requested_spawn()
_apply_sun_state(0.0)
_setup_quests()
if _should_show_spawn_dialog() and DialogSystem and DialogSystem.has_method("show_text"):
@@ -45,7 +46,6 @@ func _ready() -> void:
await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout
if DialogSystem and DialogSystem.has_method("close_if_text"):
DialogSystem.close_if_text(spawn_dialog_text)
_show_quest_intro_dialog()
func _process(delta):
time = fmod((time + delta), day_length)
@@ -132,3 +132,23 @@ func _mark_spawn_dialog_shown() -> void:
if QuestManager == null:
return
QuestManager.set_meta(SPAWN_DIALOG_META_KEY, true)
func _move_player_to_requested_spawn() -> void:
if _player == null or TeleportState == null:
return
var spawn_name: StringName = TeleportState.consume_spawn_name()
if spawn_name == StringName():
return
var spawn_marker := find_child(String(spawn_name), true, false) as Node3D
if spawn_marker == null:
return
if _player.has_method("teleport_to_spawn"):
_player.call("teleport_to_spawn", spawn_marker.global_transform)
elif _player is RigidBody3D:
var player_body := _player as RigidBody3D
player_body.global_transform = spawn_marker.global_transform
player_body.linear_velocity = Vector3.ZERO
player_body.angular_velocity = Vector3.ZERO
else:
(_player as Node3D).global_transform = spawn_marker.global_transform
+59 -50
View File
@@ -5,7 +5,6 @@
[ext_resource type="Script" uid="uid://bpxggc8nr6tf6" path="res://scenes/player.gd" id="1_muv8p"]
[ext_resource type="PackedScene" uid="uid://c5of6aaxop1hl" path="res://scenes/block.tscn" id="2_tc7dm"]
[ext_resource type="PackedScene" uid="uid://dp6jk0k3o4v1u" path="res://scenes/UI/pause_menu.tscn" id="3_pause_menu"]
[ext_resource type="PackedScene" path="res://scenes/Characters/repo_bot.tscn" id="4_repo"]
[ext_resource type="PackedScene" path="res://scenes/Vehicles/car.tscn" id="5_car"]
[ext_resource type="PackedScene" uid="uid://bnqaqbgynoyys" path="res://assets/models/TestCharAnimated.glb" id="5_fi66n"]
[ext_resource type="Script" uid="uid://bk53njt7i3kmv" path="res://scenes/Interaction/dialog_trigger_area.gd" id="6_dialog"]
@@ -15,7 +14,6 @@
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="9_ground_mat"]
[ext_resource type="Texture2D" uid="uid://c4ggdp0kg5wjk" path="res://addons/simplegrasstextured/textures/grassbushcc008.png" id="10_loupo"]
[ext_resource type="Script" uid="uid://2juaclm8gc1n" path="res://addons/simplegrasstextured/grass.gd" id="11_1meta"]
[ext_resource type="Script" uid="uid://bv2ei5rg0dn3d" path="res://scenes/Levels/RadialCommandMenu.gd" id="15_18iqp"]
[ext_resource type="Texture2D" uid="uid://b2dmnrm3ubjon" path="res://assets/textures/stolenFire.png" id="16_i35yb"]
[ext_resource type="PackedScene" uid="uid://bnwpu7p8sbsfa" path="res://scenes/Interaction/RadialCommandMenu.tscn" id="16_px5jg"]
@@ -401,10 +399,6 @@ script = ExtResource("1_a4mo8")
[node name="human" parent="." unique_id=333471061 instance=ExtResource("1_eg4yq")]
[node name="RepoBot" parent="." unique_id=1519051606 instance=ExtResource("4_repo")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9426608, 0, -4.4451966)
look_target_path = NodePath("../Player")
[node name="Thing" type="RigidBody3D" parent="." unique_id=32794772]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.7986288)
physics_material_override = SubResource("PhysicsMaterial_2q6dc")
@@ -450,48 +444,6 @@ root_motion_track = NodePath("Armature/Skeleton3D:mixamorig_Hips")
tree_root = SubResource("AnimationNodeStateMachine_bwsrl")
anim_player = NodePath("../TestCharAnimated/AnimationPlayer")
[node name="Car" parent="." unique_id=1231424399 instance=ExtResource("5_car")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -3)
[node name="DialogZone" type="Area3D" parent="." unique_id=616412893]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.5, 0, -2.5)
script = ExtResource("6_dialog")
prompt_text = "Press E to inspect area"
dialog_text = "Dialog trigger area"
[node name="CollisionShape3D" type="CollisionShape3D" parent="DialogZone" unique_id=1201310304]
shape = SubResource("SphereShape3D_dialog_zone")
[node name="Visual" type="MeshInstance3D" parent="DialogZone" unique_id=851990198]
mesh = SubResource("SphereMesh_dialog_zone")
[node name="AutoDialogZone" type="Area3D" parent="." unique_id=123053838]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, -6.5)
script = ExtResource("6_dialog")
dialog_text = "Auto dialog trigger"
auto_popup = true
auto_popup_close_on_exit = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="AutoDialogZone" unique_id=54253856]
shape = SubResource("SphereShape3D_dialog_zone")
[node name="Visual" type="MeshInstance3D" parent="AutoDialogZone" unique_id=1933827323]
transform = Transform3D(0.8, 0, 0, 0, 0.8, 0, 0, 0, 0.8, 0, 0, 0)
mesh = SubResource("SphereMesh_dialog_zone")
[node name="QuestCheckpoint" type="Area3D" parent="." unique_id=1060088011]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9, 0, -10)
script = ExtResource("7_qtrigger")
event_name = &"reach_checkpoint"
target_group = &"vehicle"
quest_id_filter = "first_drive"
[node name="CollisionShape3D" type="CollisionShape3D" parent="QuestCheckpoint" unique_id=1399159753]
shape = SubResource("SphereShape3D_checkpoint")
[node name="Visual" type="MeshInstance3D" parent="QuestCheckpoint" unique_id=1488541765]
mesh = SubResource("SphereMesh_checkpoint")
[node name="Ground" type="StaticBody3D" parent="." unique_id=1718381675]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
@@ -548,7 +500,6 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("15_18iqp")
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control" unique_id=1204675817]
layout_mode = 1
@@ -589,8 +540,66 @@ layout_mode = 1
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1808155101]
environment = SubResource("Environment_a4mo8")
[node name="LevelExitTeleporter" parent="." unique_id=1508738453 instance=ExtResource("8_teleporter")]
[node name="CarTeleporter" parent="." unique_id=1508738453 instance=ExtResource("8_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 0)
target_spawn_name = &"EntrySpawn"
quest_event_name = &"entered_car_feature"
quest_id_filter = "first_drive"
[node name="Label3D" type="Label3D" parent="CarTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "CAR"
[node name="TriggerZonesTeleporter" parent="." instance=ExtResource("8_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 6)
target_scene_path = "res://scenes/Levels/trigger_zones_level.tscn"
target_spawn_name = &"EntrySpawn"
[node name="Label3D" type="Label3D" parent="TriggerZonesTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "TRIGGERS"
[node name="RepoBotTeleporter" parent="." instance=ExtResource("8_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 12)
target_scene_path = "res://scenes/Levels/repo_bot_level.tscn"
target_spawn_name = &"EntrySpawn"
[node name="Label3D" type="Label3D" parent="RepoBotTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "REPO BOT"
[node name="AshlingTeleporter" parent="." instance=ExtResource("8_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 18)
target_scene_path = "res://scenes/Levels/ashling_level.tscn"
target_spawn_name = &"EntrySpawn"
[node name="Label3D" type="Label3D" parent="AshlingTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "ASHLING"
[node name="CarReturnSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, -3)
[node name="TriggerZonesReturnSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 3)
[node name="RepoBotReturnSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 9)
[node name="AshlingReturnSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 15)
[node name="GPUParticles3D" type="GPUParticles3D" parent="." unique_id=2107882789]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.8036127, 1.8337743, -6.1764746)
File diff suppressed because it is too large Load Diff
+27
View File
@@ -165,6 +165,26 @@ layout_mode = 2
size_flags_vertical = 3
select_mode = 0
[node name="RecipesPanel" type="PanelContainer" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="VBoxContainer" type="VBoxContainer" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/RecipesPanel"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="RecipesLabel" type="Label" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/RecipesPanel/VBoxContainer"]
layout_mode = 2
text = "Crafting"
horizontal_alignment = 1
[node name="RecipesList" type="ItemList" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/RecipesPanel/VBoxContainer"]
custom_minimum_size = Vector2(0, 240)
layout_mode = 2
size_flags_vertical = 3
select_mode = 0
[node name="ControlsPanel" type="PanelContainer" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer"]
layout_mode = 2
@@ -220,6 +240,11 @@ layout_mode = 2
theme = ExtResource("4_button_theme")
text = "PICK UP"
[node name="CraftButton" type="Button" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow"]
layout_mode = 2
theme = ExtResource("4_button_theme")
text = "CRAFT"
[node name="RefreshButton" type="Button" parent="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow"]
layout_mode = 2
theme = ExtResource("4_button_theme")
@@ -377,9 +402,11 @@ text = ""
[connection signal="pressed" from="PauseMenu/CenterContainer/Panel/VBoxContainer/MainMenuButton" to="." method="_on_pause_main_menu_pressed"]
[connection signal="item_selected" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/CharacterPanel/VBoxContainer/CharacterItems" to="." method="_on_character_items_selected"]
[connection signal="item_selected" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/GroundPanel/VBoxContainer/GroundItems" to="." method="_on_ground_items_selected"]
[connection signal="item_selected" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/Columns/RecipesPanel/VBoxContainer/RecipesList" to="." method="_on_recipe_selected"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/MoveButton" to="." method="_on_inventory_move_pressed"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/DropButton" to="." method="_on_inventory_drop_pressed"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/PickupButton" to="." method="_on_inventory_pickup_pressed"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/CraftButton" to="." method="_on_inventory_craft_pressed"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/RefreshButton" to="." method="_on_inventory_refresh_pressed"]
[connection signal="pressed" from="InventoryMenu/MarginContainer/Panel/VBoxContainer/ControlsPanel/VBoxContainer/ActionRow/CloseButton" to="." method="_on_inventory_close_pressed"]
[connection signal="item_selected" from="MailMenu/MarginContainer/Panel/VBoxContainer/Columns/InboxPanel/VBoxContainer/InboxItems" to="." method="_on_mail_inbox_selected"]
+77
View File
@@ -0,0 +1,77 @@
extends Node3D
@export var player_spawn_position := Vector3(0.0, 0.0, 0.0)
@export var day_length := 120.0
@export var start_light_angle := -90.0
@onready var _player: RigidBody3D = get_node_or_null("Player") as RigidBody3D
@onready var _sun: DirectionalLight3D = $DirectionalLight3D
@onready var _quest_text: RichTextLabel = get_node_or_null("PhoneUI/Control/PhoneFrame/QuestText") as RichTextLabel
var _time := 0.0
func _ready() -> void:
_move_player_to_spawn()
_setup_quest_ui()
func _process(delta: float) -> void:
_update_day_night(delta)
func _move_player_to_spawn() -> void:
if _player == null:
return
var spawn_marker := _consume_spawn_marker()
if spawn_marker != null:
_player.call("teleport_to_spawn", spawn_marker.global_transform)
else:
_player.global_position = player_spawn_position
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
func _consume_spawn_marker() -> Node3D:
if TeleportState == null:
return null
var spawn_name: StringName = TeleportState.consume_spawn_name()
if spawn_name == StringName():
return null
return find_child(String(spawn_name), true, false) as Node3D
func _update_day_night(delta: float) -> void:
if _sun == null or day_length <= 0.0:
return
_time = fmod(_time + delta, day_length)
var t: float = _time / day_length
var angle: float = lerp(start_light_angle, start_light_angle + 360.0, t)
_sun.rotation_degrees.x = angle
var energy_curve: float = -sin((t * TAU) + (start_light_angle * PI / 180.0))
_sun.light_energy = clamp((energy_curve * 1.0) + 0.2, 0.0, 1.2)
func _setup_quest_ui() -> void:
if QuestManager == null:
return
if not QuestManager.is_connected("quest_state_changed", Callable(self, "_refresh_quest_ui")):
QuestManager.quest_state_changed.connect(_refresh_quest_ui)
_refresh_quest_ui()
func _refresh_quest_ui() -> void:
if _quest_text == null or QuestManager == null:
return
var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)):
_quest_text.text = "No active quest."
return
var title := String(state.get("title", "Quest"))
if bool(state.get("completed", false)):
_quest_text.text = "[b]%s[/b]\nComplete." % title
return
var step_index: int = int(state.get("current_step_index", 0))
var total_steps: int = int(state.get("total_steps", 0))
var step_text := String(state.get("current_step_text", ""))
_quest_text.text = "[b]%s[/b]\nStep %d/%d\n%s" % [title, step_index + 1, total_steps, step_text]
+1
View File
@@ -0,0 +1 @@
uid://cy2vwcmtqsr4o
+135
View File
@@ -0,0 +1,135 @@
[gd_scene load_steps=11 format=3]
[ext_resource type="Script" path="res://scenes/Levels/repo_bot_level.gd" id="1_level"]
[ext_resource type="Script" path="res://scenes/player.gd" id="2_player"]
[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="3_model"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="4_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="5_ground_mat"]
[ext_resource type="PackedScene" uid="uid://dp6jk0k3o4v1u" path="res://scenes/UI/pause_menu.tscn" id="6_pause_menu"]
[ext_resource type="PackedScene" path="res://scenes/Characters/repo_bot.tscn" id="7_repo_bot"]
[ext_resource type="PackedScene" uid="uid://bnwpu7p8sbsfa" path="res://scenes/Interaction/RadialCommandMenu.tscn" id="8_radial_menu"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
[sub_resource type="BoxShape3D" id="BoxShape3D_ground"]
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxMesh" id="BoxMesh_ground"]
material = ExtResource("5_ground_mat")
size = Vector3(1080, 2, 1080)
[node name="RepoBotLevel" type="Node3D"]
script = ExtResource("1_level")
[node name="Ground" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"]
shape = SubResource("BoxShape3D_ground")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
mesh = SubResource("BoxMesh_ground")
[node name="Player" type="RigidBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
script = ExtResource("2_player")
camera_path = NodePath("Camera3D")
phone_path = NodePath("../PhoneUI")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("SphereShape3D_player")
[node name="TestCharAnimated" parent="Player" instance=ExtResource("3_model")]
transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(0.9989785, -4.651856e-10, -0.045188628, 0.006969331, 0.9880354, 0.15407, 0.044647958, -0.15422754, 0.9870261, 0.22036135, 1.8988357, 0.64972365)
current = true
fov = 49.0
[node name="SpotLight3D" type="SpotLight3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 0.906308, -0.422618, 0, 0.422618, 0.906308, 0, 1.7, -0.35)
visible = false
spot_range = 30.0
spot_angle = 25.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0)
shadow_enabled = true
[node name="EntrySpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
[node name="RepoBot" parent="." instance=ExtResource("7_repo_bot")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4)
look_target_path = NodePath("../Player")
[node name="Label3D" type="Label3D" parent="RepoBot"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 2.0, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "REPO BOT"
[node name="ReturnTeleporter" parent="." instance=ExtResource("4_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.5)
target_scene_path = "res://scenes/Levels/level.tscn"
target_spawn_name = &"RepoBotReturnSpawn"
[node name="Label3D" type="Label3D" parent="ReturnTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "PLAYGROUND"
[node name="PauseMenu" parent="." instance=ExtResource("6_pause_menu")]
[node name="PhoneUI" type="CanvasLayer" parent="."]
layer = 5
visible = false
[node name="Control" type="Control" parent="PhoneUI"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -180.0
offset_top = -320.0
offset_right = 180.0
offset_bottom = 320.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.08, 0.08, 0.1, 1)
[node name="QuestTitle" type="Label" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 18.0
offset_right = 150.0
offset_bottom = 41.0
text = "Quest Log"
[node name="QuestText" type="RichTextLabel" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 52.0
offset_right = 344.0
offset_bottom = 613.0
bbcode_enabled = true
text = "No active quest."
scroll_active = false
[node name="RadialCommandMenu" parent="PhoneUI/Control" instance=ExtResource("8_radial_menu")]
layout_mode = 1
+41 -1
View File
@@ -6,12 +6,14 @@ extends Node3D
@onready var _player: RigidBody3D = get_node_or_null("Player") as RigidBody3D
@onready var _sun: DirectionalLight3D = $DirectionalLight3D
@onready var _quest_text: RichTextLabel = get_node_or_null("PhoneUI/Control/PhoneFrame/QuestText") as RichTextLabel
var _time := 0.0
func _ready() -> void:
_move_player_to_spawn()
_setup_quest_ui()
func _process(delta: float) -> void:
@@ -21,11 +23,24 @@ func _process(delta: float) -> void:
func _move_player_to_spawn() -> void:
if _player == null:
return
_player.global_position = player_spawn_position
var spawn_marker := _consume_spawn_marker()
if spawn_marker != null:
_player.call("teleport_to_spawn", spawn_marker.global_transform)
else:
_player.global_position = player_spawn_position
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
func _consume_spawn_marker() -> Node3D:
if TeleportState == null:
return null
var spawn_name: StringName = TeleportState.consume_spawn_name()
if spawn_name == StringName():
return null
return find_child(String(spawn_name), true, false) as Node3D
func _update_day_night(delta: float) -> void:
if _sun == null or day_length <= 0.0:
return
@@ -35,3 +50,28 @@ func _update_day_night(delta: float) -> void:
_sun.rotation_degrees.x = angle
var energy_curve: float = -sin((t * TAU) + (start_light_angle * PI / 180.0))
_sun.light_energy = clamp((energy_curve * 1.0) + 0.2, 0.0, 1.2)
func _setup_quest_ui() -> void:
if QuestManager == null:
return
if not QuestManager.is_connected("quest_state_changed", Callable(self, "_refresh_quest_ui")):
QuestManager.quest_state_changed.connect(_refresh_quest_ui)
_refresh_quest_ui()
func _refresh_quest_ui() -> void:
if _quest_text == null or QuestManager == null:
return
var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)):
_quest_text.text = "No active quest."
return
var title := String(state.get("title", "Quest"))
if bool(state.get("completed", false)):
_quest_text.text = "[b]%s[/b]\nComplete." % title
return
var step_index: int = int(state.get("current_step_index", 0))
var total_steps: int = int(state.get("total_steps", 0))
var step_text := String(state.get("current_step_text", ""))
_quest_text.text = "[b]%s[/b]\nStep %d/%d\n%s" % [title, step_index + 1, total_steps, step_text]
+86 -1
View File
@@ -1,4 +1,4 @@
[gd_scene load_steps=9 format=3 uid="uid://b7p7k1i4t0m2l"]
[gd_scene load_steps=15 format=3 uid="uid://b7p7k1i4t0m2l"]
[ext_resource type="Script" path="res://scenes/Levels/transportation_level.gd" id="1_6y4q1"]
[ext_resource type="Script" path="res://scenes/player.gd" id="2_player"]
@@ -6,6 +6,10 @@
[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="4_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="5_ground_mat"]
[ext_resource type="PackedScene" uid="uid://dp6jk0k3o4v1u" path="res://scenes/UI/pause_menu.tscn" id="6_pause_menu"]
[ext_resource type="PackedScene" path="res://scenes/Vehicles/car.tscn" id="7_car"]
[ext_resource type="Script" uid="uid://cshtdpjp4xy2f" path="res://scenes/Quests/quest_trigger_area.gd" id="8_qtrigger"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_prop_red.tres" id="9_checkpoint_mat"]
[ext_resource type="PackedScene" uid="uid://bnwpu7p8sbsfa" path="res://scenes/Interaction/RadialCommandMenu.tscn" id="10_radial_menu"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
@@ -16,6 +20,13 @@ size = Vector3(1080, 2, 1080)
material = ExtResource("5_ground_mat")
size = Vector3(1080, 2, 1080)
[sub_resource type="SphereShape3D" id="SphereShape3D_checkpoint"]
[sub_resource type="SphereMesh" id="SphereMesh_checkpoint"]
material = ExtResource("9_checkpoint_mat")
radius = 1.2
height = 2.4
[node name="TransportationLevel" type="Node3D"]
script = ExtResource("1_6y4q1")
@@ -33,6 +44,7 @@ mesh = SubResource("BoxMesh_ground")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
script = ExtResource("2_player")
camera_path = NodePath("Camera3D")
phone_path = NodePath("../PhoneUI")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
@@ -56,8 +68,81 @@ spot_angle = 25.0
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0)
shadow_enabled = true
[node name="EntrySpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
[node name="Car" parent="." instance=ExtResource("7_car")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -3)
[node name="QuestCheckpoint" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9, 0, -10)
script = ExtResource("8_qtrigger")
event_name = &"reach_checkpoint"
target_group = &"vehicle"
quest_id_filter = "first_drive"
[node name="CollisionShape3D" type="CollisionShape3D" parent="QuestCheckpoint"]
shape = SubResource("SphereShape3D_checkpoint")
[node name="Visual" type="MeshInstance3D" parent="QuestCheckpoint"]
mesh = SubResource("SphereMesh_checkpoint")
[node name="ReturnTeleporter" parent="." instance=ExtResource("4_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 0, 0)
target_scene_path = "res://scenes/Levels/level.tscn"
target_spawn_name = &"CarReturnSpawn"
[node name="Label3D" type="Label3D" parent="ReturnTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
pixel_size = 0.012
text = "PLAYGROUND"
[node name="PauseMenu" parent="." instance=ExtResource("6_pause_menu")]
[node name="PhoneUI" type="CanvasLayer" parent="."]
layer = 5
visible = false
[node name="Control" type="Control" parent="PhoneUI"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -180.0
offset_top = -320.0
offset_right = 180.0
offset_bottom = 320.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.08, 0.08, 0.1, 1)
[node name="QuestTitle" type="Label" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 18.0
offset_right = 150.0
offset_bottom = 41.0
text = "Quest Log"
[node name="QuestText" type="RichTextLabel" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 52.0
offset_right = 344.0
offset_bottom = 613.0
bbcode_enabled = true
text = "No active quest."
scroll_active = false
[node name="RadialCommandMenu" parent="PhoneUI/Control" instance=ExtResource("10_radial_menu")]
layout_mode = 1
+121
View File
@@ -0,0 +1,121 @@
extends Node3D
@export var player_spawn_position := Vector3(0.0, 0.0, 0.0)
@export var day_length := 120.0
@export var start_light_angle := -90.0
@onready var _player: RigidBody3D = get_node_or_null("Player") as RigidBody3D
@onready var _sun: DirectionalLight3D = $DirectionalLight3D
@onready var _quest_text: RichTextLabel = get_node_or_null("PhoneUI/Control/PhoneFrame/QuestText") as RichTextLabel
const FIRST_QUEST_ID := "first_drive"
const QUEST_PROMPT_META_PREFIX := "quest_intro_prompt_shown_"
const FIRST_QUEST := {
"id": FIRST_QUEST_ID,
"title": "RepoBot's First Task",
"description": "Get familiar with movement and vehicles.",
"steps": [
{
"id": "enter_car_feature",
"text": "Walk through the car teleporter.",
"complete_event": "entered_car_feature",
},
{
"id": "reach_checkpoint",
"text": "Drive the car into the checkpoint marker.",
"complete_event": "reach_checkpoint",
},
],
}
var _time := 0.0
func _ready() -> void:
_move_player_to_spawn()
_setup_quests()
_show_quest_intro_dialog()
func _process(delta: float) -> void:
_update_day_night(delta)
func _move_player_to_spawn() -> void:
if _player == null:
return
var spawn_marker := _consume_spawn_marker()
if spawn_marker != null:
_player.call("teleport_to_spawn", spawn_marker.global_transform)
else:
_player.global_position = player_spawn_position
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
func _consume_spawn_marker() -> Node3D:
if TeleportState == null:
return null
var spawn_name: StringName = TeleportState.consume_spawn_name()
if spawn_name == StringName():
return null
return find_child(String(spawn_name), true, false) as Node3D
func _update_day_night(delta: float) -> void:
if _sun == null or day_length <= 0.0:
return
_time = fmod(_time + delta, day_length)
var t: float = _time / day_length
var angle: float = lerp(start_light_angle, start_light_angle + 360.0, t)
_sun.rotation_degrees.x = angle
var energy_curve: float = -sin((t * TAU) + (start_light_angle * PI / 180.0))
_sun.light_energy = clamp((energy_curve * 1.0) + 0.2, 0.0, 1.2)
func _setup_quests() -> void:
if QuestManager == null:
return
if not QuestManager.has_quest(FIRST_QUEST_ID):
QuestManager.register_quest(FIRST_QUEST)
if not QuestManager.is_active_quest(FIRST_QUEST_ID) and not QuestManager.is_quest_completed(FIRST_QUEST_ID):
QuestManager.start_quest(FIRST_QUEST_ID)
if not QuestManager.is_connected("quest_state_changed", Callable(self, "_refresh_quest_ui")):
QuestManager.quest_state_changed.connect(_refresh_quest_ui)
_refresh_quest_ui()
func _refresh_quest_ui() -> void:
if _quest_text == null or QuestManager == null:
return
var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)):
_quest_text.text = "No active quest."
return
var title := String(state.get("title", "Quest"))
if bool(state.get("completed", false)):
_quest_text.text = "[b]%s[/b]\nComplete." % title
return
var step_index: int = int(state.get("current_step_index", 0))
var total_steps: int = int(state.get("total_steps", 0))
var step_text := String(state.get("current_step_text", ""))
_quest_text.text = "[b]%s[/b]\nStep %d/%d\n%s" % [title, step_index + 1, total_steps, step_text]
func _show_quest_intro_dialog() -> void:
if QuestManager == null:
return
var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)) or bool(state.get("completed", false)):
return
var quest_id := String(state.get("quest_id", "")).strip_edges()
var step_id := String(state.get("current_step_id", "")).strip_edges()
var step_text := String(state.get("current_step_text", ""))
if quest_id.is_empty() or step_id.is_empty() or step_text.is_empty():
return
var prompt_key := "%s%s_%s" % [QUEST_PROMPT_META_PREFIX, quest_id, step_id]
if QuestManager.has_meta(prompt_key) and bool(QuestManager.get_meta(prompt_key)):
return
if DialogSystem and DialogSystem.has_method("show_text"):
DialogSystem.show_text("RepoBot: New task assigned.\n\n%s" % step_text)
QuestManager.set_meta(prompt_key, true)
@@ -0,0 +1 @@
uid://br28dq2hwo7sc
+177
View File
@@ -0,0 +1,177 @@
[gd_scene load_steps=14 format=3]
[ext_resource type="Script" path="res://scenes/Levels/trigger_zones_level.gd" id="1_level"]
[ext_resource type="Script" path="res://scenes/player.gd" id="2_player"]
[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="3_model"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="4_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="5_ground_mat"]
[ext_resource type="PackedScene" uid="uid://dp6jk0k3o4v1u" path="res://scenes/UI/pause_menu.tscn" id="6_pause_menu"]
[ext_resource type="Script" uid="uid://bk53njt7i3kmv" path="res://scenes/Interaction/dialog_trigger_area.gd" id="7_dialog"]
[ext_resource type="PackedScene" uid="uid://bnwpu7p8sbsfa" path="res://scenes/Interaction/RadialCommandMenu.tscn" id="8_radial_menu"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
[sub_resource type="BoxShape3D" id="BoxShape3D_ground"]
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxMesh" id="BoxMesh_ground"]
material = ExtResource("5_ground_mat")
size = Vector3(1080, 2, 1080)
[sub_resource type="SphereShape3D" id="SphereShape3D_dialog_zone"]
radius = 2.5
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dialog_zone"]
transparency = 1
cull_mode = 2
shading_mode = 0
albedo_color = Color(0.2, 0.8, 0.35, 0.18)
[sub_resource type="SphereMesh" id="SphereMesh_dialog_zone"]
material = SubResource("StandardMaterial3D_dialog_zone")
radius = 2.5
height = 5.0
[node name="TriggerZonesLevel" type="Node3D"]
script = ExtResource("1_level")
[node name="Ground" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"]
shape = SubResource("BoxShape3D_ground")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
mesh = SubResource("BoxMesh_ground")
[node name="Player" type="RigidBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
script = ExtResource("2_player")
camera_path = NodePath("Camera3D")
phone_path = NodePath("../PhoneUI")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("SphereShape3D_player")
[node name="TestCharAnimated" parent="Player" instance=ExtResource("3_model")]
transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(0.9989785, -4.651856e-10, -0.045188628, 0.006969331, 0.9880354, 0.15407, 0.044647958, -0.15422754, 0.9870261, 0.22036135, 1.8988357, 0.64972365)
current = true
fov = 49.0
[node name="SpotLight3D" type="SpotLight3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 0.906308, -0.422618, 0, 0.422618, 0.906308, 0, 1.7, -0.35)
visible = false
spot_range = 30.0
spot_angle = 25.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0)
shadow_enabled = true
[node name="EntrySpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
[node name="ManualDialogZone" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, -3)
script = ExtResource("7_dialog")
prompt_text = "Press E to inspect area"
dialog_text = "Manual dialog trigger zone"
[node name="CollisionShape3D" type="CollisionShape3D" parent="ManualDialogZone"]
shape = SubResource("SphereShape3D_dialog_zone")
[node name="Visual" type="MeshInstance3D" parent="ManualDialogZone"]
mesh = SubResource("SphereMesh_dialog_zone")
[node name="Label3D" type="Label3D" parent="ManualDialogZone"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 3.0, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "PRESS E"
[node name="AutoDialogZone" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, -3)
script = ExtResource("7_dialog")
dialog_text = "Auto dialog trigger zone"
auto_popup = true
auto_popup_close_on_exit = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="AutoDialogZone"]
shape = SubResource("SphereShape3D_dialog_zone")
[node name="Visual" type="MeshInstance3D" parent="AutoDialogZone"]
mesh = SubResource("SphereMesh_dialog_zone")
[node name="Label3D" type="Label3D" parent="AutoDialogZone"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 3.0, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "AUTO"
[node name="ReturnTeleporter" parent="." instance=ExtResource("4_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5.5)
target_scene_path = "res://scenes/Levels/level.tscn"
target_spawn_name = &"TriggerZonesReturnSpawn"
[node name="Label3D" type="Label3D" parent="ReturnTeleporter"]
transform = Transform3D(1, 0, 0, 0, 0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 5.4, 0)
billboard = 1
no_depth_test = true
pixel_size = 0.012
text = "PLAYGROUND"
[node name="PauseMenu" parent="." instance=ExtResource("6_pause_menu")]
[node name="PhoneUI" type="CanvasLayer" parent="."]
layer = 5
visible = false
[node name="Control" type="Control" parent="PhoneUI"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -180.0
offset_top = -320.0
offset_right = 180.0
offset_bottom = 320.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.08, 0.08, 0.1, 1)
[node name="QuestTitle" type="Label" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 18.0
offset_right = 150.0
offset_bottom = 41.0
text = "Quest Log"
[node name="QuestText" type="RichTextLabel" parent="PhoneUI/Control/PhoneFrame"]
layout_mode = 0
offset_left = 18.0
offset_top = 52.0
offset_right = 344.0
offset_bottom = 613.0
bbcode_enabled = true
text = "No active quest."
scroll_active = false
[node name="RadialCommandMenu" parent="PhoneUI/Control" instance=ExtResource("8_radial_menu")]
layout_mode = 1
+112 -112
View File
@@ -1,112 +1,112 @@
extends Node2D
@onready var _tab_bar: TabBar = $MarginContainer/VBoxContainer/TabBar
@onready var _tab_container: TabContainer = $MarginContainer/VBoxContainer/TabContainer
@onready var _music_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeSlider
@onready var _music_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeValue
@onready var _music_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicMuteCheckBox
@onready var _sfx_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeSlider
@onready var _sfx_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeValue
@onready var _sfx_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxMuteCheckBox
@onready var _menu_music: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuMusic")
@onready var _menu_sfx: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuSfx")
func _ready() -> void:
_tab_bar.tab_changed.connect(_on_tab_bar_tab_changed)
_tab_container.tab_changed.connect(_on_tab_container_tab_changed)
_tab_container.current_tab = _tab_bar.current_tab
_music_volume_slider.value_changed.connect(_on_music_volume_changed)
_music_mute_checkbox.toggled.connect(_on_music_mute_toggled)
_sfx_volume_slider.value_changed.connect(_on_sfx_volume_changed)
_sfx_mute_checkbox.toggled.connect(_on_sfx_mute_toggled)
_sync_audio_controls()
_register_focus_sounds()
func _input(event):
if event.is_action_pressed("ui_cancel"):
get_tree().change_scene_to_file("uid://b4k81taauef4q")
func _on_tab_bar_tab_changed(tab_index: int) -> void:
if _tab_container.current_tab != tab_index:
_tab_container.current_tab = tab_index
func _on_tab_container_tab_changed(tab_index: int) -> void:
if _tab_bar.current_tab != tab_index:
_tab_bar.current_tab = tab_index
func _sync_audio_controls() -> void:
var value: float = 0.7
var muted: bool = false
if _menu_music:
if _menu_music.has_method("get_user_volume"):
value = _menu_music.get_user_volume()
if _menu_music.has_method("is_user_muted"):
muted = _menu_music.is_user_muted()
_apply_audio_control_state(_music_volume_slider, _music_mute_checkbox, _music_volume_value, value, muted)
var sfx_value: float = 0.7
var sfx_muted: bool = false
if _menu_sfx:
if _menu_sfx.has_method("get_user_volume"):
sfx_value = _menu_sfx.get_user_volume()
if _menu_sfx.has_method("is_user_muted"):
sfx_muted = _menu_sfx.is_user_muted()
_apply_audio_control_state(_sfx_volume_slider, _sfx_mute_checkbox, _sfx_volume_value, sfx_value, sfx_muted)
func _on_music_volume_changed(value: float) -> void:
if _menu_music and _menu_music.has_method("set_user_volume"):
_menu_music.set_user_volume(value)
_update_volume_label(_music_volume_value, value, _music_mute_checkbox.button_pressed)
func _on_music_mute_toggled(pressed: bool) -> void:
if _menu_music and _menu_music.has_method("set_user_muted"):
_menu_music.set_user_muted(pressed)
_music_volume_slider.editable = not pressed
_update_volume_label(_music_volume_value, _music_volume_slider.value, pressed)
func _on_sfx_volume_changed(value: float) -> void:
if _menu_sfx and _menu_sfx.has_method("set_user_volume"):
_menu_sfx.set_user_volume(value)
_update_volume_label(_sfx_volume_value, value, _sfx_mute_checkbox.button_pressed)
func _on_sfx_mute_toggled(pressed: bool) -> void:
if _menu_sfx and _menu_sfx.has_method("set_user_muted"):
_menu_sfx.set_user_muted(pressed)
_sfx_volume_slider.editable = not pressed
_update_volume_label(_sfx_volume_value, _sfx_volume_slider.value, pressed)
func _apply_audio_control_state(slider: HSlider, checkbox: CheckBox, value_label: Label, value: float, muted: bool) -> void:
slider.set_block_signals(true)
slider.value = value
slider.set_block_signals(false)
slider.editable = not muted
checkbox.set_block_signals(true)
checkbox.button_pressed = muted
checkbox.set_block_signals(false)
_update_volume_label(value_label, value, muted)
func _update_volume_label(value_label: Label, value: float, muted: bool) -> void:
if muted:
value_label.text = "Muted"
else:
var percent: int = int(round(value * 100.0))
value_label.text = str(percent) + "%"
func _register_focus_sounds() -> void:
_connect_focus_recursive(self)
func _connect_focus_recursive(node: Node) -> void:
if node is Control:
var control: Control = node
if control.focus_mode != Control.FOCUS_NONE:
if not control.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")):
control.focus_entered.connect(_on_menu_item_focus)
if control.has_signal("mouse_entered") and (control is BaseButton or control.focus_mode != Control.FOCUS_NONE):
if not control.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")):
control.mouse_entered.connect(_on_menu_item_focus)
for child in node.get_children():
_connect_focus_recursive(child)
func _on_menu_item_focus() -> void:
if MenuSfx:
MenuSfx.play_hover()
extends Node2D
@onready var _tab_bar: TabBar = $MarginContainer/VBoxContainer/TabBar
@onready var _tab_container: TabContainer = $MarginContainer/VBoxContainer/TabContainer
@onready var _music_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeSlider
@onready var _music_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeValue
@onready var _music_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicMuteCheckBox
@onready var _sfx_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeSlider
@onready var _sfx_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeValue
@onready var _sfx_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxMuteCheckBox
@onready var _menu_music: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuMusic")
@onready var _menu_sfx: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuSfx")
func _ready() -> void:
_tab_bar.tab_changed.connect(_on_tab_bar_tab_changed)
_tab_container.tab_changed.connect(_on_tab_container_tab_changed)
_tab_container.current_tab = _tab_bar.current_tab
_music_volume_slider.value_changed.connect(_on_music_volume_changed)
_music_mute_checkbox.toggled.connect(_on_music_mute_toggled)
_sfx_volume_slider.value_changed.connect(_on_sfx_volume_changed)
_sfx_mute_checkbox.toggled.connect(_on_sfx_mute_toggled)
_sync_audio_controls()
_register_focus_sounds()
func _input(event):
if event.is_action_pressed("ui_cancel"):
get_tree().change_scene_to_file("uid://b4k81taauef4q")
func _on_tab_bar_tab_changed(tab_index: int) -> void:
if _tab_container.current_tab != tab_index:
_tab_container.current_tab = tab_index
func _on_tab_container_tab_changed(tab_index: int) -> void:
if _tab_bar.current_tab != tab_index:
_tab_bar.current_tab = tab_index
func _sync_audio_controls() -> void:
var value: float = 0.7
var muted: bool = false
if _menu_music:
if _menu_music.has_method("get_user_volume"):
value = _menu_music.get_user_volume()
if _menu_music.has_method("is_user_muted"):
muted = _menu_music.is_user_muted()
_apply_audio_control_state(_music_volume_slider, _music_mute_checkbox, _music_volume_value, value, muted)
var sfx_value: float = 0.7
var sfx_muted: bool = false
if _menu_sfx:
if _menu_sfx.has_method("get_user_volume"):
sfx_value = _menu_sfx.get_user_volume()
if _menu_sfx.has_method("is_user_muted"):
sfx_muted = _menu_sfx.is_user_muted()
_apply_audio_control_state(_sfx_volume_slider, _sfx_mute_checkbox, _sfx_volume_value, sfx_value, sfx_muted)
func _on_music_volume_changed(value: float) -> void:
if _menu_music and _menu_music.has_method("set_user_volume"):
_menu_music.set_user_volume(value)
_update_volume_label(_music_volume_value, value, _music_mute_checkbox.button_pressed)
func _on_music_mute_toggled(pressed: bool) -> void:
if _menu_music and _menu_music.has_method("set_user_muted"):
_menu_music.set_user_muted(pressed)
_music_volume_slider.editable = not pressed
_update_volume_label(_music_volume_value, _music_volume_slider.value, pressed)
func _on_sfx_volume_changed(value: float) -> void:
if _menu_sfx and _menu_sfx.has_method("set_user_volume"):
_menu_sfx.set_user_volume(value)
_update_volume_label(_sfx_volume_value, value, _sfx_mute_checkbox.button_pressed)
func _on_sfx_mute_toggled(pressed: bool) -> void:
if _menu_sfx and _menu_sfx.has_method("set_user_muted"):
_menu_sfx.set_user_muted(pressed)
_sfx_volume_slider.editable = not pressed
_update_volume_label(_sfx_volume_value, _sfx_volume_slider.value, pressed)
func _apply_audio_control_state(slider: HSlider, checkbox: CheckBox, value_label: Label, value: float, muted: bool) -> void:
slider.set_block_signals(true)
slider.value = value
slider.set_block_signals(false)
slider.editable = not muted
checkbox.set_block_signals(true)
checkbox.button_pressed = muted
checkbox.set_block_signals(false)
_update_volume_label(value_label, value, muted)
func _update_volume_label(value_label: Label, value: float, muted: bool) -> void:
if muted:
value_label.text = "Muted"
else:
var percent: int = int(round(value * 100.0))
value_label.text = str(percent) + "%"
func _register_focus_sounds() -> void:
_connect_focus_recursive(self)
func _connect_focus_recursive(node: Node) -> void:
if node is Control:
var control: Control = node
if control.focus_mode != Control.FOCUS_NONE:
if not control.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")):
control.focus_entered.connect(_on_menu_item_focus)
if control.has_signal("mouse_entered") and (control is BaseButton or control.focus_mode != Control.FOCUS_NONE):
if not control.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")):
control.mouse_entered.connect(_on_menu_item_focus)
for child in node.get_children():
_connect_focus_recursive(child)
func _on_menu_item_focus() -> void:
if MenuSfx:
MenuSfx.play_hover()
+157 -157
View File
@@ -1,157 +1,157 @@
[gd_scene load_steps=5 format=3 uid="uid://d3tqrm4ry4l88"]
[ext_resource type="Script" uid="uid://h1slqbemgwvr" path="res://scenes/UI/Settings.gd" id="1_1dggd"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="1_i47rn"]
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="2_46duy"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="3_46duy"]
[node name="settings_screen" type="Node2D"]
script = ExtResource("1_1dggd")
metadata/_edit_vertical_guides_ = [1152.0]
[node name="TextureRect" type="TextureRect" parent="."]
offset_left = -192.0
offset_top = -188.0
offset_right = 1344.0
offset_bottom = 836.0
texture = ExtResource("1_i47rn")
[node name="MarginContainer" type="MarginContainer" parent="."]
offset_right = 1152.0
offset_bottom = 648.0
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_fonts/font = ExtResource("2_46duy")
theme_override_font_sizes/font_size = 42
text = "Settings"
horizontal_alignment = 1
[node name="TabBar" type="TabBar" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme = ExtResource("3_46duy")
current_tab = 0
tab_count = 4
tab_0/title = "Gameplay"
tab_1/title = "Video"
tab_2/title = "Audio"
tab_3/title = "Dev"
[node name="TabContainer" type="TabContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
tabs_visible = false
[node name="Gameplay" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Video" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Audio" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="AudioVBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 10
offset_left = 120.0
offset_top = 240.0
offset_right = -120.0
offset_bottom = -40.0
[node name="MusicVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
text = "Music Volume"
horizontal_alignment = 1
size_flags_horizontal = 4
[node name="MusicVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
alignment = 1
custom_minimum_size = Vector2(0, 40)
[node name="MusicVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
min_value = 0.0
max_value = 1.0
step = 0.01
size_flags_horizontal = 3
custom_minimum_size = Vector2(320, 0)
[node name="MusicVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
text = "70%"
size_flags_horizontal = 1
horizontal_alignment = 1
custom_minimum_size = Vector2(60, 0)
[node name="MusicMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
text = "Mute"
size_flags_horizontal = 1
[node name="SfxVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
text = "Menu SFX Volume"
horizontal_alignment = 1
size_flags_horizontal = 4
[node name="SfxVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
alignment = 1
custom_minimum_size = Vector2(0, 40)
[node name="SfxVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
min_value = 0.0
max_value = 1.0
step = 0.01
size_flags_horizontal = 3
custom_minimum_size = Vector2(320, 0)
[node name="SfxVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
text = "70%"
size_flags_horizontal = 1
horizontal_alignment = 1
custom_minimum_size = Vector2(60, 0)
[node name="SfxMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
text = "Mute"
size_flags_horizontal = 1
[node name="Dev" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[gd_scene load_steps=5 format=3 uid="uid://d3tqrm4ry4l88"]
[ext_resource type="Script" uid="uid://h1slqbemgwvr" path="res://scenes/UI/Settings.gd" id="1_1dggd"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="1_i47rn"]
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="2_46duy"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="3_46duy"]
[node name="settings_screen" type="Node2D"]
script = ExtResource("1_1dggd")
metadata/_edit_vertical_guides_ = [1152.0]
[node name="TextureRect" type="TextureRect" parent="."]
offset_left = -192.0
offset_top = -188.0
offset_right = 1344.0
offset_bottom = 836.0
texture = ExtResource("1_i47rn")
[node name="MarginContainer" type="MarginContainer" parent="."]
offset_right = 1152.0
offset_bottom = 648.0
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_fonts/font = ExtResource("2_46duy")
theme_override_font_sizes/font_size = 42
text = "Settings"
horizontal_alignment = 1
[node name="TabBar" type="TabBar" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
theme = ExtResource("3_46duy")
current_tab = 0
tab_count = 4
tab_0/title = "Gameplay"
tab_1/title = "Video"
tab_2/title = "Audio"
tab_3/title = "Dev"
[node name="TabContainer" type="TabContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
tabs_visible = false
[node name="Gameplay" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Video" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Audio" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="AudioVBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 10
offset_left = 120.0
offset_top = 240.0
offset_right = -120.0
offset_bottom = -40.0
[node name="MusicVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
text = "Music Volume"
horizontal_alignment = 1
size_flags_horizontal = 4
[node name="MusicVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
alignment = 1
custom_minimum_size = Vector2(0, 40)
[node name="MusicVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
min_value = 0.0
max_value = 1.0
step = 0.01
size_flags_horizontal = 3
custom_minimum_size = Vector2(320, 0)
[node name="MusicVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
text = "70%"
size_flags_horizontal = 1
horizontal_alignment = 1
custom_minimum_size = Vector2(60, 0)
[node name="MusicMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
layout_mode = 2
text = "Mute"
size_flags_horizontal = 1
[node name="SfxVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
text = "Menu SFX Volume"
horizontal_alignment = 1
size_flags_horizontal = 4
[node name="SfxVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
alignment = 1
custom_minimum_size = Vector2(0, 40)
[node name="SfxVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
min_value = 0.0
max_value = 1.0
step = 0.01
size_flags_horizontal = 3
custom_minimum_size = Vector2(320, 0)
[node name="SfxVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
text = "70%"
size_flags_horizontal = 1
horizontal_alignment = 1
custom_minimum_size = Vector2(60, 0)
[node name="SfxMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
layout_mode = 2
text = "Mute"
size_flags_horizontal = 1
[node name="Dev" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
+15 -15
View File
@@ -1,15 +1,15 @@
extends Node
var is_logged_in: bool = false
var access_token: String = ""
var username: String = ""
func set_session(new_username: String, token: String) -> void:
is_logged_in = true
username = new_username
access_token = token
func clear_session() -> void:
is_logged_in = false
username = ""
access_token = ""
extends Node
var is_logged_in: bool = false
var access_token: String = ""
var username: String = ""
func set_session(new_username: String, token: String) -> void:
is_logged_in = true
username = new_username
access_token = token
func clear_session() -> void:
is_logged_in = false
username = ""
access_token = ""
+122 -122
View File
@@ -1,99 +1,99 @@
extends Control
const AUTH_LOGOUT_URL := "https://pauth.ranaze.com/api/Auth/logout"
@onready var _status_label: Label = %StatusLabel
@onready var _character_list: ItemList = %CharacterList
@onready var _name_input: LineEdit = %NameInput
@onready var _logout_request: HTTPRequest = %LogoutRequest
var _characters: Array = []
func _ready() -> void:
if not AuthState.is_logged_in:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
return
_load_characters()
func _log_failure(action: String, response: Dictionary) -> void:
push_warning("%s failed: status=%s error=%s body=%s" % [
action,
response.get("status", "n/a"),
response.get("error", ""),
response.get("body", "")
])
func _load_characters() -> void:
_status_label.text = "Loading characters..."
var response := await CharacterService.list_characters()
if not response.get("ok", false):
_log_failure("List characters", response)
_status_label.text = "Failed to load characters."
return
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
if typeof(parsed) != TYPE_ARRAY:
_status_label.text = "Unexpected character response."
return
_characters = parsed
_character_list.clear()
for character in _characters:
var character_name := String(character.get("name", "Unnamed"))
_character_list.add_item(character_name)
if _characters.is_empty():
_status_label.text = "No characters yet. Add one below."
else:
_status_label.text = ""
func _on_add_button_pressed() -> void:
var character_name := _name_input.text.strip_edges()
if character_name.is_empty():
_status_label.text = "Enter a character name first."
return
_status_label.text = "Creating character..."
var response := await CharacterService.create_character(character_name)
if not response.get("ok", false):
_log_failure("Create character", response)
_status_label.text = "Failed to create character."
return
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
if typeof(parsed) == TYPE_DICTIONARY:
_characters.append(parsed)
_character_list.add_item(String(parsed.get("name", character_name)))
_name_input.text = ""
_status_label.text = "Character added."
else:
_status_label.text = "Character created, but response was unexpected."
extends Control
const AUTH_LOGOUT_URL := "https://pauth.ranaze.com/api/Auth/logout"
@onready var _status_label: Label = %StatusLabel
@onready var _character_list: ItemList = %CharacterList
@onready var _name_input: LineEdit = %NameInput
@onready var _logout_request: HTTPRequest = %LogoutRequest
var _characters: Array = []
func _ready() -> void:
if not AuthState.is_logged_in:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
return
_load_characters()
func _log_failure(action: String, response: Dictionary) -> void:
push_warning("%s failed: status=%s error=%s body=%s" % [
action,
response.get("status", "n/a"),
response.get("error", ""),
response.get("body", "")
])
func _load_characters() -> void:
_status_label.text = "Loading characters..."
var response := await CharacterService.list_characters()
if not response.get("ok", false):
_log_failure("List characters", response)
_status_label.text = "Failed to load characters."
return
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
if typeof(parsed) != TYPE_ARRAY:
_status_label.text = "Unexpected character response."
return
_characters = parsed
_character_list.clear()
for character in _characters:
var character_name := String(character.get("name", "Unnamed"))
_character_list.add_item(character_name)
if _characters.is_empty():
_status_label.text = "No characters yet. Add one below."
else:
_status_label.text = ""
func _on_add_button_pressed() -> void:
var character_name := _name_input.text.strip_edges()
if character_name.is_empty():
_status_label.text = "Enter a character name first."
return
_status_label.text = "Creating character..."
var response := await CharacterService.create_character(character_name)
if not response.get("ok", false):
_log_failure("Create character", response)
_status_label.text = "Failed to create character."
return
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
if typeof(parsed) == TYPE_DICTIONARY:
_characters.append(parsed)
_character_list.add_item(String(parsed.get("name", character_name)))
_name_input.text = ""
_status_label.text = "Character added."
else:
_status_label.text = "Character created, but response was unexpected."
func _on_delete_button_pressed() -> void:
var selected := _character_list.get_selected_items()
if selected.is_empty():
_status_label.text = "Select a character to delete."
return
var index := selected[0]
if index < 0 or index >= _characters.size():
_status_label.text = "Invalid selection."
return
var character: Dictionary = _characters[index]
var character_id := String(character.get("id", character.get("Id", "")))
if character_id.is_empty():
_status_label.text = "Missing character id."
return
_status_label.text = "Deleting character..."
var response := await CharacterService.delete_character(character_id)
if not response.get("ok", false):
_log_failure("Delete character", response)
_status_label.text = "Failed to delete character."
return
_characters.remove_at(index)
var index := selected[0]
if index < 0 or index >= _characters.size():
_status_label.text = "Invalid selection."
return
var character: Dictionary = _characters[index]
var character_id := String(character.get("id", character.get("Id", "")))
if character_id.is_empty():
_status_label.text = "Missing character id."
return
_status_label.text = "Deleting character..."
var response := await CharacterService.delete_character(character_id)
if not response.get("ok", false):
_log_failure("Delete character", response)
_status_label.text = "Failed to delete character."
return
_characters.remove_at(index)
_character_list.remove_item(index)
_status_label.text = "Character deleted."
@@ -111,34 +111,34 @@ func _on_select_button_pressed() -> void:
var character: Dictionary = _characters[index]
SelectedCharacter.set_character(character)
get_tree().change_scene_to_file("res://scenes/Levels/location_level.tscn")
func _on_refresh_button_pressed() -> void:
_load_characters()
func _on_back_button_pressed() -> void:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _on_logout_button_pressed() -> void:
_request_logout()
func _request_logout() -> void:
if AuthState.access_token.is_empty():
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
return
var headers := PackedStringArray([
"Authorization: Bearer %s" % AuthState.access_token,
])
var err := _logout_request.request(AUTH_LOGOUT_URL, headers, HTTPClient.METHOD_POST)
if err != OK:
push_warning("Failed to send logout request: %s" % err)
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _on_logout_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var body_text := body.get_string_from_utf8()
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
push_warning("Logout failed (%s/%s): %s" % [result, response_code, body_text])
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _on_refresh_button_pressed() -> void:
_load_characters()
func _on_back_button_pressed() -> void:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _on_logout_button_pressed() -> void:
_request_logout()
func _request_logout() -> void:
if AuthState.access_token.is_empty():
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
return
var headers := PackedStringArray([
"Authorization: Bearer %s" % AuthState.access_token,
])
var err := _logout_request.request(AUTH_LOGOUT_URL, headers, HTTPClient.METHOD_POST)
if err != OK:
push_warning("Failed to send logout request: %s" % err)
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _on_logout_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var body_text := body.get_string_from_utf8()
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
push_warning("Logout failed (%s/%s): %s" % [result, response_code, body_text])
AuthState.clear_session()
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
+116 -116
View File
@@ -1,92 +1,92 @@
[gd_scene load_steps=5 format=3 uid="uid://cw3b7n7k2c4h6"]
[ext_resource type="Script" path="res://scenes/UI/character_screen.gd" id="1_0p3qk"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_5g2t1"]
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_k2j6k"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_5b3b7"]
[node name="CharacterScreen" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_0p3qk")
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_5g2t1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 80
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 80
theme_override_constants/margin_bottom = 40
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_constants/separation = 18
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("3_k2j6k")
text = "YOUR CHARACTERS"
horizontal_alignment = 1
[node name="StatusLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
horizontal_alignment = 1
[node name="CharacterList" type="ItemList" parent="MarginContainer/ContentCenter/ContentVBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
custom_minimum_size = Vector2(520, 240)
[node name="AddHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 10
[node name="NameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "character name"
[node name="AddButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
layout_mode = 2
size_flags_horizontal = 0
theme = ExtResource("4_5b3b7")
text = "ADD"
text_alignment = 1
[gd_scene load_steps=5 format=3 uid="uid://cw3b7n7k2c4h6"]
[ext_resource type="Script" path="res://scenes/UI/character_screen.gd" id="1_0p3qk"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_5g2t1"]
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_k2j6k"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_5b3b7"]
[node name="CharacterScreen" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_0p3qk")
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_5g2t1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 80
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 80
theme_override_constants/margin_bottom = 40
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_constants/separation = 18
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("3_k2j6k")
text = "YOUR CHARACTERS"
horizontal_alignment = 1
[node name="StatusLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
horizontal_alignment = 1
[node name="CharacterList" type="ItemList" parent="MarginContainer/ContentCenter/ContentVBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
custom_minimum_size = Vector2(520, 240)
[node name="AddHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 10
[node name="NameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "character name"
[node name="AddButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
layout_mode = 2
size_flags_horizontal = 0
theme = ExtResource("4_5b3b7")
text = "ADD"
text_alignment = 1
[node name="ActionHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
@@ -104,36 +104,36 @@ layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "REFRESH"
text_alignment = 1
[node name="DeleteButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "DELETE"
text_alignment = 1
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "BACK"
text_alignment = 1
[node name="LogoutButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "LOG OUT"
text_alignment = 1
[node name="LogoutRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
text_alignment = 1
[node name="DeleteButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "DELETE"
text_alignment = 1
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "BACK"
text_alignment = 1
[node name="LogoutButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "LOG OUT"
text_alignment = 1
[node name="LogoutRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/AddHBox/AddButton" to="." method="_on_add_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/SelectButton" to="." method="_on_select_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/RefreshButton" to="." method="_on_refresh_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/DeleteButton" to="." method="_on_delete_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/BackButton" to="." method="_on_back_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/LogoutButton" to="." method="_on_logout_button_pressed"]
[connection signal="request_completed" from="LogoutRequest" to="." method="_on_logout_request_completed"]
[connection signal="request_completed" from="LogoutRequest" to="." method="_on_logout_request_completed"]
+55 -55
View File
@@ -1,16 +1,16 @@
extends Node
const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters"
func list_characters() -> Dictionary:
return await _request(HTTPClient.METHOD_GET, CHARACTER_API_URL)
func create_character(character_name: String) -> Dictionary:
var payload := JSON.stringify({
"name": character_name
})
return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload)
extends Node
const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters"
func list_characters() -> Dictionary:
return await _request(HTTPClient.METHOD_GET, CHARACTER_API_URL)
func create_character(character_name: String) -> Dictionary:
var payload := JSON.stringify({
"name": character_name
})
return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload)
func delete_character(character_id: String) -> Dictionary:
var url := "%s/%s" % [CHARACTER_API_URL, character_id]
return await _request(HTTPClient.METHOD_DELETE, url)
@@ -28,45 +28,45 @@ func update_character_coord(character_id: String, coord: Vector2i) -> Dictionary
func heartbeat_character(character_id: String) -> Dictionary:
var url := "%s/%s/heartbeat" % [CHARACTER_API_URL, character_id]
return await _request(HTTPClient.METHOD_POST, url, "")
func _request(method: int, url: String, body: String = "") -> Dictionary:
var request := HTTPRequest.new()
add_child(request)
var headers := PackedStringArray()
if not AuthState.access_token.is_empty():
headers.append("Authorization: Bearer %s" % AuthState.access_token)
if method == HTTPClient.METHOD_POST or method == HTTPClient.METHOD_PUT:
headers.append("Content-Type: application/json")
var err := request.request(url, headers, method, body)
if err != OK:
request.queue_free()
return {
"ok": false,
"status": 0,
"error": "Failed to send request (%s)." % err,
"body": ""
}
var result: Array = await request.request_completed
request.queue_free()
var result_code: int = result[0]
var response_code: int = result[1]
var response_body: String = result[3].get_string_from_utf8()
if result_code != HTTPRequest.RESULT_SUCCESS:
return {
"ok": false,
"status": response_code,
"error": "Network error (%s)." % result_code,
"body": response_body
}
return {
"ok": response_code >= 200 and response_code < 300,
"status": response_code,
"error": "",
"body": response_body
}
func _request(method: int, url: String, body: String = "") -> Dictionary:
var request := HTTPRequest.new()
add_child(request)
var headers := PackedStringArray()
if not AuthState.access_token.is_empty():
headers.append("Authorization: Bearer %s" % AuthState.access_token)
if method == HTTPClient.METHOD_POST or method == HTTPClient.METHOD_PUT:
headers.append("Content-Type: application/json")
var err := request.request(url, headers, method, body)
if err != OK:
request.queue_free()
return {
"ok": false,
"status": 0,
"error": "Failed to send request (%s)." % err,
"body": ""
}
var result: Array = await request.request_completed
request.queue_free()
var result_code: int = result[0]
var response_code: int = result[1]
var response_body: String = result[3].get_string_from_utf8()
if result_code != HTTPRequest.RESULT_SUCCESS:
return {
"ok": false,
"status": response_code,
"error": "Network error (%s)." % result_code,
"body": response_body
}
return {
"ok": response_code >= 200 and response_code < 300,
"status": response_code,
"error": "",
"body": response_body
}
+36 -36
View File
@@ -1,7 +1,7 @@
extends Control
const AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login"
extends Control
const AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login"
@onready var _username_input: LineEdit = %UsernameInput
@onready var _password_input: LineEdit = %PasswordInput
@onready var _login_request: HTTPRequest = %LoginRequest
@@ -18,38 +18,38 @@ func _on_log_in_button_pressed() -> void:
var password := _password_input.text
if username.is_empty() or password.is_empty():
_show_error("Username and password required.")
return
var payload := {
"username": username,
"password": password,
}
var headers := PackedStringArray(["Content-Type: application/json"])
var err := _login_request.request(AUTH_LOGIN_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
if err != OK:
_show_error("Failed to send login request.")
func _on_login_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var body_text := body.get_string_from_utf8()
if result != HTTPRequest.RESULT_SUCCESS:
_show_error("Network error. Please try again.")
return
if response_code >= 200 and response_code < 300:
var response: Variant = JSON.parse_string(body_text)
if typeof(response) == TYPE_DICTIONARY:
#print("Login success for %s" % response.get("username", "unknown"))
#print("Access Token: %s" % response.get("accessToken", ""))
var token := String(response.get("accessToken", ""))
var username := String(response.get("username", ""))
AuthState.set_session(username, token)
get_tree().change_scene_to_file("res://scenes/UI/character_screen.tscn")
else:
_show_error("Login failed. Check your credentials.")
func _on_back_button_pressed() -> void:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
return
var payload := {
"username": username,
"password": password,
}
var headers := PackedStringArray(["Content-Type: application/json"])
var err := _login_request.request(AUTH_LOGIN_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
if err != OK:
_show_error("Failed to send login request.")
func _on_login_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var body_text := body.get_string_from_utf8()
if result != HTTPRequest.RESULT_SUCCESS:
_show_error("Network error. Please try again.")
return
if response_code >= 200 and response_code < 300:
var response: Variant = JSON.parse_string(body_text)
if typeof(response) == TYPE_DICTIONARY:
#print("Login success for %s" % response.get("username", "unknown"))
#print("Access Token: %s" % response.get("accessToken", ""))
var token := String(response.get("accessToken", ""))
var username := String(response.get("username", ""))
AuthState.set_session(username, token)
get_tree().change_scene_to_file("res://scenes/UI/character_screen.tscn")
else:
_show_error("Login failed. Check your credentials.")
func _on_back_button_pressed() -> void:
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
func _show_error(message: String) -> void:
_error_label.text = message
+117 -117
View File
@@ -1,117 +1,117 @@
[gd_scene load_steps=5 format=3 uid="uid://fmp1tah03kew"]
[ext_resource type="Script" path="res://scenes/UI/login_screen.gd" id="1_jqkpi"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_2n6di"]
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_c4k70"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_gx673"]
[node name="LoginScreen" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_jqkpi")
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_2n6di")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 80
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 80
theme_override_constants/margin_bottom = 40
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_constants/separation = 24
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("3_c4k70")
text = "ACCOUNT LOGIN"
horizontal_alignment = 1
[node name="FormVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 8
custom_minimum_size = Vector2(480, 0)
[node name="UsernameLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
layout_mode = 2
text = "Username"
[node name="UsernameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "enter username"
caret_blink = true
[node name="PasswordLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
layout_mode = 2
text = "Password"
[node name="PasswordInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "enter password"
secret = true
[node name="ButtonVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
[node name="ErrorLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
unique_name_in_owner = true
layout_mode = 2
theme = ExtResource("3_c4k70")
horizontal_alignment = 1
theme_override_font_sizes/font_size = 26
theme_override_colors/font_color = Color(1, 0.2, 0.2, 1)
[node name="LogInButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_gx673")
text = "LOG IN"
text_alignment = 1
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_gx673")
text = "BACK"
text_alignment = 1
[node name="LoginRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/LogInButton" to="." method="_on_log_in_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/BackButton" to="." method="_on_back_button_pressed"]
[connection signal="request_completed" from="LoginRequest" to="." method="_on_login_request_completed"]
[gd_scene load_steps=5 format=3 uid="uid://fmp1tah03kew"]
[ext_resource type="Script" path="res://scenes/UI/login_screen.gd" id="1_jqkpi"]
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_2n6di"]
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_c4k70"]
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_gx673"]
[node name="LoginScreen" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_jqkpi")
[node name="TextureRect" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("2_2n6di")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 80
theme_override_constants/margin_top = 40
theme_override_constants/margin_right = 80
theme_override_constants/margin_bottom = 40
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_constants/separation = 24
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("3_c4k70")
text = "ACCOUNT LOGIN"
horizontal_alignment = 1
[node name="FormVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 8
custom_minimum_size = Vector2(480, 0)
[node name="UsernameLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
layout_mode = 2
text = "Username"
[node name="UsernameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "enter username"
caret_blink = true
[node name="PasswordLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
layout_mode = 2
text = "Password"
[node name="PasswordInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "enter password"
secret = true
[node name="ButtonVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_constants/separation = 12
[node name="ErrorLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
unique_name_in_owner = true
layout_mode = 2
theme = ExtResource("3_c4k70")
horizontal_alignment = 1
theme_override_font_sizes/font_size = 26
theme_override_colors/font_color = Color(1, 0.2, 0.2, 1)
[node name="LogInButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_gx673")
text = "LOG IN"
text_alignment = 1
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_gx673")
text = "BACK"
text_alignment = 1
[node name="LoginRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/LogInButton" to="." method="_on_log_in_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/BackButton" to="." method="_on_back_button_pressed"]
[connection signal="request_completed" from="LoginRequest" to="." method="_on_login_request_completed"]
+107 -107
View File
@@ -1,107 +1,107 @@
extends AudioStreamPlayer
const MENU_SCENES: Dictionary = {
"res://scenes/UI/start_screen.tscn": true,
"res://scenes/UI/Settings.tscn": true,
"res://scenes/UI/login_screen.tscn": true,
}
const CONFIG_PATH := "user://settings.cfg"
const CONFIG_SECTION := "audio"
const CONFIG_KEY_MUSIC_VOLUME := "menu_music_volume"
const CONFIG_KEY_MUSIC_MUTED := "menu_music_muted"
const DEFAULT_VOLUME := 0.7
var _last_scene: Node = null
var _user_volume_linear: float = DEFAULT_VOLUME
var _is_muted: bool = false
func _ready() -> void:
set_process(true)
_load_settings()
_apply_volume()
_update_playback(get_tree().current_scene)
func _process(_delta: float) -> void:
var current := get_tree().current_scene
if current != _last_scene:
_update_playback(current)
elif _should_play_scene(current) and not playing and not _is_muted and _user_volume_linear > 0.0:
play()
func _update_playback(scene: Node) -> void:
if scene == null:
_last_scene = null
return
_last_scene = scene
if _should_play_scene(scene):
if not playing:
play()
elif playing:
stop()
func _should_play_scene(scene: Node) -> bool:
if scene == null:
return false
var scene_path: String = scene.get_scene_file_path()
if scene_path.is_empty():
return false
return MENU_SCENES.has(scene_path)
func set_user_volume(value: float) -> void:
var clamped_value: float = clamp(value, 0.0, 1.0)
if is_equal_approx(_user_volume_linear, clamped_value):
return
_user_volume_linear = clamped_value
_apply_volume()
_save_settings()
func get_user_volume() -> float:
return _user_volume_linear
func set_user_muted(muted: bool) -> void:
var new_muted: bool = muted
if _is_muted == new_muted:
return
_is_muted = new_muted
_apply_volume()
_save_settings()
func is_user_muted() -> bool:
return _is_muted
func _apply_volume() -> void:
if _is_muted or _user_volume_linear <= 0.0:
volume_db = -80.0
else:
volume_db = linear_to_db(_user_volume_linear)
func _load_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err == OK:
var stored_volume: float = float(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, DEFAULT_VOLUME))
_user_volume_linear = clamp(stored_volume, 0.0, 1.0)
var stored_muted: bool = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, false))
_is_muted = stored_muted
elif err == ERR_DOES_NOT_EXIST:
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
else:
push_warning("Failed to load settings.cfg: %s" % err)
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
func _save_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err != OK and err != ERR_DOES_NOT_EXIST:
push_warning("Failed to load settings.cfg before saving: %s" % err)
config = ConfigFile.new()
elif err != OK:
config = ConfigFile.new()
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, _user_volume_linear)
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, _is_muted)
var save_err: int = config.save(CONFIG_PATH)
if save_err != OK:
push_warning("Failed to save settings.cfg: %s" % save_err)
extends AudioStreamPlayer
const MENU_SCENES: Dictionary = {
"res://scenes/UI/start_screen.tscn": true,
"res://scenes/UI/Settings.tscn": true,
"res://scenes/UI/login_screen.tscn": true,
}
const CONFIG_PATH := "user://settings.cfg"
const CONFIG_SECTION := "audio"
const CONFIG_KEY_MUSIC_VOLUME := "menu_music_volume"
const CONFIG_KEY_MUSIC_MUTED := "menu_music_muted"
const DEFAULT_VOLUME := 0.7
var _last_scene: Node = null
var _user_volume_linear: float = DEFAULT_VOLUME
var _is_muted: bool = false
func _ready() -> void:
set_process(true)
_load_settings()
_apply_volume()
_update_playback(get_tree().current_scene)
func _process(_delta: float) -> void:
var current := get_tree().current_scene
if current != _last_scene:
_update_playback(current)
elif _should_play_scene(current) and not playing and not _is_muted and _user_volume_linear > 0.0:
play()
func _update_playback(scene: Node) -> void:
if scene == null:
_last_scene = null
return
_last_scene = scene
if _should_play_scene(scene):
if not playing:
play()
elif playing:
stop()
func _should_play_scene(scene: Node) -> bool:
if scene == null:
return false
var scene_path: String = scene.get_scene_file_path()
if scene_path.is_empty():
return false
return MENU_SCENES.has(scene_path)
func set_user_volume(value: float) -> void:
var clamped_value: float = clamp(value, 0.0, 1.0)
if is_equal_approx(_user_volume_linear, clamped_value):
return
_user_volume_linear = clamped_value
_apply_volume()
_save_settings()
func get_user_volume() -> float:
return _user_volume_linear
func set_user_muted(muted: bool) -> void:
var new_muted: bool = muted
if _is_muted == new_muted:
return
_is_muted = new_muted
_apply_volume()
_save_settings()
func is_user_muted() -> bool:
return _is_muted
func _apply_volume() -> void:
if _is_muted or _user_volume_linear <= 0.0:
volume_db = -80.0
else:
volume_db = linear_to_db(_user_volume_linear)
func _load_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err == OK:
var stored_volume: float = float(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, DEFAULT_VOLUME))
_user_volume_linear = clamp(stored_volume, 0.0, 1.0)
var stored_muted: bool = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, false))
_is_muted = stored_muted
elif err == ERR_DOES_NOT_EXIST:
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
else:
push_warning("Failed to load settings.cfg: %s" % err)
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
func _save_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err != OK and err != ERR_DOES_NOT_EXIST:
push_warning("Failed to load settings.cfg before saving: %s" % err)
config = ConfigFile.new()
elif err != OK:
config = ConfigFile.new()
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, _user_volume_linear)
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, _is_muted)
var save_err: int = config.save(CONFIG_PATH)
if save_err != OK:
push_warning("Failed to save settings.cfg: %s" % save_err)
+13 -13
View File
@@ -1,13 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://dfmq1n507y7d3"]
[ext_resource type="AudioStream" uid="uid://txgki0ijeuud" path="res://assets/audio/silly-test.ogg" id="1_ek0t3"]
[ext_resource type="Script" path="res://scenes/UI/menu_music.gd" id="2_21d4q"]
[node name="MenuMusic" type="AudioStreamPlayer"]
bus = &"Music"
stream = ExtResource("1_ek0t3")
autoplay = true
volume_db = -3.0
priority = 10.0
script = ExtResource("2_21d4q")
[gd_scene load_steps=3 format=3 uid="uid://dfmq1n507y7d3"]
[ext_resource type="AudioStream" uid="uid://txgki0ijeuud" path="res://assets/audio/silly-test.ogg" id="1_ek0t3"]
[ext_resource type="Script" path="res://scenes/UI/menu_music.gd" id="2_21d4q"]
[node name="MenuMusic" type="AudioStreamPlayer"]
bus = &"Music"
stream = ExtResource("1_ek0t3")
autoplay = true
volume_db = -3.0
priority = 10.0
script = ExtResource("2_21d4q")
+76 -76
View File
@@ -1,76 +1,76 @@
extends AudioStreamPlayer
const CONFIG_PATH := "user://settings.cfg"
const CONFIG_SECTION := "audio"
const CONFIG_KEY_VOLUME := "menu_sfx_volume"
const CONFIG_KEY_MUTED := "menu_sfx_muted"
const DEFAULT_VOLUME := 0.7
var _user_volume_linear: float = DEFAULT_VOLUME
var _is_muted: bool = false
func _ready() -> void:
_load_settings()
_apply_volume()
func play_hover() -> void:
if stream == null or _is_muted or _user_volume_linear <= 0.0:
return
play(0.0)
func set_user_volume(value: float) -> void:
var clamped_value: float = clamp(value, 0.0, 1.0)
if is_equal_approx(_user_volume_linear, clamped_value):
return
_user_volume_linear = clamped_value
_apply_volume()
_save_settings()
func get_user_volume() -> float:
return _user_volume_linear
func set_user_muted(muted: bool) -> void:
if _is_muted == muted:
return
_is_muted = muted
_apply_volume()
_save_settings()
if _is_muted:
stop()
func is_user_muted() -> bool:
return _is_muted
func _apply_volume() -> void:
if _is_muted or _user_volume_linear <= 0.0:
volume_db = -80.0
else:
volume_db = linear_to_db(_user_volume_linear)
func _load_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err == OK:
_user_volume_linear = clamp(float(config.get_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, DEFAULT_VOLUME)), 0.0, 1.0)
_is_muted = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUTED, false))
elif err == ERR_DOES_NOT_EXIST:
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
else:
push_warning("Failed to load settings.cfg: %s" % err)
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
func _save_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err != OK and err != ERR_DOES_NOT_EXIST:
push_warning("Failed to load settings.cfg before saving: %s" % err)
config = ConfigFile.new()
elif err != OK:
config = ConfigFile.new()
config.set_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, _user_volume_linear)
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUTED, _is_muted)
var save_err: int = config.save(CONFIG_PATH)
if save_err != OK:
push_warning("Failed to save settings.cfg: %s" % save_err)
extends AudioStreamPlayer
const CONFIG_PATH := "user://settings.cfg"
const CONFIG_SECTION := "audio"
const CONFIG_KEY_VOLUME := "menu_sfx_volume"
const CONFIG_KEY_MUTED := "menu_sfx_muted"
const DEFAULT_VOLUME := 0.7
var _user_volume_linear: float = DEFAULT_VOLUME
var _is_muted: bool = false
func _ready() -> void:
_load_settings()
_apply_volume()
func play_hover() -> void:
if stream == null or _is_muted or _user_volume_linear <= 0.0:
return
play(0.0)
func set_user_volume(value: float) -> void:
var clamped_value: float = clamp(value, 0.0, 1.0)
if is_equal_approx(_user_volume_linear, clamped_value):
return
_user_volume_linear = clamped_value
_apply_volume()
_save_settings()
func get_user_volume() -> float:
return _user_volume_linear
func set_user_muted(muted: bool) -> void:
if _is_muted == muted:
return
_is_muted = muted
_apply_volume()
_save_settings()
if _is_muted:
stop()
func is_user_muted() -> bool:
return _is_muted
func _apply_volume() -> void:
if _is_muted or _user_volume_linear <= 0.0:
volume_db = -80.0
else:
volume_db = linear_to_db(_user_volume_linear)
func _load_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err == OK:
_user_volume_linear = clamp(float(config.get_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, DEFAULT_VOLUME)), 0.0, 1.0)
_is_muted = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUTED, false))
elif err == ERR_DOES_NOT_EXIST:
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
else:
push_warning("Failed to load settings.cfg: %s" % err)
_user_volume_linear = DEFAULT_VOLUME
_is_muted = false
func _save_settings() -> void:
var config: ConfigFile = ConfigFile.new()
var err: int = config.load(CONFIG_PATH)
if err != OK and err != ERR_DOES_NOT_EXIST:
push_warning("Failed to load settings.cfg before saving: %s" % err)
config = ConfigFile.new()
elif err != OK:
config = ConfigFile.new()
config.set_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, _user_volume_linear)
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUTED, _is_muted)
var save_err: int = config.save(CONFIG_PATH)
if save_err != OK:
push_warning("Failed to save settings.cfg: %s" % save_err)
+13 -13
View File
@@ -1,13 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://dt785sv7ie7uj"]
[ext_resource type="AudioStream" uid="uid://64dplcgx2icb" path="res://assets/audio/silly-menu-hover-test.ogg" id="1_a5j5k"]
[ext_resource type="Script" path="res://scenes/UI/menu_sfx.gd" id="1_ijvfa"]
[node name="MenuSfx" type="AudioStreamPlayer"]
bus = &"SFX"
stream = ExtResource("1_a5j5k")
volume_db = -6.0
autoplay = false
priority = 0.5
max_polyphony = 4
script = ExtResource("1_ijvfa")
[gd_scene load_steps=3 format=3 uid="uid://dt785sv7ie7uj"]
[ext_resource type="AudioStream" uid="uid://64dplcgx2icb" path="res://assets/audio/silly-menu-hover-test.ogg" id="1_a5j5k"]
[ext_resource type="Script" path="res://scenes/UI/menu_sfx.gd" id="1_ijvfa"]
[node name="MenuSfx" type="AudioStreamPlayer"]
bus = &"SFX"
stream = ExtResource("1_a5j5k")
volume_db = -6.0
autoplay = false
priority = 0.5
max_polyphony = 4
script = ExtResource("1_ijvfa")
+12
View File
@@ -271,6 +271,18 @@ func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
vehicle_exited.emit(null)
func teleport_to_spawn(spawn_transform: Transform3D) -> void:
global_transform = spawn_transform
linear_velocity = Vector3.ZERO
angular_velocity = Vector3.ZERO
sleeping = false
_camera_yaw = global_transform.basis.get_euler().y
if cam:
var target_basis := Basis(Vector3.UP, _camera_yaw)
cam.global_position = global_position + (target_basis * _camera_offset_local)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
func is_in_vehicle() -> bool:
return _in_vehicle