Author SHA1 Message Date
pillboxstyx b3287ccf6f Starting to add a sprint, will figure out how to hook in the run animation next 2026-02-13 02:48:07 -06:00
admin d1fade919c Adding beginning of world block generation
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 57s
Deploy Promiscuity Locations API / deploy (push) Successful in 58s
k8s smoke test / test (push) Successful in 7s
2026-01-27 21:01:29 -06:00
admin b2eb85fdf1 Hooking up animations, and tweaking car behavior 2026-01-27 20:53:53 -06:00
admin d8138fbe69 sync assets and microservice updates 2026-01-27 20:41:28 -06:00
admin e54dd191f1 Merge branch 'car' 2026-01-27 20:37:17 -06:00
admin db632e2522 Merge pull request 'character-coords' (#5) from character-coords into main
Deploy Promiscuity Auth API / deploy (push) Successful in 45s
Deploy Promiscuity Character API / deploy (push) Successful in 1m25s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m0s
k8s smoke test / test (push) Successful in 7s
Reviewed-on: #5
2026-01-27 20:23:25 -06:00
deifiedexile 52e1dfe99f Add player test character with baked animations
Deploy Promiscuity Auth API / deploy (push) Successful in 58s
Deploy Promiscuity Character API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 44s
k8s smoke test / test (push) Successful in 8s
2026-01-27 17:43:05 -06:00
Zeeshaun 3a99c2154c Location Service now ensures that the 0,0 location exists 2026-01-23 14:24:08 -06:00
Zeeshaun 241312b0be Adding character coords 2026-01-23 14:24:08 -06:00
admin aef117bcf9 enhancing car behavior 2026-01-21 18:16:04 -06:00
admin 582b2c43cb Adding basic car 2026-01-21 17:59:14 -06:00
admin 900951b1a7 Location fixes
Deploy Promiscuity Auth API / deploy (push) Successful in 43s
Deploy Promiscuity Character API / deploy (push) Successful in 42s
Deploy Promiscuity Locations API / deploy (push) Successful in 56s
k8s smoke test / test (push) Successful in 6s
2026-01-21 17:43:39 -06:00
admin fc647890a6 Adding coordinate to Location model
Deploy Promiscuity Auth API / deploy (push) Successful in 43s
Deploy Promiscuity Character API / deploy (push) Successful in 42s
Deploy Promiscuity Locations API / deploy (push) Successful in 56s
k8s smoke test / test (push) Successful in 5s
2026-01-20 17:29:34 -06:00
admin 86fa27c78d Adding coordinate to Location model
Deploy Promiscuity Auth API / deploy (push) Successful in 44s
Deploy Promiscuity Character API / deploy (push) Successful in 42s
Deploy Promiscuity Locations API / deploy (push) Failing after 12s
k8s smoke test / test (push) Successful in 5s
2026-01-20 17:25:41 -06:00
admin 268eca39c0 Adding coordinate to Location model 2026-01-20 17:25:35 -06:00
admin 88e3767971 Merge branch 'main' of https://git.ranaze.com/null/promiscuity into main
Deploy Promiscuity Auth API / deploy (push) Successful in 1m15s
Deploy Promiscuity Character API / deploy (push) Successful in 46s
Deploy Promiscuity Locations API / deploy (push) Successful in 59s
k8s smoke test / test (push) Successful in 8s
2026-01-20 17:13:22 -06:00
admin cdeea60e52 Updating initial Location crud endpoints to be global & restricted to SUPER role 2026-01-20 17:13:12 -06:00
40 changed files with 1485 additions and 790 deletions
+8 -1
View File
@@ -11,4 +11,11 @@
- `SUPER/SUPER` - Super User - `SUPER/SUPER` - Super User
- `test1/test1` - Super User - `test1/test1` - Super User
- `test3/test3` - User - `test3/test3` - User
## Controls
- Move: WASD
- Jump: Space
- Interact (enter/exit car): E
- Flashlight: F
- Phone: Tab
- Pause menu: Esc
+19 -19
View File
@@ -1,19 +1,19 @@
[remap] [remap]
importer="oggvorbisstr" importer="oggvorbisstr"
type="AudioStreamOggVorbis" type="AudioStreamOggVorbis"
uid="uid://de2e8sy4x724m" uid="uid://de2e8sy4x724m"
path="res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr" path="res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr"
[deps] [deps]
source_file="res://assets/audio/jump.ogg" source_file="res://assets/audio/jump.ogg"
dest_files=["res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr"] dest_files=["res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr"]
[params] [params]
loop=false loop=false
loop_offset=0 loop_offset=0
bpm=0 bpm=0
beat_count=0 beat_count=0
bar_beats=4 bar_beats=4
@@ -1,19 +1,19 @@
[remap] [remap]
importer="oggvorbisstr" importer="oggvorbisstr"
type="AudioStreamOggVorbis" type="AudioStreamOggVorbis"
uid="uid://64dplcgx2icb" uid="uid://64dplcgx2icb"
path="res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr" path="res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"
[deps] [deps]
source_file="res://assets/audio/silly-menu-hover-test.ogg" source_file="res://assets/audio/silly-menu-hover-test.ogg"
dest_files=["res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"] dest_files=["res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"]
[params] [params]
loop=false loop=false
loop_offset=0 loop_offset=0
bpm=0 bpm=0
beat_count=0 beat_count=0
bar_beats=4 bar_beats=4
+19 -19
View File
@@ -1,19 +1,19 @@
[remap] [remap]
importer="oggvorbisstr" importer="oggvorbisstr"
type="AudioStreamOggVorbis" type="AudioStreamOggVorbis"
uid="uid://txgki0ijeuud" uid="uid://txgki0ijeuud"
path="res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr" path="res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr"
[deps] [deps]
source_file="res://assets/audio/silly-test.ogg" source_file="res://assets/audio/silly-test.ogg"
dest_files=["res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr"] dest_files=["res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr"]
[params] [params]
loop=false loop=false
loop_offset=0 loop_offset=0
bpm=0 bpm=0
beat_count=0 beat_count=0
bar_beats=4 bar_beats=4
@@ -1,36 +1,36 @@
[remap] [remap]
importer="font_data_dynamic" importer="font_data_dynamic"
type="FontFile" type="FontFile"
uid="uid://m5ceou0rk6j6" uid="uid://m5ceou0rk6j6"
path="res://.godot/imported/PlayfairDisplay-VariableFont_wght.ttf-fbc765a7962e1c71b0eb2c53d6eb2a10.fontdata" path="res://.godot/imported/PlayfairDisplay-VariableFont_wght.ttf-fbc765a7962e1c71b0eb2c53d6eb2a10.fontdata"
[deps] [deps]
source_file="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" source_file="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf"
dest_files=["res://.godot/imported/PlayfairDisplay-VariableFont_wght.ttf-fbc765a7962e1c71b0eb2c53d6eb2a10.fontdata"] dest_files=["res://.godot/imported/PlayfairDisplay-VariableFont_wght.ttf-fbc765a7962e1c71b0eb2c53d6eb2a10.fontdata"]
[params] [params]
Rendering=null Rendering=null
antialiasing=1 antialiasing=1
generate_mipmaps=false generate_mipmaps=false
disable_embedded_bitmaps=true disable_embedded_bitmaps=true
multichannel_signed_distance_field=false multichannel_signed_distance_field=false
msdf_pixel_range=8 msdf_pixel_range=8
msdf_size=48 msdf_size=48
allow_system_fallback=true allow_system_fallback=true
force_autohinter=false force_autohinter=false
modulate_color_glyphs=false modulate_color_glyphs=false
hinting=1 hinting=1
subpixel_positioning=4 subpixel_positioning=4
keep_rounding_remainders=true keep_rounding_remainders=true
oversampling=0.0 oversampling=0.0
Fallbacks=null Fallbacks=null
fallbacks=[] fallbacks=[]
Compress=null Compress=null
compress=true compress=true
preload=[] preload=[]
language_support={} language_support={}
script_support={} script_support={}
opentype_features={} opentype_features={}
+40 -40
View File
@@ -1,40 +1,40 @@
[remap] [remap]
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dhuosr0p605gj" uid="uid://dhuosr0p605gj"
path="res://.godot/imported/pp_start_bg.png-8fb0f850edd45e79935f992c58fa8ca2.ctex" path="res://.godot/imported/pp_start_bg.png-8fb0f850edd45e79935f992c58fa8ca2.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/images/pp_start_bg.png" source_file="res://assets/images/pp_start_bg.png"
dest_files=["res://.godot/imported/pp_start_bg.png-8fb0f850edd45e79935f992c58fa8ca2.ctex"] dest_files=["res://.godot/imported/pp_start_bg.png-8fb0f850edd45e79935f992c58fa8ca2.ctex"]
[params] [params]
compress/mode=0 compress/mode=0
compress/high_quality=false compress/high_quality=false
compress/lossy_quality=0.7 compress/lossy_quality=0.7
compress/uastc_level=0 compress/uastc_level=0
compress/rdo_quality_loss=0.0 compress/rdo_quality_loss=0.0
compress/hdr_compression=1 compress/hdr_compression=1
compress/normal_map=0 compress/normal_map=0
compress/channel_pack=0 compress/channel_pack=0
mipmaps/generate=false mipmaps/generate=false
mipmaps/limit=-1 mipmaps/limit=-1
roughness/mode=0 roughness/mode=0
roughness/src_normal="" roughness/src_normal=""
process/channel_remap/red=0 process/channel_remap/red=0
process/channel_remap/green=1 process/channel_remap/green=1
process/channel_remap/blue=2 process/channel_remap/blue=2
process/channel_remap/alpha=3 process/channel_remap/alpha=3
process/fix_alpha_border=true process/fix_alpha_border=true
process/premult_alpha=false process/premult_alpha=false
process/normal_map_invert_y=false process/normal_map_invert_y=false
process/hdr_as_srgb=false process/hdr_as_srgb=false
process/hdr_clamp_exposure=false process/hdr_clamp_exposure=false
process/size_limit=0 process/size_limit=0
detect_3d/compress_to=1 detect_3d/compress_to=1
Binary file not shown.
@@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bnqaqbgynoyys"
path="res://.godot/imported/TestCharAnimated.glb-ecf7bb997b01494120b0dfccb4e1f064.scn"
[deps]
source_file="res://assets/models/TestCharAnimated.glb"
dest_files=["res://.godot/imported/TestCharAnimated.glb-ecf7bb997b01494120b0dfccb4e1f064.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
+59 -59
View File
@@ -1,59 +1,59 @@
[remap] [remap]
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bb6hj6l23043x" uid="uid://bb6hj6l23043x"
path="res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn" path="res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn"
[deps] [deps]
source_file="res://assets/models/human.blend" source_file="res://assets/models/human.blend"
dest_files=["res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn"] dest_files=["res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn"]
[params] [params]
nodes/root_type="" nodes/root_type=""
nodes/root_name="" nodes/root_name=""
nodes/root_script=null nodes/root_script=null
nodes/apply_root_scale=true nodes/apply_root_scale=true
nodes/root_scale=1.0 nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true meshes/ensure_tangents=true
meshes/generate_lods=true meshes/generate_lods=true
meshes/create_shadow_meshes=true meshes/create_shadow_meshes=true
meshes/light_baking=1 meshes/light_baking=1
meshes/lightmap_texel_size=0.2 meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false meshes/force_disable_compression=false
skins/use_named_skins=true skins/use_named_skins=true
animation/import=true animation/import=true
animation/fps=30 animation/fps=30
animation/trimming=false animation/trimming=false
animation/remove_immutable_tracks=true animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false animation/import_rest_as_RESET=false
import_script/path="" import_script/path=""
materials/extract=0 materials/extract=0
materials/extract_format=0 materials/extract_format=0
materials/extract_path="" materials/extract_path=""
_subresources={} _subresources={}
blender/nodes/visible=0 blender/nodes/visible=0
blender/nodes/active_collection_only=false blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true blender/nodes/punctual_lights=true
blender/nodes/cameras=true blender/nodes/cameras=true
blender/nodes/custom_properties=true blender/nodes/custom_properties=true
blender/nodes/modifiers=1 blender/nodes/modifiers=1
blender/meshes/colors=false blender/meshes/colors=false
blender/meshes/uvs=true blender/meshes/uvs=true
blender/meshes/normals=true blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true blender/meshes/tangents=true
blender/meshes/skins=2 blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true blender/materials/unpack_enabled=true
blender/materials/export_materials=1 blender/materials/export_materials=1
blender/animation/limit_playback=true blender/animation/limit_playback=true
blender/animation/always_sample=true blender/animation/always_sample=true
blender/animation/group_tracks=true blender/animation/group_tracks=true
gltf/naming_version=2 gltf/naming_version=2
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cpuebxqhtv72i"
path="res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.scn"
[deps]
source_file="res://assets/models/humanFemale.glb"
dest_files=["res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
+43 -43
View File
@@ -1,43 +1,43 @@
[remap] [remap]
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://f2g3tvryiodc" uid="uid://f2g3tvryiodc"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://icon.svg" source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params] [params]
compress/mode=0 compress/mode=0
compress/high_quality=false compress/high_quality=false
compress/lossy_quality=0.7 compress/lossy_quality=0.7
compress/uastc_level=0 compress/uastc_level=0
compress/rdo_quality_loss=0.0 compress/rdo_quality_loss=0.0
compress/hdr_compression=1 compress/hdr_compression=1
compress/normal_map=0 compress/normal_map=0
compress/channel_pack=0 compress/channel_pack=0
mipmaps/generate=false mipmaps/generate=false
mipmaps/limit=-1 mipmaps/limit=-1
roughness/mode=0 roughness/mode=0
roughness/src_normal="" roughness/src_normal=""
process/channel_remap/red=0 process/channel_remap/red=0
process/channel_remap/green=1 process/channel_remap/green=1
process/channel_remap/blue=2 process/channel_remap/blue=2
process/channel_remap/alpha=3 process/channel_remap/alpha=3
process/fix_alpha_border=true process/fix_alpha_border=true
process/premult_alpha=false process/premult_alpha=false
process/normal_map_invert_y=false process/normal_map_invert_y=false
process/hdr_as_srgb=false process/hdr_as_srgb=false
process/hdr_clamp_exposure=false process/hdr_clamp_exposure=false
process/size_limit=0 process/size_limit=0
detect_3d/compress_to=1 detect_3d/compress_to=1
svg/scale=1.0 svg/scale=1.0
editor/scale_with_editor_scale=false editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false editor/convert_colors_with_editor_theme=false
+82 -76
View File
@@ -1,76 +1,82 @@
; Engine configuration file. ; Engine configuration file.
; It's best edited using the editor UI and not directly, ; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious. ; since the parameters that go here are not all obvious.
; ;
; Format: ; Format:
; [section] ; section goes between [] ; [section] ; section goes between []
; param=value ; assign values to parameters ; param=value ; assign values to parameters
config_version=5 config_version=5
[application] [application]
config/name="Promiscuity" config/name="Promiscuity"
run/main_scene="uid://b4k81taauef4q" run/main_scene="uid://b4k81taauef4q"
config/features=PackedStringArray("4.5", "Forward Plus") config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[audio] [audio]
bus_layout="res://audio/bus_layout.tres" bus_layout="res://audio/bus_layout.tres"
[autoload] [autoload]
MenuMusic="*res://scenes/UI/menu_music.tscn" MenuMusic="*res://scenes/UI/menu_music.tscn"
MenuSfx="*res://scenes/UI/menu_sfx.tscn" MenuSfx="*res://scenes/UI/menu_sfx.tscn"
AuthState="*res://scenes/UI/auth_state.gd" AuthState="*res://scenes/UI/auth_state.gd"
CharacterService="*res://scenes/UI/character_service.gd" CharacterService="*res://scenes/UI/character_service.gd"
SelectedCharacter="*res://scenes/UI/selected_character.gd"
[dotnet]
[dotnet]
project/assembly_name="Promiscuity"
project/assembly_name="Promiscuity"
[input]
[input]
ui_left={
"deadzone": 0.5, ui_left={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.5,
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
] , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
} ]
ui_right={ }
"deadzone": 0.5, ui_right={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.5,
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
] , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
} ]
ui_up={ }
"deadzone": 0.5, ui_up={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.5,
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
] , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
} ]
ui_down={ }
"deadzone": 0.5, ui_down={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.5,
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
] , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
} ]
player_light={ }
"deadzone": 0.2, player_light={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.2,
] "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
} ]
player_phone={ }
"deadzone": 0.2, player_phone={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "deadzone": 0.2,
] "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
} ]
}
interact={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
+25 -16
View File
@@ -3,8 +3,9 @@ extends Node3D
@export var left_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeLeft/Pupil") @export var left_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeLeft/Pupil")
@export var right_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeRight/Pupil") @export var right_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeRight/Pupil")
@export var camera_path: NodePath @export var camera_path: NodePath
@export var look_origin_path: NodePath = NodePath("Body/HeadPivot") @export var look_origin_path: NodePath = NodePath("Body/HeadPivot")
@export var look_reference_path: NodePath = NodePath("Body") @export var look_reference_path: NodePath = NodePath("Body")
@export var look_target_path: NodePath = NodePath("")
@export var lock_vertical: bool = true @export var lock_vertical: bool = true
@export var vertical_unlock_height: float = 0.6 @export var vertical_unlock_height: float = 0.6
@export var vertical_lock_smooth_speed: float = 6.0 @export var vertical_lock_smooth_speed: float = 6.0
@@ -28,10 +29,11 @@ var _head: Node3D
var _head_base_rot: Vector3 var _head_base_rot: Vector3
var _vertical_lock_factor: float = 1.0 var _vertical_lock_factor: float = 1.0
var _vertical_hold_timer: float = 0.0 var _vertical_hold_timer: float = 0.0
var _look_reference: Node3D var _look_reference: Node3D
var _look_target: Node3D
func _ready() -> void: func _ready() -> void:
_left_pupil = get_node_or_null(left_pupil_path) as Node3D _left_pupil = get_node_or_null(left_pupil_path) as Node3D
_right_pupil = get_node_or_null(right_pupil_path) as Node3D _right_pupil = get_node_or_null(right_pupil_path) as Node3D
if _left_pupil: if _left_pupil:
@@ -39,9 +41,10 @@ func _ready() -> void:
if _right_pupil: if _right_pupil:
_right_base = _right_pupil.position _right_base = _right_pupil.position
_camera = _resolve_camera() _camera = _resolve_camera()
_look_origin = get_node_or_null(look_origin_path) as Node3D _look_origin = get_node_or_null(look_origin_path) as Node3D
_look_reference = get_node_or_null(look_reference_path) as Node3D _look_reference = get_node_or_null(look_reference_path) as Node3D
_head = get_node_or_null(head_path) as Node3D _look_target = get_node_or_null(look_target_path) as Node3D
_head = get_node_or_null(head_path) as Node3D
if _head: if _head:
_head_base_rot = _head.rotation _head_base_rot = _head.rotation
@@ -54,15 +57,21 @@ func _process(_delta: float) -> void:
_update_pupils() _update_pupils()
func _update_pupils() -> void: func _update_pupils() -> void:
if _camera == null or not _camera.is_inside_tree(): if _look_target == null and look_target_path != NodePath(""):
_camera = _resolve_camera() _look_target = get_node_or_null(look_target_path) as Node3D
if _camera == null: if _look_target == null:
return var viewport_cam := get_viewport().get_camera_3d()
var origin := _look_origin if viewport_cam != null and viewport_cam != _camera:
if origin == null: _camera = viewport_cam
origin = self elif _camera == null or not _camera.is_inside_tree():
var target := _camera.global_position _camera = _resolve_camera()
if _look_target == null and _camera == null:
return
var origin := _look_origin
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 var dir_world := target - origin.global_position
if dir_world.length_squared() <= 0.0001: if dir_world.length_squared() <= 0.0001:
return return
+6 -1
View File
@@ -57,7 +57,12 @@ height = 1.1
[node name="RepoBot" type="Node3D"] [node name="RepoBot" type="Node3D"]
script = ExtResource("1_repo_bot") script = ExtResource("1_repo_bot")
[node name="Body" type="StaticBody3D" parent="."] [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"] [node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.55, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.55, 0)
+301 -170
View File
@@ -1,170 +1,301 @@
[gd_scene load_steps=17 format=3 uid="uid://dchj6g2i8ebph"] [gd_scene load_steps=18 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"]
[ext_resource type="Script" uid="uid://bpxggc8nr6tf6" path="res://scenes/player.gd" id="1_muv8p"] [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://c5of6aaxop1hl" path="res://scenes/block.tscn" id="2_tc7dm"]
[ext_resource type="Script" uid="uid://b7fopt7sx74g8" path="res://scenes/Levels/menu.gd" id="3_tc7dm"] [ext_resource type="Script" uid="uid://b7fopt7sx74g8" path="res://scenes/Levels/menu.gd" id="3_tc7dm"]
[ext_resource type="PackedScene" path="res://scenes/Characters/repo_bot.tscn" id="4_repo"] [ext_resource type="PackedScene" path="res://scenes/Characters/repo_bot.tscn" id="4_repo"]
[ext_resource type="PackedScene" uid="uid://bnqaqbgynoyys" path="res://assets/models/TestCharAnimated.glb" id="5_fi66n"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"] [ext_resource type="PackedScene" path="res://scenes/Vehicles/car.tscn" id="5_car"]
bounce = 0.5
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"]
[sub_resource type="SphereShape3D" id="SphereShape3D_2q6dc"] bounce = 0.5
[sub_resource type="SphereMesh" id="SphereMesh_w7c3h"] [sub_resource type="SphereShape3D" id="SphereShape3D_2q6dc"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_w8frs"] [sub_resource type="SphereMesh" id="SphereMesh_w7c3h"]
bounce = 0.5
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_w8frs"]
[sub_resource type="SphereShape3D" id="SphereShape3D_mx8sn"] bounce = 0.5
[sub_resource type="BoxShape3D" id="BoxShape3D_2q6dc"] [sub_resource type="SphereShape3D" id="SphereShape3D_mx8sn"]
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxShape3D" id="BoxShape3D_2q6dc"]
[sub_resource type="BoxMesh" id="BoxMesh_w7c3h"] size = Vector3(1080, 2, 1080)
size = Vector3(1080, 2, 1080)
[sub_resource type="BoxMesh" id="BoxMesh_w7c3h"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_fi66n"] size = Vector3(1080, 2, 1080)
[sub_resource type="Sky" id="Sky_a4mo8"] [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_fi66n"]
sky_material = SubResource("ProceduralSkyMaterial_fi66n")
[sub_resource type="Sky" id="Sky_a4mo8"]
[sub_resource type="Environment" id="Environment_a4mo8"] sky_material = SubResource("ProceduralSkyMaterial_fi66n")
background_mode = 2
sky = SubResource("Sky_a4mo8") [sub_resource type="Environment" id="Environment_a4mo8"]
ambient_light_source = 3 background_mode = 2
sky = SubResource("Sky_a4mo8")
[node name="Node3D" type="Node3D"] ambient_light_source = 3
script = ExtResource("1_a4mo8")
[node name="Node3D" type="Node3D"]
[node name="human" parent="." instance=ExtResource("1_eg4yq")] script = ExtResource("1_a4mo8")
[node name="RepoBot" parent="." instance=ExtResource("4_repo")] [node name="human" parent="." instance=ExtResource("1_eg4yq")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9426608, 0, -4.4451966)
[node name="RepoBot" parent="." instance=ExtResource("4_repo")]
[node name="Thing" type="RigidBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9426608, 0, -4.4451966)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.7986288) look_target_path = NodePath("../Player")
physics_material_override = SubResource("PhysicsMaterial_2q6dc")
gravity_scale = 0.0 [node name="Thing" type="RigidBody3D" parent="."]
contact_monitor = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.7986288)
max_contacts_reported = 5 physics_material_override = SubResource("PhysicsMaterial_2q6dc")
gravity_scale = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="Thing"] contact_monitor = true
shape = SubResource("SphereShape3D_2q6dc") max_contacts_reported = 5
debug_color = Color(0.29772994, 0.6216631, 0.28140613, 0.41960785)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Thing"]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Thing"] shape = SubResource("SphereShape3D_2q6dc")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) debug_color = Color(0.29772994, 0.6216631, 0.28140613, 0.41960785)
mesh = SubResource("SphereMesh_w7c3h")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Thing"]
[node name="Player" type="RigidBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
physics_material_override = SubResource("PhysicsMaterial_w8frs") mesh = SubResource("SphereMesh_w7c3h")
script = ExtResource("1_muv8p")
camera_path = NodePath("Camera3D") [node name="Player" type="RigidBody3D" parent="."]
phone_path = NodePath("../PhoneUI") physics_material_override = SubResource("PhysicsMaterial_w8frs")
script = ExtResource("1_muv8p")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"] camera_path = NodePath("Camera3D")
shape = SubResource("SphereShape3D_mx8sn") phone_path = NodePath("../PhoneUI")
[node name="Camera3D" type="Camera3D" parent="Player"] [node name="TestCharAnimated" parent="Player" instance=ExtResource("5_fi66n")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.31670225, 0) transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0)
current = true
[node name="Skeleton3D" parent="Player/TestCharAnimated/Armature" index="0"]
[node name="SpotLight3D" type="SpotLight3D" parent="Player"] bones/0/position = Vector3(0.0026170756, -0.0012418391, -0.98996276)
bones/0/rotation = Quaternion(-0.7278173, -0.05346041, 0.012412205, 0.6835714)
[node name="Ground" type="StaticBody3D" parent="."] bones/1/position = Vector3(8.593209e-06, 0.09923458, -0.012273359)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) bones/1/rotation = Quaternion(-0.031655744, 0.011362745, 0.022744056, 0.9991754)
bones/2/position = Vector3(-2.2989244e-10, 0.117319785, 2.9730852e-08)
[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"] bones/2/rotation = Quaternion(0.010291794, 0.001169461, 0.013815534, 0.9998509)
shape = SubResource("BoxShape3D_2q6dc") bones/3/position = Vector3(-2.3358266e-09, 0.13458829, -1.382244e-08)
bones/3/rotation = Quaternion(0.067557335, 0.0021622085, 0.013776206, 0.99761796)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"] bones/4/position = Vector3(2.508626e-07, 0.15027755, 0.008779066)
mesh = SubResource("BoxMesh_w7c3h") bones/4/rotation = Quaternion(0.051882792, -0.00090027065, 0.0039363643, 0.998645)
bones/5/position = Vector3(2.152588e-08, 0.103218146, 0.03142428)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] bones/5/rotation = Quaternion(-0.06595954, 0.011917545, -0.0014885183, 0.99775004)
transform = Transform3D(1, 0, 0, 0, 0.5, 0.8660253, 0, -0.8660253, 0.5, 0, 34, 0) bones/6/position = Vector3(1.5477327e-06, 0.18474667, 0.06636399)
shadow_enabled = true bones/7/position = Vector3(0.061058242, 0.09106279, 0.0075706206)
bones/7/rotation = Quaternion(-0.5982132, -0.43452233, 0.50765175, -0.4422909)
[node name="Starter Blocks" type="Node3D" parent="."] bones/8/position = Vector3(1.1842036e-08, 0.12922287, 5.2578955e-08)
bones/8/rotation = Quaternion(0.5434686, 0.18407291, 0.20301422, 0.7934383)
[node name="Block" parent="Starter Blocks" instance=ExtResource("2_tc7dm")] bones/9/position = Vector3(1.5054144e-07, 0.2740467, 2.2048344e-08)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.298158, -7.0724635) bones/9/rotation = Quaternion(-5.192343e-09, -1.1239315e-06, 0.01273731, 0.99991894)
bones/10/position = Vector3(1.9432857e-08, 0.27614468, 9.6609995e-08)
[node name="Block2" parent="Starter Blocks" instance=ExtResource("2_tc7dm")] bones/10/rotation = Quaternion(0.048323337, -0.28487536, 0.026756868, 0.9569718)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.63255787, 2.596316, -6.980046) bones/11/position = Vector3(-0.030029751, 0.037888147, 0.021671427)
bones/11/rotation = Quaternion(0.2098604, -0.059559863, 0.20750383, 0.9536002)
[node name="Menu" type="CanvasLayer" parent="."] bones/12/position = Vector3(-3.3527822e-08, 0.0474497, -1.48403245e-08)
process_mode = 3 bones/12/rotation = Quaternion(-0.14269812, 0.08661232, -0.24053419, 0.9561793)
visible = false bones/13/position = Vector3(-9.313215e-09, 0.043821093, 1.7813335e-08)
script = ExtResource("3_tc7dm") bones/13/rotation = Quaternion(-0.121022485, -0.07554186, 0.024044419, 0.98947906)
bones/14/position = Vector3(-4.847594e-08, 0.034590863, -5.3379924e-08)
[node name="PhoneUI" type="CanvasLayer" parent="."] bones/15/position = Vector3(-0.02822038, 0.12266627, 0.0023182186)
layer = 5 bones/15/rotation = Quaternion(0.06621808, 0.00073213095, 0.008435749, 0.99776924)
visible = false bones/16/position = Vector3(-8.136956e-09, 0.03891963, -1.4837047e-08)
bones/16/rotation = Quaternion(0.1509963, 7.805282e-07, -0.015479834, 0.98841316)
[node name="Control" type="Control" parent="PhoneUI"] bones/17/position = Vector3(7.499967e-09, 0.034151517, -5.4443618e-08)
layout_mode = 3 bones/17/rotation = Quaternion(0.070082076, 7.71772e-07, -0.007181638, 0.99751544)
anchors_preset = 15 bones/18/position = Vector3(2.700483e-09, 0.030779874, 4.9589467e-08)
anchor_right = 1.0 bones/19/position = Vector3(-1.2452401e-07, 0.12775533, -8.01722e-08)
anchor_bottom = 1.0 bones/19/rotation = Quaternion(0.13653132, -0.0038339633, -0.022952663, 0.9903624)
grow_horizontal = 2 bones/20/position = Vector3(2.5990463e-09, 0.036139663, 1.713758e-10)
grow_vertical = 2 bones/20/rotation = Quaternion(0.15125485, 1.1823237e-06, -0.015500045, 0.9883733)
bones/21/position = Vector3(2.9034163e-07, 0.034597658, -3.6051716e-08)
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/Control"] bones/21/rotation = Quaternion(0.1599333, 1.4124467e-06, -0.016389191, 0.98699176)
layout_mode = 1 bones/22/position = Vector3(-3.0519334e-07, 0.03680197, -9.1170705e-08)
anchors_preset = 8 bones/23/position = Vector3(0.022166358, 0.12147022, -9.993157e-05)
anchor_left = 0.5 bones/23/rotation = Quaternion(0.18325116, -0.004651328, -0.031451236, 0.9825519)
anchor_top = 0.5 bones/24/position = Vector3(4.5585904e-09, 0.036011815, -8.07135e-09)
anchor_right = 0.5 bones/24/rotation = Quaternion(0.18138836, 1.5675009e-06, -0.018595627, 0.9832358)
anchor_bottom = 0.5 bones/25/position = Vector3(-9.647604e-09, 0.033073086, -1.46341534e-08)
offset_left = -180.0 bones/25/rotation = Quaternion(0.1552087, 2.2643005e-06, -0.015905593, 0.9877537)
offset_top = -320.0 bones/26/position = Vector3(-2.434721e-07, 0.03660111, 2.471306e-07)
offset_right = 180.0 bones/27/position = Vector3(0.04725836, 0.109081924, 0.0022613404)
offset_bottom = 320.0 bones/27/rotation = Quaternion(0.23387046, -0.0035344863, -0.048872076, 0.97103226)
grow_horizontal = 2 bones/28/position = Vector3(4.3135207e-09, 0.041366525, 3.569472e-08)
grow_vertical = 2 bones/28/rotation = Quaternion(0.15124865, 1.4340599e-05, -0.015863812, 0.98836845)
color = Color(0.08, 0.08, 0.1, 1) bones/29/position = Vector3(1.195453e-08, 0.02594832, 1.5542597e-08)
bones/29/rotation = Quaternion(0.14350323, 3.322942e-05, -0.014478063, 0.98954403)
[node name="Control" type="Control" parent="Menu"] bones/30/position = Vector3(-2.4944999e-08, 0.029238665, 6.2325825e-08)
layout_mode = 3 bones/31/position = Vector3(-0.06105696, 0.09106397, 0.007570758)
anchors_preset = 15 bones/31/rotation = Quaternion(0.60009927, -0.4346891, 0.52418536, 0.4197095)
anchor_right = 1.0 bones/32/position = Vector3(2.116817e-08, 0.12922288, 7.016769e-08)
anchor_bottom = 1.0 bones/32/rotation = Quaternion(0.46432823, -0.26300937, -0.23269261, 0.813068)
grow_horizontal = 2 bones/33/position = Vector3(-2.350565e-08, 0.27404687, 1.5161348e-08)
grow_vertical = 2 bones/33/rotation = Quaternion(7.667138e-08, -5.0620065e-06, -0.058376648, 0.99829465)
size_flags_horizontal = 4 bones/34/position = Vector3(1.3629875e-07, 0.27614468, -9.158248e-09)
size_flags_vertical = 4 bones/34/rotation = Quaternion(0.059040155, 0.17509013, -0.005968382, 0.9827626)
bones/35/position = Vector3(0.030029776, 0.03788806, 0.021671649)
[node name="VBoxContainer" type="VBoxContainer" parent="Menu/Control"] bones/35/rotation = Quaternion(0.21378386, 0.065372065, -0.22564878, 0.94821185)
layout_mode = 1 bones/36/position = Vector3(-2.7939748e-09, 0.04744962, -1.7240383e-08)
anchors_preset = 8 bones/36/rotation = Quaternion(-0.14410053, -0.076146945, 0.2772088, 0.9468854)
anchor_left = 0.5 bones/37/position = Vector3(5.58785e-09, 0.04382136, 3.566345e-08)
anchor_top = 0.5 bones/37/rotation = Quaternion(-0.1552004, 0.07467698, -0.026057797, 0.9847117)
anchor_right = 0.5 bones/38/position = Vector3(8.2187e-08, 0.034590647, -2.4929412e-07)
anchor_bottom = 0.5 bones/39/position = Vector3(0.028220426, 0.1226661, 0.0023184721)
offset_left = -39.5 bones/39/rotation = Quaternion(0.059513006, -0.0009918809, -0.010980465, 0.99816674)
offset_top = -33.0 bones/40/position = Vector3(-1.1795999e-08, 0.038919587, -4.9162594e-09)
offset_right = 39.5 bones/40/rotation = Quaternion(0.15128906, 2.1640317e-06, 0.012328284, 0.98841274)
offset_bottom = 33.0 bones/41/position = Vector3(-1.4053852e-07, 0.034151636, -1.1118345e-08)
grow_horizontal = 2 bones/41/rotation = Quaternion(0.07021882, 5.966413e-07, 0.0057199914, 0.9975152)
grow_vertical = 2 bones/42/position = Vector3(-7.1266735e-09, 0.030779934, 3.7720538e-10)
bones/43/position = Vector3(1.69263e-07, 0.12775521, 2.5128352e-07)
[node name="ContinueButton" type="Button" parent="Menu/Control/VBoxContainer"] bones/43/rotation = Quaternion(0.13051407, 0.003691554, 0.018921439, 0.99125904)
layout_mode = 2 bones/44/position = Vector3(4.8907918e-09, 0.036139727, -8.499214e-09)
text = "Continue" bones/44/rotation = Quaternion(0.15154669, 1.6962191e-06, 0.012345144, 0.988373)
bones/45/position = Vector3(-2.8403383e-07, 0.03459763, -4.4995545e-09)
[node name="MainMenuButton" type="Button" parent="Menu/Control/VBoxContainer"] bones/45/rotation = Quaternion(0.16023898, 1.233635e-06, 0.013053549, 0.98699194)
layout_mode = 2 bones/46/position = Vector3(2.8485744e-07, 0.03680191, -9.493007e-09)
text = "Main Menu" bones/47/position = Vector3(-0.022166327, 0.12146996, -9.982591e-05)
bones/47/rotation = Quaternion(0.1774532, 0.0045050457, 0.02645378, 0.98376334)
[node name="QuitButton" type="Button" parent="Menu/Control/VBoxContainer"] bones/48/position = Vector3(4.6136024e-09, 0.036011953, 3.056637e-08)
layout_mode = 2 bones/48/rotation = Quaternion(0.18173504, 1.8501213e-06, 0.014812392, 0.98323596)
text = "Quit" bones/49/position = Vector3(-3.407626e-08, 0.033073153, 1.8664657e-08)
bones/49/rotation = Quaternion(0.15551038, 3.26233e-07, 0.012668259, 0.98775303)
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] bones/50/position = Vector3(2.3113911e-07, 0.03660121, -1.1142021e-08)
environment = SubResource("Environment_a4mo8") bones/51/position = Vector3(-0.047258336, 0.109081805, 0.0022614468)
bones/51/rotation = Quaternion(0.22849467, 0.003407588, 0.042977322, 0.97259015)
[connection signal="pressed" from="Menu/Control/VBoxContainer/ContinueButton" to="Menu" method="_on_continue_button_pressed"] bones/52/position = Vector3(-1.5261804e-08, 0.041366428, -1.9607107e-09)
[connection signal="pressed" from="Menu/Control/VBoxContainer/MainMenuButton" to="Menu" method="_on_main_menu_button_pressed"] bones/52/rotation = Quaternion(0.1515478, 7.4422354e-07, 0.01235583, 0.98837274)
[connection signal="pressed" from="Menu/Control/VBoxContainer/QuitButton" to="Menu" method="_on_quit_button_pressed"] bones/53/position = Vector3(1.4340932e-08, 0.025948457, 3.3946527e-08)
bones/53/rotation = Quaternion(0.14377311, 5.8452275e-07, 0.011701506, 0.98954153)
bones/54/position = Vector3(4.750415e-09, 0.029238641, -1.2388978e-09)
bones/55/position = Vector3(0.09123873, -0.06657194, -0.0005540352)
bones/55/rotation = Quaternion(0.13601132, 0.07838244, 0.9844897, -0.07834015)
bones/56/position = Vector3(-1.3288446e-09, 0.40599436, 1.0443015e-08)
bones/56/rotation = Quaternion(-0.13252214, -0.014519276, 0.03215593, 0.99055195)
bones/57/position = Vector3(5.707578e-09, 0.42099008, -1.5425995e-08)
bones/57/rotation = Quaternion(0.5344839, -0.021701135, -0.027458949, 0.8444537)
bones/58/position = Vector3(-6.5650525e-09, 0.15721555, -2.6694579e-08)
bones/58/rotation = Quaternion(0.2693438, -0.032380052, -0.014592491, 0.96238893)
bones/59/position = Vector3(2.4854705e-08, 0.09999996, -3.1755625e-09)
bones/60/position = Vector3(-0.091250315, -0.066556, -0.0005535231)
bones/60/rotation = Quaternion(-0.09026457, -0.016758345, 0.9954146, -0.026857648)
bones/61/position = Vector3(1.3129254e-08, 0.40599442, 3.8557886e-09)
bones/61/rotation = Quaternion(-0.07401098, 0.084030755, -0.024547135, 0.99340767)
bones/62/position = Vector3(-7.3885964e-10, 0.42099023, -5.226429e-09)
bones/62/rotation = Quaternion(0.56540585, 0.011310213, -0.008501466, 0.8246915)
bones/63/position = Vector3(7.93632e-09, 0.1572156, -2.6982683e-10)
bones/63/rotation = Quaternion(0.28907102, 0.031904068, 0.014082117, 0.9566723)
bones/64/position = Vector3(7.567915e-10, 0.099999994, -3.2595668e-09)
[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_mx8sn")
[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"]
[node name="Car" parent="." instance=ExtResource("5_car")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -3)
[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_2q6dc")
[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_w7c3h")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.5, 0.8660253, 0, -0.8660253, 0.5, 0, 34, 0)
shadow_enabled = true
[node name="Starter Blocks" type="Node3D" parent="."]
[node name="Block" parent="Starter Blocks" instance=ExtResource("2_tc7dm")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.298158, -7.0724635)
[node name="Block2" parent="Starter Blocks" instance=ExtResource("2_tc7dm")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.63255787, 2.596316, -6.980046)
[node name="Menu" type="CanvasLayer" parent="."]
process_mode = 3
visible = false
script = ExtResource("3_tc7dm")
[node name="Control" type="Control" parent="Menu"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
size_flags_vertical = 4
[node name="VBoxContainer" type="VBoxContainer" parent="Menu/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 = -39.5
offset_top = -33.0
offset_right = 39.5
offset_bottom = 33.0
grow_horizontal = 2
grow_vertical = 2
[node name="ContinueButton" type="Button" parent="Menu/Control/VBoxContainer"]
layout_mode = 2
text = "Continue"
[node name="MainMenuButton" type="Button" parent="Menu/Control/VBoxContainer"]
layout_mode = 2
text = "Main Menu"
[node name="QuitButton" type="Button" parent="Menu/Control/VBoxContainer"]
layout_mode = 2
text = "Quit"
[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="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_a4mo8")
[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"]
[editable path="Player/TestCharAnimated"]
+15
View File
@@ -0,0 +1,15 @@
extends Node3D
@export var tile_size := 4.0
@export var block_height := 1.0
@onready var _block: MeshInstance3D = $TerrainBlock
@onready var _camera: Camera3D = $Camera3D
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)
if _camera:
_camera.look_at(block_pos, Vector3.UP)
+1
View File
@@ -0,0 +1 @@
uid://1fico5npv6dy
+23
View File
@@ -0,0 +1,23 @@
[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
+12 -9
View File
@@ -14,13 +14,15 @@ func _input(event):
else: else:
pause_game() pause_game()
func pause_game(): func pause_game():
get_tree().paused = true get_tree().paused = true
visible = true visible = true
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
func resume_game(): func resume_game():
get_tree().paused = false get_tree().paused = false
visible = false visible = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _on_quit_button_pressed(): func _on_quit_button_pressed():
get_tree().quit() get_tree().quit()
@@ -28,9 +30,10 @@ func _on_quit_button_pressed():
func _on_continue_button_pressed(): func _on_continue_button_pressed():
resume_game() resume_game()
func _on_main_menu_button_pressed(): func _on_main_menu_button_pressed():
resume_game() resume_game()
get_tree().change_scene_to_file(START_SCREEN_SCENE) Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
get_tree().change_scene_to_file(START_SCREEN_SCENE)
func _register_focus_sounds() -> void: func _register_focus_sounds() -> void:
if pause_menu == null: if pause_menu == null:
+22 -7
View File
@@ -69,11 +69,11 @@ func _on_add_button_pressed() -> void:
else: else:
_status_label.text = "Character created, but response was unexpected." _status_label.text = "Character created, but response was unexpected."
func _on_delete_button_pressed() -> void: func _on_delete_button_pressed() -> void:
var selected := _character_list.get_selected_items() var selected := _character_list.get_selected_items()
if selected.is_empty(): if selected.is_empty():
_status_label.text = "Select a character to delete." _status_label.text = "Select a character to delete."
return return
var index := selected[0] var index := selected[0]
if index < 0 or index >= _characters.size(): if index < 0 or index >= _characters.size():
@@ -94,8 +94,23 @@ func _on_delete_button_pressed() -> void:
return return
_characters.remove_at(index) _characters.remove_at(index)
_character_list.remove_item(index) _character_list.remove_item(index)
_status_label.text = "Character deleted." _status_label.text = "Character deleted."
func _on_select_button_pressed() -> void:
var selected := _character_list.get_selected_items()
if selected.is_empty():
_status_label.text = "Select a character first."
return
var index := selected[0]
if index < 0 or index >= _characters.size():
_status_label.text = "Invalid selection."
return
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: func _on_refresh_button_pressed() -> void:
_load_characters() _load_characters()
+23 -15
View File
@@ -87,16 +87,23 @@ theme = ExtResource("4_5b3b7")
text = "ADD" text = "ADD"
text_alignment = 1 text_alignment = 1
[node name="ActionHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"] [node name="ActionHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 4 size_flags_horizontal = 4
theme_override_constants/separation = 10 theme_override_constants/separation = 10
[node name="RefreshButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"] [node name="SelectButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 4 size_flags_horizontal = 4
theme = ExtResource("4_5b3b7") theme = ExtResource("4_5b3b7")
text = "REFRESH" text = "SELECT"
text_alignment = 1
[node name="RefreshButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
layout_mode = 2
size_flags_horizontal = 4
theme = ExtResource("4_5b3b7")
text = "REFRESH"
text_alignment = 1 text_alignment = 1
[node name="DeleteButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"] [node name="DeleteButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
@@ -123,9 +130,10 @@ text_alignment = 1
[node name="LogoutRequest" type="HTTPRequest" parent="."] [node name="LogoutRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true 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/AddHBox/AddButton" to="." method="_on_add_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/SelectButton" to="." method="_on_select_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/RefreshButton" to="." method="_on_refresh_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/DeleteButton" to="." method="_on_delete_button_pressed"]
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/LogoutButton" to="." method="_on_logout_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"]
+16
View File
@@ -0,0 +1,16 @@
extends Node
var character: Dictionary = {}
func set_character(data: Dictionary) -> void:
character = data
func clear() -> void:
character = {}
func get_coord() -> Vector2:
var coord: Dictionary = character.get("coord", {})
return Vector2(
float(coord.get("x", 0)),
float(coord.get("y", 0))
)
+1
View File
@@ -0,0 +1 @@
uid://bbl0our5y4k7a
+105
View File
@@ -0,0 +1,105 @@
extends RigidBody3D
@export var drive_speed := 18.0
@export var drive_accel := 20.0
@export var brake_strength := 28.0
@export var turn_speed := 2.0
@export var turn_accel := 8.0
@export var lateral_damp := 10.0
@export var launch_impulse := 28.0
@export var launch_up_impulse := 6.0
@export var seat_path: NodePath
@export var exit_path: NodePath
@export var camera_path: NodePath
@export var interact_area_path: NodePath
@onready var seat: Node3D = get_node(seat_path) if seat_path != NodePath("") else null
@onready var exit_point: Node3D = get_node(exit_path) if exit_path != NodePath("") else null
@onready var car_camera: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
@onready var interact_area: Area3D = get_node(interact_area_path) if interact_area_path != NodePath("") else null
var _nearby_driver: Node = null
var _driver: Node = null
func _ready() -> void:
if interact_area:
interact_area.collision_layer = 2
interact_area.collision_mask = 1
interact_area.body_entered.connect(_on_interact_body_entered)
interact_area.body_exited.connect(_on_interact_body_exited)
if car_camera:
car_camera.current = false
contact_monitor = true
max_contacts_reported = 8
func _process(_delta: float) -> void:
if _driver == null and _nearby_driver != null and Input.is_action_just_pressed("interact"):
_enter_vehicle(_nearby_driver)
elif _driver != null and Input.is_action_just_pressed("interact"):
_exit_vehicle()
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
if _driver == null:
return
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var forward := global_transform.basis.z
forward.y = 0.0
forward = forward.normalized()
var current_speed := linear_velocity.dot(forward)
var target_speed := input2v.y * drive_speed
var accel := drive_accel if abs(input2v.y) > 0.01 else brake_strength
var new_forward_speed := move_toward(current_speed, target_speed, accel * state.step)
var forward_vel := forward * new_forward_speed
var lateral_vel := linear_velocity - (forward * current_speed)
lateral_vel = lateral_vel.move_toward(Vector3.ZERO, lateral_damp * state.step)
linear_velocity = forward_vel + lateral_vel
var speed_factor: float = clamp(abs(new_forward_speed) / drive_speed, 0.0, 1.0)
var turn_input := input2v.x
if new_forward_speed < -0.1:
turn_input = -turn_input
var target_turn: float = turn_input * turn_speed * speed_factor
angular_velocity.y = move_toward(angular_velocity.y, target_turn, turn_accel * state.step)
if speed_factor > 0.2:
var hit_ids := {}
for i in state.get_contact_count():
var collider := state.get_contact_collider_object(i)
if collider is RigidBody3D and collider != self:
var collider_id := (collider as Node).get_instance_id()
if hit_ids.has(collider_id):
continue
hit_ids[collider_id] = true
var contact_pos := state.get_contact_collider_position(i)
var launch_dir := (contact_pos - global_position).normalized()
if launch_dir.length() <= 0.001:
var normal_world := (global_transform.basis * state.get_contact_local_normal(i)).normalized()
launch_dir = normal_world if normal_world.length() > 0.001 else forward.normalized()
var impulse := launch_dir * (launch_impulse * speed_factor) + Vector3.UP * launch_up_impulse
(collider as RigidBody3D).apply_central_impulse(impulse)
func _enter_vehicle(player: Node) -> void:
if seat == null:
return
_driver = player
player.call("enter_vehicle", self, seat, car_camera)
if car_camera:
car_camera.current = true
func _exit_vehicle() -> void:
if _driver == null:
return
_driver.call("exit_vehicle", exit_point, car_camera)
_driver = null
if car_camera:
car_camera.current = false
func _on_interact_body_entered(body: Node) -> void:
if body.has_method("enter_vehicle"):
_nearby_driver = body
func _on_interact_body_exited(body: Node) -> void:
if body == _nearby_driver:
_nearby_driver = null
+1
View File
@@ -0,0 +1 @@
uid://4qf5yinepytc
+43
View File
@@ -0,0 +1,43 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://scenes/Vehicles/car.gd" id="1_kbd20"]
[sub_resource type="BoxShape3D" id="BoxShape3D_7r1j6"]
size = Vector3(1.4, 0.9, 2.6)
[sub_resource type="BoxShape3D" id="BoxShape3D_jk0m1"]
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")
exit_path = NodePath("Exit")
camera_path = NodePath("CarCamera")
interact_area_path = NodePath("InteractArea")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_7r1j6")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_4y8xk")
surface_material_override/0 = SubResource("StandardMaterial3D_red")
[node name="InteractArea" type="Area3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="InteractArea"]
shape = SubResource("BoxShape3D_jk0m1")
[node name="Seat" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0, 0.6, 0.0)
[node name="Exit" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3, 0.0, 0.0)
[node name="CarCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0, 2.0, 4.2)
+246 -154
View File
@@ -1,154 +1,246 @@
extends RigidBody3D extends RigidBody3D
# Initially I used a CharacterBody3D, however, I wanted the player to bounce off # 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 # other objects in the environment and that would have required manual handling
# of collisions. So that's why we're using a RigidBody3D instead. # of collisions. So that's why we're using a RigidBody3D instead.
const MOVE_SPEED := 8.0 const MOVE_SPEED := 8.0
const ACCELLERATION := 30.0 const SPRINT_MOVE_SPEED :=13
const DECELLERATION := 40.0 const ACCELLERATION := 30.0
const JUMP_SPEED := 4.0 const DECELLERATION := 40.0
const MAX_NUMBER_OF_JUMPS := 2 const JUMP_SPEED := 4.0
const MAX_NUMBER_OF_JUMPS := 2
const MIN_FOV := 10 const MIN_FOV := 10
const MAX_FOV := 180 const MAX_FOV := 180
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1 const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
var mouse_sensitivity := 0.005 var mouse_sensitivity := 0.005
var rotation_x := 0.0 var rotation_x := 0.0
var rotation_y := 0.0 var rotation_y := 0.0
var cameraMoveMode := false var cameraMoveMode := false
var current_number_of_jumps := 0 var current_number_of_jumps := 0
var _pending_mouse_delta := Vector2.ZERO var _pending_mouse_delta := Vector2.ZERO
var _last_move_forward := Vector3(0, 0, 1) var _last_move_forward := Vector3(0, 0, 1)
var _last_move_right := Vector3(1, 0, 0) var _last_move_right := Vector3(1, 0, 0)
var _camera_offset_local := Vector3.ZERO var _camera_offset_local := Vector3.ZERO
var _camera_yaw := 0.0 var _camera_yaw := 0.0
var _camera_pitch := 0.0 var _camera_pitch := 0.0
var _in_vehicle := false
@export var camera_follow_speed := 10.0 var _vehicle_collision_layer := 0
var _vehicle_collision_mask := 0
var jump_sound = preload("res://assets/audio/jump.ogg") var _vehicle_original_parent: Node = null
var audio_player = AudioStreamPlayer.new() var _light_was_on := false
var _jump_triggered := false
@export var camera_path: NodePath @onready var _flashlight: SpotLight3D = $SpotLight3D
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null @onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
@export var phone_path: NodePath @onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
var phone_visible := false @export var camera_follow_speed := 10.0
@export var anim_idle_name := "Idle"
func _ready() -> void: @export var anim_walk_name := "Walk"
axis_lock_angular_x = true @export var anim_jump_name := "Jump"
axis_lock_angular_z = true @export var anim_run_name := "Run"
angular_damp = 6.0 @export var anim_walk_speed_threshold := 0.25
contact_monitor = true @export var anim_sprint_speed_threshold := 10.0
max_contacts_reported = 4
add_child(audio_player) var jump_sound = preload("res://assets/audio/jump.ogg")
audio_player.stream = jump_sound var audio_player = AudioStreamPlayer.new()
audio_player.volume_db = -20
if cam: @export var camera_path: NodePath
_camera_offset_local = cam.transform.origin @onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
_camera_pitch = cam.rotation.x @export var phone_path: NodePath
_camera_yaw = global_transform.basis.get_euler().y @onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
cam.set_as_top_level(true) var phone_visible := false
cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) func _ready() -> void:
var move_basis := cam.global_transform.basis if cam else global_transform.basis axis_lock_angular_x = true
var forward := move_basis.z axis_lock_angular_z = true
var right := move_basis.x angular_damp = 6.0
forward.y = 0.0 contact_monitor = true
right.y = 0.0 max_contacts_reported = 4
if forward.length() > 0.0001: add_child(audio_player)
_last_move_forward = forward.normalized() audio_player.stream = jump_sound
if right.length() > 0.0001: audio_player.volume_db = -20
_last_move_right = right.normalized() if cam:
_camera_offset_local = cam.transform.origin
func _integrate_forces(state): _camera_pitch = cam.rotation.x
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO: _camera_yaw = global_transform.basis.get_euler().y
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity cam.set_as_top_level(true)
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) # Prevent flipping cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
_camera_pitch = rotation_x var move_basis := cam.global_transform.basis if cam else global_transform.basis
rotation.y = rotation_y var forward := move_basis.z
_pending_mouse_delta = Vector2.ZERO var right := move_basis.x
forward.y = 0.0
# Input as 2D vector right.y = 0.0
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") if forward.length() > 0.0001:
_last_move_forward = forward.normalized()
if Input.is_action_just_pressed("player_phone"): if right.length() > 0.0001:
phone_visible = !phone_visible _last_move_right = right.normalized()
if phone: _vehicle_collision_layer = collision_layer
phone.visible = phone_visible _vehicle_collision_mask = collision_mask
# Camera based movement func _integrate_forces(state):
var forward := Vector3.FORWARD * -1.0 if _in_vehicle:
var right := Vector3.RIGHT linear_velocity = Vector3.ZERO
if cam: return
forward = cam.global_transform.basis.z if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
right = cam.global_transform.basis.x rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
# Project onto ground plane so looking up/down doesn't kill movement. rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
forward.y = 0.0 rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) # Prevent flipping
right.y = 0.0 _camera_pitch = rotation_x
if forward.length() > 0.0001: rotation.y = rotation_y
forward = forward.normalized() _pending_mouse_delta = Vector2.ZERO
_last_move_forward = forward
else: # Input as 2D vector
forward = _last_move_forward var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if right.length() > 0.0001:
right = right.normalized() if Input.is_action_just_pressed("player_phone"):
_last_move_right = right phone_visible = !phone_visible
else: if phone:
right = _last_move_right phone.visible = phone_visible
var dir := (right * input2v.x + forward * input2v.y).normalized() # Camera based movement
var target_v := dir * MOVE_SPEED var forward := Vector3.FORWARD * -1.0
var right := Vector3.RIGHT
var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION if cam:
linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step) forward = cam.global_transform.basis.z
linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step) right = cam.global_transform.basis.x
# Project onto ground plane so looking up/down doesn't kill movement.
# Jump Logic forward.y = 0.0
var on_floor = false right.y = 0.0
for i in state.get_contact_count(): if forward.length() > 0.0001:
var normal = state.get_contact_local_normal(i) forward = forward.normalized()
if normal.y > 0.5: _last_move_forward = forward
on_floor = true else:
break forward = _last_move_forward
if right.length() > 0.0001:
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1): right = right.normalized()
current_number_of_jumps = (current_number_of_jumps + 1) % 2 _last_move_right = right
linear_velocity.y = JUMP_SPEED else:
audio_player.play() right = _last_move_right
if cam: var dir := (right * input2v.x + forward * input2v.y).normalized()
var target_yaw := global_transform.basis.get_euler().y
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step) var target_v := dir * MOVE_SPEED
var target_basis := Basis(Vector3.UP, _camera_yaw)
var target_pos := global_position + (target_basis * _camera_offset_local) # Sprinting
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step) if Input.is_key_pressed(KEY_SHIFT):
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) target_v = dir * SPRINT_MOVE_SPEED
func _input(event): var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
if event is InputEventMouseButton: linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
if event.button_index == MOUSE_BUTTON_MIDDLE: linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
if event.pressed:
cameraMoveMode = true # Jump Logic
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) var on_floor = false
else: for i in state.get_contact_count():
cameraMoveMode = false var normal = state.get_contact_local_normal(i)
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) if normal.y > 0.5:
on_floor = true
if event is InputEventMouseMotion and cameraMoveMode: break
_pending_mouse_delta += event.relative
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
if event is InputEventMouseButton and event.pressed: current_number_of_jumps = (current_number_of_jumps + 1) % 2
if event.button_index == MOUSE_BUTTON_WHEEL_UP: linear_velocity.y = JUMP_SPEED
zoom_camera(1.0 / ZOOM_FACTOR) # Zoom in audio_player.play()
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: _jump_triggered = true
zoom_camera(ZOOM_FACTOR) # Zoom out
if cam:
if event.is_action_pressed("player_light"): var target_yaw := global_transform.basis.get_euler().y
$SpotLight3D.visible = !$SpotLight3D.visible _camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
var target_basis := Basis(Vector3.UP, _camera_yaw)
func zoom_camera(factor): var target_pos := global_position + (target_basis * _camera_offset_local)
var new_fov = cam.fov * factor cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
_update_animation(on_floor, state.linear_velocity)
_jump_triggered = false
func _input(event):
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) # Zoom in
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_camera(ZOOM_FACTOR) # Zoom out
if event.is_action_pressed("player_light"):
_flashlight.visible = !_flashlight.visible
func zoom_camera(factor):
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_walk_name:
_anim_player.play(anim_walk_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
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
@@ -28,12 +28,13 @@ public class CharactersController : ControllerBase
if (string.IsNullOrWhiteSpace(userId)) if (string.IsNullOrWhiteSpace(userId))
return Unauthorized(); return Unauthorized();
var character = new Character var character = new Character
{ {
OwnerUserId = userId, OwnerUserId = userId,
Name = req.Name.Trim(), Name = req.Name.Trim(),
CreatedUtc = DateTime.UtcNow Coord = new Coord { X = 0, Y = 0 },
}; CreatedUtc = DateTime.UtcNow
};
await _characters.CreateAsync(character); await _characters.CreateAsync(character);
return Ok(character); return Ok(character);
+27 -23
View File
@@ -1,23 +1,27 @@
# CharacterApi document shapes # CharacterApi document shapes
This service expects JSON request bodies for character creation and stores This service expects JSON request bodies for character creation and stores
character documents in MongoDB. character documents in MongoDB.
Inbound JSON documents Inbound JSON documents
- CreateCharacterRequest (`POST /api/characters`) - CreateCharacterRequest (`POST /api/characters`)
```json ```json
{ {
"name": "string" "name": "string"
} }
``` ```
Stored documents (MongoDB) Stored documents (MongoDB)
- Character - Character
```json ```json
{ {
"id": "string (ObjectId)", "id": "string (ObjectId)",
"ownerUserId": "string", "ownerUserId": "string",
"name": "string", "name": "string",
"createdUtc": "string (ISO-8601 datetime)" "coord": {
} "x": "number",
``` "y": "number"
},
"createdUtc": "string (ISO-8601 datetime)"
}
```
@@ -11,7 +11,9 @@ public class Character
public string OwnerUserId { get; set; } = string.Empty; public string OwnerUserId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; public Coord Coord { get; set; } = new();
}
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
}
@@ -0,0 +1,8 @@
namespace CharacterApi.Models;
public class Coord
{
public int X { get; set; }
public int Y { get; set; }
}
@@ -2,7 +2,7 @@ using LocationsApi.Models;
using LocationsApi.Services; using LocationsApi.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims; using MongoDB.Driver;
namespace LocationsApi.Controllers; namespace LocationsApi.Controllers;
@@ -24,30 +24,37 @@ public class LocationsController : ControllerBase
if (string.IsNullOrWhiteSpace(req.Name)) if (string.IsNullOrWhiteSpace(req.Name))
return BadRequest("Name required"); return BadRequest("Name required");
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (req.Coord is null)
if (string.IsNullOrWhiteSpace(userId)) return BadRequest("Coord required");
return Unauthorized();
var location = new Location var location = new Location
{ {
OwnerUserId = userId,
Name = req.Name.Trim(), Name = req.Name.Trim(),
Coord = req.Coord,
CreatedUtc = DateTime.UtcNow CreatedUtc = DateTime.UtcNow
}; };
await _locations.CreateAsync(location); try
{
await _locations.CreateAsync(location);
}
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
return Conflict("Coord must be unique");
}
catch (MongoWriteException ex) when (ex.WriteError.Code == 121)
{
return BadRequest("Location document failed validation");
}
return Ok(location); return Ok(location);
} }
[HttpGet] [HttpGet]
[Authorize(Roles = "USER,SUPER")] [Authorize(Roles = "SUPER")]
public async Task<IActionResult> ListMine() public async Task<IActionResult> ListMine()
{ {
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var locations = await _locations.GetAllAsync();
if (string.IsNullOrWhiteSpace(userId))
return Unauthorized();
var locations = await _locations.GetForOwnerAsync(userId);
return Ok(locations); return Ok(locations);
} }
@@ -55,12 +62,7 @@ public class LocationsController : ControllerBase
[Authorize(Roles = "SUPER")] [Authorize(Roles = "SUPER")]
public async Task<IActionResult> Delete(string id) public async Task<IActionResult> Delete(string id)
{ {
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var deleted = await _locations.DeleteAsync(id);
if (string.IsNullOrWhiteSpace(userId))
return Unauthorized();
var allowAnyOwner = User.IsInRole("SUPER");
var deleted = await _locations.DeleteForOwnerAsync(id, userId, allowAnyOwner);
if (!deleted) if (!deleted)
return NotFound(); return NotFound();
@@ -74,6 +76,9 @@ public class LocationsController : ControllerBase
if (string.IsNullOrWhiteSpace(req.Name)) if (string.IsNullOrWhiteSpace(req.Name))
return BadRequest("Name required"); return BadRequest("Name required");
if (req.Coord is not null)
return BadRequest("Coord cannot be updated");
var updated = await _locations.UpdateNameAsync(id, req.Name.Trim()); var updated = await _locations.UpdateNameAsync(id, req.Name.Trim());
if (!updated) if (!updated)
return NotFound(); return NotFound();
+10 -2
View File
@@ -7,7 +7,11 @@ Inbound JSON documents
- CreateLocationRequest (`POST /api/locations`) - CreateLocationRequest (`POST /api/locations`)
```json ```json
{ {
"name": "string" "name": "string",
"coord": {
"x": 0,
"y": 0
}
} }
``` ```
- UpdateLocationRequest (`PUT /api/locations/{id}`) - UpdateLocationRequest (`PUT /api/locations/{id}`)
@@ -16,14 +20,18 @@ Inbound JSON documents
"name": "string" "name": "string"
} }
``` ```
`coord` cannot be updated.
Stored documents (MongoDB) Stored documents (MongoDB)
- Location - Location
```json ```json
{ {
"id": "string (ObjectId)", "id": "string (ObjectId)",
"ownerUserId": "string",
"name": "string", "name": "string",
"coord": {
"x": 0,
"y": 0
},
"createdUtc": "string (ISO-8601 datetime)" "createdUtc": "string (ISO-8601 datetime)"
} }
``` ```
@@ -0,0 +1,12 @@
using MongoDB.Bson.Serialization.Attributes;
namespace LocationsApi.Models;
public class Coord
{
[BsonElement("x")]
public int X { get; set; }
[BsonElement("y")]
public int Y { get; set; }
}
@@ -3,4 +3,6 @@ namespace LocationsApi.Models;
public class CreateLocationRequest public class CreateLocationRequest
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public required Coord Coord { get; set; }
} }
@@ -9,9 +9,12 @@ public class Location
[BsonRepresentation(BsonType.ObjectId)] [BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; } public string? Id { get; set; }
public string OwnerUserId { get; set; } = string.Empty; [BsonElement("name")]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
[BsonElement("coord")]
public required Coord Coord { get; set; }
[BsonElement("createdUtc")]
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
} }
@@ -3,4 +3,6 @@ namespace LocationsApi.Models;
public class UpdateLocationRequest public class UpdateLocationRequest
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public Coord? Coord { get; set; }
} }
+2 -2
View File
@@ -4,7 +4,7 @@
See `DOCUMENTS.md` for request payloads and stored document shapes. See `DOCUMENTS.md` for request payloads and stored document shapes.
## Endpoints ## Endpoints
- `POST /api/locations` Create a location (SUPER only). - `POST /api/locations` Create a location with a unique coord pair (SUPER only).
- `GET /api/locations` List locations for the current user. - `GET /api/locations` List all locations (SUPER only).
- `DELETE /api/locations/{id}` Delete a location (SUPER only). - `DELETE /api/locations/{id}` Delete a location (SUPER only).
- `PUT /api/locations/{id}` Update a location name (SUPER only). - `PUT /api/locations/{id}` Update a location name (SUPER only).
@@ -1,49 +1,132 @@
using LocationsApi.Models; using LocationsApi.Models;
using MongoDB.Driver; using MongoDB.Bson;
using MongoDB.Driver;
namespace LocationsApi.Services;
namespace LocationsApi.Services;
public class LocationStore
{ public class LocationStore
private readonly IMongoCollection<Location> _col; {
private readonly IMongoCollection<Location> _col;
public LocationStore(IConfiguration cfg)
{ public LocationStore(IConfiguration cfg)
var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; {
var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017";
var client = new MongoClient(cs); var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb";
var db = client.GetDatabase(dbName); var client = new MongoClient(cs);
_col = db.GetCollection<Location>("Locations"); var db = client.GetDatabase(dbName);
var collectionName = "Locations";
var ownerIndex = Builders<Location>.IndexKeys.Ascending(l => l.OwnerUserId); EnsureLocationSchema(db, collectionName);
_col.Indexes.CreateOne(new CreateIndexModel<Location>(ownerIndex)); _col = db.GetCollection<Location>(collectionName);
}
var coordIndex = Builders<Location>.IndexKeys
public Task CreateAsync(Location location) => _col.InsertOneAsync(location); .Ascending(l => l.Coord.X)
.Ascending(l => l.Coord.Y);
public Task<List<Location>> GetForOwnerAsync(string ownerUserId) => var coordIndexOptions = new CreateIndexOptions { Unique = true };
_col.Find(l => l.OwnerUserId == ownerUserId).ToListAsync(); _col.Indexes.CreateOne(new CreateIndexModel<Location>(coordIndex, coordIndexOptions));
public async Task<bool> DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner) EnsureOriginLocation();
{ }
var filter = Builders<Location>.Filter.Eq(l => l.Id, id);
if (!allowAnyOwner) private static void EnsureLocationSchema(IMongoDatabase db, string collectionName)
{ {
filter = Builders<Location>.Filter.And( var validator = new BsonDocument
filter, {
Builders<Location>.Filter.Eq(l => l.OwnerUserId, ownerUserId) {
); "$jsonSchema", new BsonDocument
} {
{ "bsonType", "object" },
var result = await _col.DeleteOneAsync(filter); { "required", new BsonArray { "name", "coord", "createdUtc" } },
return result.DeletedCount > 0; {
} "properties", new BsonDocument
{
public async Task<bool> UpdateNameAsync(string id, string name) { "name", new BsonDocument { { "bsonType", "string" } } },
{ {
var filter = Builders<Location>.Filter.Eq(l => l.Id, id); "coord", new BsonDocument
var update = Builders<Location>.Update.Set(l => l.Name, name); {
var result = await _col.UpdateOneAsync(filter, update); { "bsonType", "object" },
return result.ModifiedCount > 0; { "required", new BsonArray { "x", "y" } },
} {
} "properties", new BsonDocument
{
{ "x", new BsonDocument { { "bsonType", "int" } } },
{ "y", new BsonDocument { { "bsonType", "int" } } }
}
}
}
},
{ "createdUtc", new BsonDocument { { "bsonType", "date" } } }
}
}
}
}
};
var collections = db.ListCollectionNames().ToList();
if (!collections.Contains(collectionName))
{
var createCommand = new BsonDocument
{
{ "create", collectionName },
{ "validator", validator },
{ "validationAction", "error" }
};
db.RunCommand<BsonDocument>(createCommand);
return;
}
var command = new BsonDocument
{
{ "collMod", collectionName },
{ "validator", validator },
{ "validationAction", "error" }
};
db.RunCommand<BsonDocument>(command);
}
public Task CreateAsync(Location location) => _col.InsertOneAsync(location);
public Task<List<Location>> GetAllAsync() =>
_col.Find(Builders<Location>.Filter.Empty).ToListAsync();
public async Task<bool> DeleteAsync(string id)
{
var filter = Builders<Location>.Filter.Eq(l => l.Id, id);
var result = await _col.DeleteOneAsync(filter);
return result.DeletedCount > 0;
}
public async Task<bool> UpdateNameAsync(string id, string name)
{
var filter = Builders<Location>.Filter.Eq(l => l.Id, id);
var update = Builders<Location>.Update.Set(l => l.Name, name);
var result = await _col.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}
private void EnsureOriginLocation()
{
var filter = Builders<Location>.Filter.And(
Builders<Location>.Filter.Eq(l => l.Coord.X, 0),
Builders<Location>.Filter.Eq(l => l.Coord.Y, 0)
);
var existing = _col.Find(filter).FirstOrDefault();
if (existing is not null)
return;
var origin = new Location
{
Name = "Origin",
Coord = new Coord { X = 0, Y = 0 },
CreatedUtc = DateTime.UtcNow
};
try
{
_col.InsertOne(origin);
}
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
// Another instance seeded it first.
}
}
}