Adding scene teleporter
All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 46s
Deploy Promiscuity Locations API / deploy (push) Successful in 46s
k8s smoke test / test (push) Successful in 8s

This commit is contained in:
Zeeshaun 2026-03-06 12:34:59 -06:00
parent 2850c26657
commit 0a8cf20de9
12 changed files with 229 additions and 286 deletions

View File

@ -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)

View File

@ -0,0 +1 @@
uid://dyvldjfan2beq

View File

@ -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")

View File

@ -13,10 +13,12 @@ var time := 0.0
@onready var _player: Node = $Player @onready var _player: Node = $Player
@onready var _quest_text: RichTextLabel = $PhoneUI/Control/PhoneFrame/QuestText @onready var _quest_text: RichTextLabel = $PhoneUI/Control/PhoneFrame/QuestText
const FIRST_QUEST_ID := "first_drive" const FIRST_QUEST_ID := "first_drive"
const FIRST_QUEST := { const QUEST_PROMPT_META_PREFIX := "quest_intro_prompt_shown_"
"id": FIRST_QUEST_ID, const SPAWN_DIALOG_META_KEY := "level_spawn_dialog_shown"
"title": "RepoBot's First Task", const FIRST_QUEST := {
"id": FIRST_QUEST_ID,
"title": "RepoBot's First Task",
"description": "Get familiar with movement and vehicles.", "description": "Get familiar with movement and vehicles.",
"steps": [ "steps": [
{ {
@ -32,16 +34,17 @@ const FIRST_QUEST := {
], ],
} }
func _ready() -> void: func _ready() -> void:
_setup_quests() _setup_quests()
if show_spawn_dialog and DialogSystem and DialogSystem.has_method("show_text"): if _should_show_spawn_dialog() and DialogSystem and DialogSystem.has_method("show_text"):
await get_tree().process_frame await get_tree().process_frame
DialogSystem.show_text(spawn_dialog_text) DialogSystem.show_text(spawn_dialog_text)
if spawn_dialog_auto_close_seconds > 0.0: _mark_spawn_dialog_shown()
await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout if spawn_dialog_auto_close_seconds > 0.0:
if DialogSystem and DialogSystem.has_method("close_if_text"): await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout
DialogSystem.close_if_text(spawn_dialog_text) if DialogSystem and DialogSystem.has_method("close_if_text"):
_show_quest_intro_dialog() DialogSystem.close_if_text(spawn_dialog_text)
_show_quest_intro_dialog()
func _process(delta): func _process(delta):
time = fmod((time + delta), day_length) 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] _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: func _show_quest_intro_dialog() -> void:
if QuestManager == null: if QuestManager == null:
return return
var state: Dictionary = QuestManager.get_active_quest_state() var state: Dictionary = QuestManager.get_active_quest_state()
if not bool(state.get("active", false)) or bool(state.get("completed", false)): if not bool(state.get("active", false)) or bool(state.get("completed", false)):
return return
var step_text := String(state.get("current_step_text", "")) var quest_id := String(state.get("quest_id", "")).strip_edges()
if step_text.is_empty(): var step_id := String(state.get("current_step_id", "")).strip_edges()
return var step_text := String(state.get("current_step_text", ""))
if DialogSystem and DialogSystem.has_method("show_text"): if quest_id.is_empty() or step_id.is_empty() or step_text.is_empty():
DialogSystem.show_text("RepoBot: New task assigned.\n\n%s" % step_text) 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)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=81 format=3 uid="uid://dchj6g2i8ebph"] [gd_scene load_steps=82 format=3 uid="uid://dchj6g2i8ebph"]
[ext_resource type="Script" uid="uid://brgmxhhhtakja" path="res://scenes/Levels/level.gd" id="1_a4mo8"] [ext_resource type="Script" uid="uid://brgmxhhhtakja" path="res://scenes/Levels/level.gd" id="1_a4mo8"]
[ext_resource type="PackedScene" uid="uid://bb6hj6l23043x" path="res://assets/models/human.blend" id="1_eg4yq"] [ext_resource type="PackedScene" uid="uid://bb6hj6l23043x" path="res://assets/models/human.blend" id="1_eg4yq"]
@ -10,6 +10,7 @@
[ext_resource type="PackedScene" uid="uid://bnqaqbgynoyys" path="res://assets/models/TestCharAnimated.glb" id="5_fi66n"] [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://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="Script" uid="uid://cshtdpjp4xy2f" path="res://scenes/Quests/quest_trigger_area.gd" id="7_qtrigger"]
[ext_resource type="PackedScene" path="res://scenes/Interaction/scene_teleporter.tscn" id="8_teleporter"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"] [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"]
bounce = 0.5 bounce = 0.5
@ -609,6 +610,10 @@ scroll_active = false
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_a4mo8") 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.5, 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/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/MainMenuButton" to="Menu" method="_on_main_menu_button_pressed"]
[connection signal="pressed" from="Menu/Control/VBoxContainer/QuitButton" to="Menu" method="_on_quit_button_pressed"] [connection signal="pressed" from="Menu/Control/VBoxContainer/QuitButton" to="Menu" method="_on_quit_button_pressed"]

View File

@ -1,235 +0,0 @@
extends Node3D
const LOCATIONS_API_URL := "https://ploc.ranaze.com/api/Locations"
@export var tile_size := 4.0
@export var block_height := 1.0
@export_range(1, 8, 1) var tile_radius := 3
@export var tracked_node_path: NodePath
@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
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_location_coords: Dictionary = {}
var _locations_loaded := false
func _ready() -> void:
_tiles_root = Node3D.new()
_tiles_root.name = "GeneratedTiles"
add_child(_tiles_root)
if _camera:
_camera_start_offset = _camera.global_position
_tracked_node = get_node_or_null(tracked_node_path) as Node3D
if _tracked_node == null:
_tracked_node = get_node_or_null("Player") as Node3D
var start_coord := SelectedCharacter.get_coord()
_center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y))
_block.visible = false
await _load_existing_locations()
_rebuild_tiles(_center_coord)
_snap_camera_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
_rebuild_tiles(_center_coord)
func _get_stream_position() -> Vector3:
if _tracked_node:
return _tracked_node.global_position
if _camera:
return _camera.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 _snap_camera_to_coord(coord: Vector2i) -> void:
if _camera == null:
return
var center_world := _coord_to_world(coord)
_camera.global_position = center_world + _camera_start_offset
_camera.look_at(center_world, Vector3.UP)
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_location_coords.has(coord):
continue
wanted_keys[coord] = true
if _tile_nodes.has(coord):
continue
_spawn_tile(coord)
var keys_to_remove: Array = _tile_nodes.keys()
for key in keys_to_remove:
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) -> 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 := _block.duplicate() as MeshInstance3D
tile.name = "TileMesh"
tile.visible = true
tile.scale = Vector3(tile_size, block_height, tile_size)
tile_root.add_child(tile)
tile.add_child(_create_tile_border())
if show_tile_labels:
tile_root.add_child(_create_tile_label(coord))
var anchor := Marker3D.new()
anchor.name = "NpcAnchor"
anchor.position = Vector3(0.0, (block_height * 0.5) + 0.5, 0.0)
tile_root.add_child(anchor)
_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
var material := StandardMaterial3D.new()
material.albedo_color = border_color
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
material.disable_receive_shadows = true
material.no_depth_test = true
_border_material = material
return _border_material
func _create_tile_label(coord: Vector2i) -> Label3D:
var label := Label3D.new()
label.name = "LocationIdLabel"
label.text = _location_id_for_coord(coord)
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 _location_id_for_coord(coord: Vector2i) -> String:
return "%d,%d" % [coord.x, coord.y]
func _load_existing_locations() -> void:
_locations_loaded = false
_known_location_coords.clear()
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(LOCATIONS_API_URL, headers, HTTPClient.METHOD_GET)
if err != OK:
push_warning("Failed to request 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_bytes: PackedByteArray = result[3]
var response_body: String = response_bytes.get_string_from_utf8()
if result_code != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
push_warning("Failed to load 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("Locations response was not an array.")
_locations_loaded = true
return
for item in parsed:
if typeof(item) != TYPE_DICTIONARY:
continue
var location: Dictionary = item
var coord_variant: Variant = location.get("coord", {})
if typeof(coord_variant) != TYPE_DICTIONARY:
continue
var coord_dict: Dictionary = coord_variant
var x := int(coord_dict.get("x", 0))
var y := int(coord_dict.get("y", 0))
_known_location_coords[Vector2i(x, y)] = true
_locations_loaded = true

View File

@ -1 +0,0 @@
uid://1fico5npv6dy

View File

@ -1,23 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://b7p7k1i4t0m2l"]
[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_6y4q1"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yu2x4"]
albedo_color = Color(0.2, 0.6, 0.2, 1)
[sub_resource type="BoxMesh" id="BoxMesh_t2a5k"]
material = SubResource("StandardMaterial3D_yu2x4")
[node name="LocationLevel" type="Node3D"]
script = ExtResource("1_6y4q1")
[node name="TerrainBlock" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_t2a5k")
[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

View File

@ -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)

View File

@ -0,0 +1 @@
uid://c2vm651r4nepy

View File

@ -0,0 +1,62 @@
[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/scene_teleporter.tscn" id="4_teleporter"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"]
[sub_resource type="BoxShape3D" id="BoxShape3D_ground"]
size = Vector3(1080, 2, 1080)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ground"]
albedo_color = Color(0.194, 0.575, 0.194, 1)
[sub_resource type="BoxMesh" id="BoxMesh_ground"]
material = SubResource("StandardMaterial3D_ground")
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.5, 0)
target_scene_path = "res://scenes/Levels/level.tscn"

View File

@ -110,7 +110,7 @@ func _on_select_button_pressed() -> void:
var character: Dictionary = _characters[index] var character: Dictionary = _characters[index]
SelectedCharacter.set_character(character) SelectedCharacter.set_character(character)
get_tree().change_scene_to_file("res://scenes/Levels/location_level.tscn") get_tree().change_scene_to_file("res://scenes/Levels/transportation_level.tscn")
func _on_refresh_button_pressed() -> void: func _on_refresh_button_pressed() -> void:
_load_characters() _load_characters()