Merge branches 'main' and 'main' of https://git.ranaze.com/null/promiscuity
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 45s
Deploy Promiscuity Inventory API / deploy (push) Successful in 45s
Deploy Promiscuity Locations API / deploy (push) Successful in 45s
k8s smoke test / test (push) Successful in 8s

# Conflicts:
#	game/scenes/Levels/level.tscn
This commit is contained in:
2026-03-19 11:36:58 -05:00
235 changed files with 6906 additions and 232 deletions
+249
View File
@@ -0,0 +1,249 @@
extends RigidBody3D
# Initially I used a CharacterBody3D, however, I wanted the player to bounce off
# other objects in the environment and that would have required manual handling
# of collisions. So that's why we're using a RigidBody3D instead.
signal vehicle_entered(vehicle: Node)
signal vehicle_exited(vehicle: Node)
const MOVE_SPEED := 8.0
const SPRINT_MOVE_SPEED :=13
const ACCELLERATION := 30.0
const DECELLERATION := 40.0
const JUMP_SPEED := 4.0
const MAX_NUMBER_OF_JUMPS := 2
const MIN_FOV := 10.0
const MAX_FOV := 179.0
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
var mouse_sensitivity := 0.005
var rotation_x := 0.0
var rotation_y := 0.0
var cameraMoveMode := false
var current_number_of_jumps := 0
var _pending_mouse_delta := Vector2.ZERO
var _last_move_forward := Vector3(0, 0, 1)
var _last_move_right := Vector3(1, 0, 0)
var _camera_offset_local := Vector3.ZERO
var _camera_yaw := 0.0
var _camera_pitch := 0.0
var _in_vehicle := false
var _vehicle_collision_layer := 0
var _vehicle_collision_mask := 0
var _vehicle_original_parent: Node = null
var _light_was_on := false
var _jump_triggered := false
@onready var _flashlight: SpotLight3D = $SpotLight3D
@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
@onready var _anim_tree: AnimationTree = find_child("AnimationTree", true, false) as AnimationTree
@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D
@export var camera_follow_speed := 10.0
@export var anim_idle_name := "Idle"
@export var anim_walk_name := "Walk"
@export var anim_jump_name := "Jump"
@export var anim_run_name := "Run"
@export var anim_walk_speed_threshold := 0.25
@export var anim_sprint_speed_threshold := 10.0
var jump_sound = preload("res://assets/audio/jump.ogg")
var audio_player = AudioStreamPlayer.new()
@export var camera_path: NodePath
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
@export var phone_path: NodePath
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
var phone_visible := false
func _ready() -> void:
add_to_group("player")
if _anim_tree:
_anim_tree.active = false
axis_lock_angular_x = true
axis_lock_angular_z = true
angular_damp = 6.0
contact_monitor = true
max_contacts_reported = 4
add_child(audio_player)
audio_player.stream = jump_sound
audio_player.volume_db = -20
if cam:
_camera_offset_local = cam.transform.origin
_camera_pitch = cam.rotation.x
_camera_yaw = global_transform.basis.get_euler().y
cam.set_as_top_level(true)
cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
var move_basis := cam.global_transform.basis if cam else global_transform.basis
var forward := move_basis.z
var right := move_basis.x
forward.y = 0.0
right.y = 0.0
if forward.length() > 0.0001:
_last_move_forward = forward.normalized()
if right.length() > 0.0001:
_last_move_right = right.normalized()
_vehicle_collision_layer = collision_layer
_vehicle_collision_mask = collision_mask
func _integrate_forces(state):
if _in_vehicle:
linear_velocity = Vector3.ZERO
return
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90))
_camera_pitch = rotation_x
rotation.y = rotation_y
_pending_mouse_delta = Vector2.ZERO
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var forward := Vector3.FORWARD * -1.0
var right := Vector3.RIGHT
if cam:
forward = cam.global_transform.basis.z
right = cam.global_transform.basis.x
forward.y = 0.0
right.y = 0.0
if forward.length() > 0.0001:
forward = forward.normalized()
_last_move_forward = forward
else:
forward = _last_move_forward
if right.length() > 0.0001:
right = right.normalized()
_last_move_right = right
else:
right = _last_move_right
var dir := (right * input2v.x + forward * input2v.y).normalized()
var target_v := dir * MOVE_SPEED
if Input.is_key_pressed(KEY_SHIFT):
target_v = dir * SPRINT_MOVE_SPEED
var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
var on_floor = false
for i in state.get_contact_count():
var normal = state.get_contact_local_normal(i)
if normal.y > 0.5:
on_floor = true
break
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
current_number_of_jumps = (current_number_of_jumps + 1) % 2
linear_velocity.y = JUMP_SPEED
audio_player.play()
_jump_triggered = true
if cam:
var target_yaw := global_transform.basis.get_euler().y
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
var target_basis := Basis(Vector3.UP, _camera_yaw)
var target_pos := global_position + (target_basis * _camera_offset_local)
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
_update_animation(on_floor, state.linear_velocity)
_jump_triggered = false
func _input(event):
if event.is_action_pressed("player_phone"):
phone_visible = !phone_visible
if phone:
phone.visible = phone_visible
return
if _in_vehicle:
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_MIDDLE:
if event.pressed:
cameraMoveMode = true
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
else:
cameraMoveMode = false
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
if event is InputEventMouseMotion and cameraMoveMode:
_pending_mouse_delta += event.relative
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_camera(1.0 / ZOOM_FACTOR)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_camera(ZOOM_FACTOR)
if event.is_action_pressed("player_light"):
_flashlight.visible = !_flashlight.visible
func zoom_camera(factor):
if cam == null:
return
var new_fov = cam.fov * factor
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)
func _update_animation(on_floor: bool, velocity: Vector3) -> void:
if _anim_player == null:
return
var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length()
if _jump_triggered and _anim_player.has_animation(anim_jump_name):
if _anim_player.current_animation != anim_jump_name:
_anim_player.play(anim_jump_name)
return
if not on_floor and _anim_player.has_animation(anim_jump_name):
if _anim_player.current_animation != anim_jump_name:
_anim_player.play(anim_jump_name)
return
if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name):
if _anim_player.current_animation != anim_run_name:
_anim_player.play(anim_run_name)
return
if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name):
if _anim_player.current_animation != anim_walk_name:
_anim_player.play(anim_walk_name)
return
if _anim_player.has_animation(anim_idle_name):
if _anim_player.current_animation != anim_idle_name:
_anim_player.play(anim_idle_name)
func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void:
_in_vehicle = true
freeze = true
sleeping = true
collision_layer = 0
collision_mask = 0
_vehicle_original_parent = get_parent()
_light_was_on = _flashlight.visible
_flashlight.visible = false
if _model_root:
_model_root.visible = false
if seat:
reparent(seat, true)
global_transform = seat.global_transform
if cam:
cam.current = false
if vehicle_camera:
vehicle_camera.current = true
vehicle_entered.emit(_vehicle)
func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
_in_vehicle = false
freeze = false
sleeping = false
collision_layer = _vehicle_collision_layer
collision_mask = _vehicle_collision_mask
if _vehicle_original_parent:
reparent(_vehicle_original_parent, true)
_vehicle_original_parent = null
_flashlight.visible = _light_was_on
if _model_root:
_model_root.visible = true
if exit_point:
global_transform = exit_point.global_transform
if vehicle_camera:
vehicle_camera.current = false
if cam:
cam.current = true
vehicle_exited.emit(null)
@@ -0,0 +1 @@
uid://kgqaeqappow3
@@ -0,0 +1,28 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/Characters/location_player.gd" id="1_player_script"]
[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="2_model"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
[node name="LocationPlayer" type="RigidBody3D"]
script = ExtResource("1_player_script")
camera_path = NodePath("Camera3D")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
shape = SubResource("SphereShape3D_player")
[node name="TestCharAnimated" parent="." instance=ExtResource("2_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="."]
transform = Transform3D(0.9998477, 0, -0.017452406, 0.0066714617, 0.9238795, 0.38262552, 0.016124869, -0.38268343, 0.92373866, 0, 6, 10)
current = true
fov = 49.0
[node name="SpotLight3D" type="SpotLight3D" parent="."]
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
@@ -0,0 +1,31 @@
@tool
extends Node3D
@export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn":
set(value):
target_scene_path = value
_sync_teleporter()
@export var target_group: StringName = &"player":
set(value):
target_group = value
_sync_teleporter()
@export var one_shot := true:
set(value):
one_shot = value
_sync_teleporter()
@onready var teleporter: Area3D = $Teleporter
func _ready() -> void:
_sync_teleporter()
func _sync_teleporter() -> void:
if teleporter == null:
return
teleporter.set("target_scene_path", target_scene_path)
teleporter.set("target_group", target_group)
teleporter.set("one_shot", one_shot)
@@ -0,0 +1 @@
uid://1c0reto6vt6m
@@ -0,0 +1,117 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://scenes/Interaction/prototype_gateway.gd" id="1_gateway_script"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/scene_teleporter.tscn" id="2_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_gateway_orange.tres" id="3_gateway_mat"]
[sub_resource type="BoxShape3D" id="BoxShape3D_gateway"]
size = Vector3(1, 1, 1)
[sub_resource type="BoxMesh" id="BoxMesh_gateway"]
material = ExtResource("3_gateway_mat")
size = Vector3(1, 1, 1)
[node name="PrototypeGateway" type="Node3D"]
script = ExtResource("1_gateway_script")
[node name="LeftBase" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftBase"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftBase"]
mesh = SubResource("BoxMesh_gateway")
[node name="LeftMidLow" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftMidLow"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftMidLow"]
mesh = SubResource("BoxMesh_gateway")
[node name="LeftMidHigh" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftMidHigh"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftMidHigh"]
mesh = SubResource("BoxMesh_gateway")
[node name="LeftTop" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 3.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftTop"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftTop"]
mesh = SubResource("BoxMesh_gateway")
[node name="RightBase" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="RightBase"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RightBase"]
mesh = SubResource("BoxMesh_gateway")
[node name="RightMidLow" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="RightMidLow"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RightMidLow"]
mesh = SubResource("BoxMesh_gateway")
[node name="RightMidHigh" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="RightMidHigh"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RightMidHigh"]
mesh = SubResource("BoxMesh_gateway")
[node name="RightTop" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 3.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="RightTop"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RightTop"]
mesh = SubResource("BoxMesh_gateway")
[node name="TopLeft" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 4.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="TopLeft"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="TopLeft"]
mesh = SubResource("BoxMesh_gateway")
[node name="TopCenter" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="TopCenter"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="TopCenter"]
mesh = SubResource("BoxMesh_gateway")
[node name="TopRight" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 4.5, 0)
[node name="CollisionShape3D" type="CollisionShape3D" parent="TopRight"]
shape = SubResource("BoxShape3D_gateway")
[node name="MeshInstance3D" type="MeshInstance3D" parent="TopRight"]
mesh = SubResource("BoxMesh_gateway")
[node name="Teleporter" parent="." instance=ExtResource("2_teleporter")]
transform = Transform3D(1.15, 0, 0, 0, 1.33, 0, 0, 0, 0.7, 0, 1.5, 0)
@@ -0,0 +1,39 @@
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
var _is_transitioning := false
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node) -> void:
if _is_transitioning:
return
if target_group != StringName() and not body.is_in_group(target_group):
return
if target_scene_path.strip_edges() == "":
push_warning("Teleporter target scene is empty.")
return
if not ResourceLoader.exists(target_scene_path):
push_warning("Teleporter target scene does not exist: %s" % target_scene_path)
return
_is_transitioning = true
if one_shot:
set_deferred("monitoring", false)
call_deferred("_deferred_change_scene")
func _deferred_change_scene() -> void:
var err := get_tree().change_scene_to_file(target_scene_path)
if err == OK:
return
push_warning("Failed to change scene to '%s' (%s)." % [target_scene_path, err])
_is_transitioning = false
if one_shot:
set_deferred("monitoring", true)
@@ -0,0 +1 @@
uid://dyvldjfan2beq
@@ -0,0 +1,32 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/Interaction/scene_teleporter.gd" id="1_tele"]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_tele"]
height = 3.0
radius = 1.5
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tele"]
transparency = 1
shading_mode = 0
albedo_color = Color(0.15, 0.95, 1, 0.25)
emission_enabled = true
emission = Color(0.1, 0.9, 1, 1)
emission_energy_multiplier = 1.5
[sub_resource type="CylinderMesh" id="CylinderMesh_tele"]
material = SubResource("StandardMaterial3D_tele")
top_radius = 1.6
bottom_radius = 1.6
height = 3.0
[node name="SceneTeleporter" type="Area3D"]
collision_layer = 2
collision_mask = 1
script = ExtResource("1_tele")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CylinderShape3D_tele")
[node name="Visual" type="MeshInstance3D" parent="."]
mesh = SubResource("CylinderMesh_tele")
+50 -25
View File
@@ -13,10 +13,12 @@ var time := 0.0
@onready var _player: Node = $Player
@onready var _quest_text: RichTextLabel = $PhoneUI/Control/PhoneFrame/QuestText
const FIRST_QUEST_ID := "first_drive"
const FIRST_QUEST := {
"id": FIRST_QUEST_ID,
"title": "RepoBot's First Task",
const FIRST_QUEST_ID := "first_drive"
const QUEST_PROMPT_META_PREFIX := "quest_intro_prompt_shown_"
const SPAWN_DIALOG_META_KEY := "level_spawn_dialog_shown"
const FIRST_QUEST := {
"id": FIRST_QUEST_ID,
"title": "RepoBot's First Task",
"description": "Get familiar with movement and vehicles.",
"steps": [
{
@@ -32,16 +34,17 @@ const FIRST_QUEST := {
],
}
func _ready() -> void:
_setup_quests()
if show_spawn_dialog and DialogSystem and DialogSystem.has_method("show_text"):
await get_tree().process_frame
DialogSystem.show_text(spawn_dialog_text)
if spawn_dialog_auto_close_seconds > 0.0:
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 _ready() -> void:
_setup_quests()
if _should_show_spawn_dialog() and DialogSystem and DialogSystem.has_method("show_text"):
await get_tree().process_frame
DialogSystem.show_text(spawn_dialog_text)
_mark_spawn_dialog_shown()
if spawn_dialog_auto_close_seconds > 0.0:
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)
@@ -94,14 +97,36 @@ func _refresh_quest_ui() -> void:
_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 step_text := String(state.get("current_step_text", ""))
if step_text.is_empty():
return
if DialogSystem and DialogSystem.has_method("show_text"):
DialogSystem.show_text("RepoBot: New task assigned.\n\n%s" % 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)
func _should_show_spawn_dialog() -> bool:
if not show_spawn_dialog:
return false
if QuestManager == null:
return true
if not QuestManager.has_meta(SPAWN_DIALOG_META_KEY):
return true
return not bool(QuestManager.get_meta(SPAWN_DIALOG_META_KEY))
func _mark_spawn_dialog_shown() -> void:
if QuestManager == null:
return
QuestManager.set_meta(SPAWN_DIALOG_META_KEY, true)
+7
View File
@@ -10,6 +10,8 @@
[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"]
[ext_resource type="Script" uid="uid://cshtdpjp4xy2f" path="res://scenes/Quests/quest_trigger_area.gd" id="7_qtrigger"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="8_teleporter"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="9_ground_mat"]
[ext_resource type="Shader" uid="uid://bi3o8elbtqoni" path="res://addons/simplegrasstextured/shaders/grass.gdshader" id="9_43ksg"]
[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"]
@@ -276,6 +278,7 @@ height = 6.0
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxMesh" id="BoxMesh_w7c3h"]
material = ExtResource("9_ground_mat")
size = Vector3(1080, 2, 1080)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_i35yb"]
@@ -564,6 +567,10 @@ scroll_active = false
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_a4mo8")
[node name="LevelExitTeleporter" parent="." instance=ExtResource("8_teleporter")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 0)
target_scene_path = "res://scenes/Levels/transportation_level.tscn"
[connection signal="pressed" from="Menu/Control/VBoxContainer/ContinueButton" to="Menu" method="_on_continue_button_pressed"]
[connection signal="pressed" from="Menu/Control/VBoxContainer/MainMenuButton" to="Menu" method="_on_main_menu_button_pressed"]
[connection signal="pressed" from="Menu/Control/VBoxContainer/QuitButton" to="Menu" method="_on_quit_button_pressed"]
+324 -7
View File
@@ -1,15 +1,332 @@
extends Node3D
@export var tile_size := 4.0
const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters"
@export var tile_size := 16.0
@export var block_height := 1.0
@export_range(1, 8, 1) var tile_radius := 3
@export var tracked_node_path: NodePath
@export var player_spawn_height := 2.0
@export var border_color: Color = Color(0.05, 0.05, 0.05, 1.0)
@export var border_height_bias := 0.005
@export var show_tile_labels := true
@export var tile_label_height := 0.01
@export var tile_label_color: Color = Color(1, 1, 1, 1)
@onready var _block: MeshInstance3D = $TerrainBlock
@onready var _camera: Camera3D = $Camera3D
@onready var _player: RigidBody3D = $Player
@onready var _camera: Camera3D = $Player/Camera3D
var _center_coord := Vector2i.ZERO
var _tiles_root: Node3D
var _tracked_node: Node3D
var _tile_nodes: Dictionary = {}
var _camera_start_offset := Vector3(0.0, 6.0, 10.0)
var _border_material: StandardMaterial3D
var _known_locations: Dictionary = {}
var _locations_loaded := false
var _character_id := ""
var _persisted_coord := Vector2i.ZERO
var _coord_sync_in_flight := false
var _queued_coord_sync: Variant = null
var _locations_refresh_in_flight := false
var _queued_locations_refresh := false
func _ready() -> void:
var coord := SelectedCharacter.get_coord()
var block_pos := Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size)
_block.position = block_pos
_block.scale = Vector3(tile_size, block_height, tile_size)
_tiles_root = Node3D.new()
_tiles_root.name = "GeneratedTiles"
add_child(_tiles_root)
if _camera:
_camera.look_at(block_pos, Vector3.UP)
_camera_start_offset = _camera.position
_tracked_node = get_node_or_null(tracked_node_path) as Node3D
if _tracked_node == null:
_tracked_node = _player
var start_coord := SelectedCharacter.get_coord()
_center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y))
_persisted_coord = _center_coord
_character_id = String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges()
_block.visible = false
await _load_existing_locations()
_ensure_selected_location_exists(_center_coord)
_rebuild_tiles(_center_coord)
_move_player_to_coord(_center_coord)
func _process(_delta: float) -> void:
if not _locations_loaded:
return
var target_world_pos := _get_stream_position()
var target_coord := _world_to_coord(target_world_pos)
if target_coord == _center_coord:
return
_center_coord = target_coord
_queue_coord_sync(_center_coord)
_queue_locations_refresh()
func _get_stream_position() -> Vector3:
if _tracked_node:
return _tracked_node.global_position
return _coord_to_world(_center_coord)
func _world_to_coord(world_pos: Vector3) -> Vector2i:
return Vector2i(
roundi(world_pos.x / tile_size),
roundi(world_pos.z / tile_size)
)
func _coord_to_world(coord: Vector2i) -> Vector3:
return Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size)
func _move_player_to_coord(coord: Vector2i) -> void:
if _player == null:
return
_player.global_position = Vector3(coord.x * tile_size, player_spawn_height, coord.y * tile_size)
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
func _rebuild_tiles(center: Vector2i) -> void:
var wanted_keys: Dictionary = {}
for x in range(center.x - tile_radius, center.x + tile_radius + 1):
for y in range(center.y - tile_radius, center.y + tile_radius + 1):
var coord := Vector2i(x, y)
if not _known_locations.has(coord):
continue
wanted_keys[coord] = true
if _tile_nodes.has(coord):
continue
_spawn_tile(coord, String(_known_locations[coord]))
for key in _tile_nodes.keys():
if wanted_keys.has(key):
continue
var tile_node := _tile_nodes[key] as Node3D
if tile_node:
tile_node.queue_free()
_tile_nodes.erase(key)
func _spawn_tile(coord: Vector2i, location_name: String) -> void:
var tile_root := Node3D.new()
tile_root.name = "Tile_%d_%d" % [coord.x, coord.y]
tile_root.position = _coord_to_world(coord)
_tiles_root.add_child(tile_root)
var tile_body := StaticBody3D.new()
tile_body.name = "TileBody"
tile_body.scale = Vector3(tile_size, block_height, tile_size)
tile_root.add_child(tile_body)
var collision_shape := CollisionShape3D.new()
collision_shape.name = "CollisionShape3D"
collision_shape.shape = BoxShape3D.new()
tile_body.add_child(collision_shape)
var tile := _block.duplicate() as MeshInstance3D
tile.name = "TileMesh"
tile.visible = true
tile_body.add_child(tile)
tile.add_child(_create_tile_border())
if show_tile_labels:
tile_root.add_child(_create_tile_label(location_name))
_tile_nodes[coord] = tile_root
func _create_tile_border() -> MeshInstance3D:
var top_y := 0.5 + border_height_bias
var corners := [
Vector3(-0.5, top_y, -0.5),
Vector3(0.5, top_y, -0.5),
Vector3(0.5, top_y, 0.5),
Vector3(-0.5, top_y, 0.5),
]
var border_mesh := ImmediateMesh.new()
border_mesh.surface_begin(Mesh.PRIMITIVE_LINES, _get_border_material())
for idx in range(corners.size()):
var current: Vector3 = corners[idx]
var next: Vector3 = corners[(idx + 1) % corners.size()]
border_mesh.surface_add_vertex(current)
border_mesh.surface_add_vertex(next)
border_mesh.surface_end()
var border := MeshInstance3D.new()
border.name = "TileBorder"
border.mesh = border_mesh
return border
func _get_border_material() -> StandardMaterial3D:
if _border_material:
return _border_material
_border_material = StandardMaterial3D.new()
_border_material.albedo_color = border_color
_border_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_border_material.disable_receive_shadows = true
_border_material.no_depth_test = true
return _border_material
func _create_tile_label(location_name: String) -> Label3D:
var label := Label3D.new()
label.name = "LocationNameLabel"
label.text = location_name
label.position = Vector3(0.0, (block_height * 0.5) + border_height_bias + tile_label_height, 0.0)
label.rotation_degrees = Vector3(-90.0, 0.0, 0.0)
label.billboard = BaseMaterial3D.BILLBOARD_DISABLED
label.modulate = tile_label_color
label.pixel_size = 0.01
label.outline_size = 12
label.no_depth_test = false
return label
func _ensure_selected_location_exists(coord: Vector2i) -> void:
if _known_locations.has(coord):
return
_known_locations[coord] = _selected_location_name(coord)
func _selected_location_name(coord: Vector2i) -> String:
var selected_name := String(SelectedCharacter.character.get("locationName", "")).strip_edges()
if not selected_name.is_empty():
return selected_name
var character_name := String(SelectedCharacter.character.get("name", "")).strip_edges()
if not character_name.is_empty():
return "%s's Location" % character_name
return "Location %d,%d" % [coord.x, coord.y]
func _load_existing_locations() -> void:
_locations_refresh_in_flight = true
_locations_loaded = false
_known_locations.clear()
if _character_id.is_empty():
push_warning("Selected character is missing an id; cannot load visible locations.")
_locations_loaded = true
return
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)
var err := request.request("%s/%s/visible-locations" % [CHARACTER_API_URL, _character_id], headers, HTTPClient.METHOD_GET)
if err != OK:
push_warning("Failed to request visible locations: %s" % err)
request.queue_free()
_locations_loaded = true
return
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 or response_code < 200 or response_code >= 300:
push_warning("Failed to load visible locations (%s/%s): %s" % [result_code, response_code, response_body])
_locations_loaded = true
return
var parsed: Variant = JSON.parse_string(response_body)
if typeof(parsed) != TYPE_ARRAY:
push_warning("Visible locations response was not an array.")
_locations_loaded = true
return
var loaded_count := 0
for item in parsed:
if typeof(item) != TYPE_DICTIONARY:
continue
var location := item as Dictionary
var coord_variant: Variant = location.get("coord", {})
if typeof(coord_variant) != TYPE_DICTIONARY:
continue
var coord_dict := coord_variant as Dictionary
var coord := Vector2i(int(coord_dict.get("x", 0)), int(coord_dict.get("y", 0)))
var location_name := String(location.get("name", "")).strip_edges()
if location_name.is_empty():
location_name = "Location %d,%d" % [coord.x, coord.y]
_known_locations[coord] = location_name
loaded_count += 1
print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, _character_id])
if loaded_count == 0:
push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id)
_locations_loaded = true
_locations_refresh_in_flight = false
_rebuild_tiles(_center_coord)
if _queued_locations_refresh:
_queued_locations_refresh = false
_queue_locations_refresh()
func _queue_locations_refresh() -> void:
if _locations_refresh_in_flight:
_queued_locations_refresh = true
return
_refresh_visible_locations()
func _refresh_visible_locations() -> void:
if _character_id.is_empty():
return
_refresh_visible_locations_async()
func _refresh_visible_locations_async() -> void:
await _load_existing_locations()
func _queue_coord_sync(coord: Vector2i) -> void:
if coord == _persisted_coord:
return
if _coord_sync_in_flight:
_queued_coord_sync = coord
return
_sync_character_coord(coord)
func _sync_character_coord(coord: Vector2i) -> void:
if _character_id.is_empty():
return
_coord_sync_in_flight = true
_queued_coord_sync = null
_sync_character_coord_async(coord)
func _sync_character_coord_async(coord: Vector2i) -> void:
var response := await CharacterService.update_character_coord(_character_id, coord)
if response.get("ok", false):
_persisted_coord = coord
SelectedCharacter.set_coord(coord)
else:
push_warning("Failed to persist character coord to %s,%s: status=%s error=%s body=%s" % [
coord.x,
coord.y,
response.get("status", "n/a"),
response.get("error", ""),
response.get("body", "")
])
_coord_sync_in_flight = false
if _queued_coord_sync != null and _queued_coord_sync is Vector2i and _queued_coord_sync != _persisted_coord:
var queued_coord: Vector2i = _queued_coord_sync
_sync_character_coord(queued_coord)
+1 -1
View File
@@ -1 +1 @@
uid://1fico5npv6dy
uid://ctbyn1gws2ahj
+20 -11
View File
@@ -1,23 +1,32 @@
[gd_scene load_steps=4 format=3 uid="uid://b7p7k1i4t0m2l"]
[gd_scene load_steps=8 format=3]
[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_6y4q1"]
[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_level_script"]
[ext_resource type="PackedScene" path="res://scenes/Characters/location_player.tscn" id="2_player_scene"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_block_dark.tres" id="3_block_mat"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yu2x4"]
albedo_color = Color(0.2, 0.6, 0.2, 1)
[sub_resource type="BoxMesh" id="BoxMesh_tile"]
material = ExtResource("3_block_mat")
size = Vector3(1, 1, 1)
[sub_resource type="BoxMesh" id="BoxMesh_t2a5k"]
material = SubResource("StandardMaterial3D_yu2x4")
[sub_resource type="Environment" id="Environment_location"]
background_mode = 1
background_color = Color(0.55, 0.72, 0.92, 1)
ambient_light_source = 2
ambient_light_color = Color(1, 1, 1, 1)
ambient_light_energy = 0.8
[node name="LocationLevel" type="Node3D"]
script = ExtResource("1_6y4q1")
script = ExtResource("1_level_script")
tracked_node_path = NodePath("Player")
[node name="TerrainBlock" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_t2a5k")
mesh = SubResource("BoxMesh_tile")
[node name="Player" parent="." instance=ExtResource("2_player_scene")]
[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="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.92388, 0.382683, 0, -0.382683, 0.92388, 0, 6, 10)
current = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_location")
@@ -0,0 +1,37 @@
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
var _time := 0.0
func _ready() -> void:
_move_player_to_spawn()
func _process(delta: float) -> void:
_update_day_night(delta)
func _move_player_to_spawn() -> void:
if _player == null:
return
_player.global_position = player_spawn_position
_player.linear_velocity = Vector3.ZERO
_player.angular_velocity = Vector3.ZERO
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)
@@ -0,0 +1 @@
uid://c2vm651r4nepy
@@ -0,0 +1,60 @@
[gd_scene load_steps=9 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"]
[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"]
[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="TransportationLevel" type="Node3D"]
script = ExtResource("1_6y4q1")
[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"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00053596497, 0.0075991154, -0.0019865036)
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")
[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="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"
+13 -3
View File
@@ -11,9 +11,19 @@ func create_character(character_name: String) -> Dictionary:
})
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)
func delete_character(character_id: String) -> Dictionary:
var url := "%s/%s" % [CHARACTER_API_URL, character_id]
return await _request(HTTPClient.METHOD_DELETE, url)
func update_character_coord(character_id: String, coord: Vector2i) -> Dictionary:
var url := "%s/%s/coord" % [CHARACTER_API_URL, character_id]
var payload := JSON.stringify({
"coord": {
"x": coord.x,
"y": coord.y
}
})
return await _request(HTTPClient.METHOD_PUT, url, payload)
func _request(method: int, url: String, body: String = "") -> Dictionary:
var request := HTTPRequest.new()
+21 -12
View File
@@ -2,16 +2,22 @@ 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
@onready var _error_label: Label = %ErrorLabel
func _on_log_in_button_pressed() -> void:
var username := _username_input.text.strip_edges()
var password := _password_input.text
if username.is_empty() or password.is_empty():
_show_error("Username and password required.")
@onready var _username_input: LineEdit = %UsernameInput
@onready var _password_input: LineEdit = %PasswordInput
@onready var _login_request: HTTPRequest = %LoginRequest
@onready var _error_label: Label = %ErrorLabel
func _ready() -> void:
if not _username_input.is_connected("text_submitted", Callable(self, "_on_input_text_submitted")):
_username_input.text_submitted.connect(_on_input_text_submitted)
if not _password_input.is_connected("text_submitted", Callable(self, "_on_input_text_submitted")):
_password_input.text_submitted.connect(_on_input_text_submitted)
func _on_log_in_button_pressed() -> void:
var username := _username_input.text.strip_edges()
var password := _password_input.text
if username.is_empty() or password.is_empty():
_show_error("Username and password required.")
return
var payload := {
@@ -44,5 +50,8 @@ func _on_login_request_completed(result: int, response_code: int, _headers: Pack
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
func _show_error(message: String) -> void:
_error_label.text = message
func _on_input_text_submitted(_new_text: String) -> void:
_on_log_in_button_pressed()
+6
View File
@@ -14,3 +14,9 @@ func get_coord() -> Vector2:
float(coord.get("x", 0)),
float(coord.get("y", 0))
)
func set_coord(coord: Vector2i) -> void:
character["coord"] = {
"x": coord.x,
"y": coord.y
}
+2 -4
View File
@@ -1,6 +1,7 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://scenes/Vehicles/car.gd" id="1_kbd20"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_prop_red.tres" id="2_car_mat"]
[sub_resource type="BoxShape3D" id="BoxShape3D_7r1j6"]
size = Vector3(1.4, 0.9, 2.6)
@@ -11,9 +12,6 @@ size = Vector3(2.2, 2.0, 3.8)
[sub_resource type="BoxMesh" id="BoxMesh_4y8xk"]
size = Vector3(1.4, 0.9, 2.6)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_red"]
albedo_color = Color(0.85, 0.1, 0.1, 1)
[node name="Car" type="RigidBody3D"]
script = ExtResource("1_kbd20")
seat_path = NodePath("Seat")
@@ -26,7 +24,7 @@ shape = SubResource("BoxShape3D_7r1j6")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_4y8xk")
surface_material_override/0 = SubResource("StandardMaterial3D_red")
surface_material_override/0 = ExtResource("2_car_mat")
[node name="InteractArea" type="Area3D" parent="."]
+20 -21
View File
@@ -1,21 +1,20 @@
[gd_scene load_steps=4 format=3 uid="uid://c5of6aaxop1hl"]
[sub_resource type="BoxShape3D" id="BoxShape3D_4du60"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_alp5v"]
albedo_color = Color(0.290196, 0.698039, 0.227451, 1)
[sub_resource type="BoxMesh" id="BoxMesh_kryjk"]
material = SubResource("StandardMaterial3D_alp5v")
[node name="Block" type="Node3D"]
[node name="RigidBody3D" type="RigidBody3D" parent="."]
collision_layer = 3
collision_mask = 3
[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"]
shape = SubResource("BoxShape3D_4du60")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RigidBody3D"]
mesh = SubResource("BoxMesh_kryjk")
[gd_scene load_steps=4 format=3 uid="uid://c5of6aaxop1hl"]
[ext_resource type="Material" path="res://assets/materials/kenney_prototype_block_dark.tres" id="1_block_mat"]
[sub_resource type="BoxShape3D" id="BoxShape3D_4du60"]
[sub_resource type="BoxMesh" id="BoxMesh_kryjk"]
material = ExtResource("1_block_mat")
[node name="Block" type="Node3D"]
[node name="RigidBody3D" type="RigidBody3D" parent="."]
collision_layer = 3
collision_mask = 3
[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"]
shape = SubResource("BoxShape3D_4du60")
[node name="MeshInstance3D" type="MeshInstance3D" parent="RigidBody3D"]
mesh = SubResource("BoxMesh_kryjk")
+4 -2
View File
@@ -11,8 +11,8 @@ const ACCELLERATION := 30.0
const DECELLERATION := 40.0
const JUMP_SPEED := 4.0
const MAX_NUMBER_OF_JUMPS := 2
const MIN_FOV := 10
const MAX_FOV := 180
const MIN_FOV := 10.0
const MAX_FOV := 179.0
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
var mouse_sensitivity := 0.005
var rotation_x := 0.0
@@ -187,6 +187,8 @@ func _input(event):
_flashlight.visible = !_flashlight.visible
func zoom_camera(factor):
if cam == null:
return
var new_fov = cam.fov * factor
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)