Author SHA1 Message Date
Zeeshaun ee0cf0659d Tracking character movement 2026-01-28 11:55:25 -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
admin f1ae24f627 Update README.md
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 43s
Deploy Promiscuity Locations API / deploy (push) Successful in 45s
k8s smoke test / test (push) Successful in 8s
2026-01-20 17:05:01 -06:00
50 changed files with 1579 additions and 689 deletions
+10 -1
View File
@@ -3,10 +3,19 @@
## Microservices ## Microservices
- Auth Microservice Swagger: https://pauth.ranaze.com/swagger/index.html - Auth Microservice Swagger: https://pauth.ranaze.com/swagger/index.html
- Character Microservice Swagger: https://pchar.ranaze.com/swagger/index.html - Character Microservice Swagger: https://pchar.ranaze.com/swagger/index.html
- Location Microservice Swagger: https://ploc.ranaze.com/swagger/index.html
- Microservices README: microservices/README.md - Microservices README: microservices/README.md
## Test users ## Test users
- `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)
+116 -34
View File
@@ -20,14 +20,27 @@ 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
@onready var _flashlight: SpotLight3D = $SpotLight3D
@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D
@export var camera_follow_speed := 10.0
@export var anim_idle_name := "Idle"
@export var anim_walk_name := "Walk"
@export var anim_jump_name := "Jump"
@export var anim_walk_speed_threshold := 0.25
var jump_sound = preload("res://assets/audio/jump.ogg")
var audio_player = AudioStreamPlayer.new()
@export var camera_path: NodePath @export var camera_path: NodePath
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null @onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
@@ -35,7 +48,7 @@ var audio_player = AudioStreamPlayer.new()
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null @onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
var phone_visible := false var phone_visible := false
func _ready() -> void: func _ready() -> void:
axis_lock_angular_x = true axis_lock_angular_x = true
axis_lock_angular_z = true axis_lock_angular_z = true
angular_damp = 6.0 angular_damp = 6.0
@@ -44,7 +57,7 @@ func _ready() -> void:
add_child(audio_player) add_child(audio_player)
audio_player.stream = jump_sound audio_player.stream = jump_sound
audio_player.volume_db = -20 audio_player.volume_db = -20
if cam: if cam:
_camera_offset_local = cam.transform.origin _camera_offset_local = cam.transform.origin
_camera_pitch = cam.rotation.x _camera_pitch = cam.rotation.x
_camera_yaw = global_transform.basis.get_euler().y _camera_yaw = global_transform.basis.get_euler().y
@@ -58,11 +71,16 @@ func _ready() -> void:
right.y = 0.0 right.y = 0.0
if forward.length() > 0.0001: if forward.length() > 0.0001:
_last_move_forward = forward.normalized() _last_move_forward = forward.normalized()
if right.length() > 0.0001: if right.length() > 0.0001:
_last_move_right = right.normalized() _last_move_right = right.normalized()
_vehicle_collision_layer = collision_layer
func _integrate_forces(state): _vehicle_collision_mask = collision_mask
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
func _integrate_forces(state):
if _in_vehicle:
linear_velocity = Vector3.ZERO
return
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) # Prevent flipping rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) # Prevent flipping
@@ -113,21 +131,27 @@ func _integrate_forces(state):
on_floor = true on_floor = true
break break
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1): if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
current_number_of_jumps = (current_number_of_jumps + 1) % 2 current_number_of_jumps = (current_number_of_jumps + 1) % 2
linear_velocity.y = JUMP_SPEED linear_velocity.y = JUMP_SPEED
audio_player.play() audio_player.play()
_jump_triggered = true
if cam:
var target_yaw := global_transform.basis.get_euler().y
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
var target_basis := Basis(Vector3.UP, _camera_yaw)
var target_pos := global_position + (target_basis * _camera_offset_local)
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
_update_animation(on_floor, state.linear_velocity)
_jump_triggered = false
if cam: func _input(event):
var target_yaw := global_transform.basis.get_euler().y if _in_vehicle:
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step) return
var target_basis := Basis(Vector3.UP, _camera_yaw) if event is InputEventMouseButton:
var target_pos := global_position + (target_basis * _camera_offset_local)
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_MIDDLE: if event.button_index == MOUSE_BUTTON_MIDDLE:
if event.pressed: if event.pressed:
cameraMoveMode = true cameraMoveMode = true
@@ -145,10 +169,68 @@ func _input(event):
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_camera(ZOOM_FACTOR) # Zoom out zoom_camera(ZOOM_FACTOR) # Zoom out
if event.is_action_pressed("player_light"): if event.is_action_pressed("player_light"):
$SpotLight3D.visible = !$SpotLight3D.visible _flashlight.visible = !_flashlight.visible
func zoom_camera(factor): func zoom_camera(factor):
var new_fov = cam.fov * factor var new_fov = cam.fov * factor
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) 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 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
@@ -8,14 +8,16 @@ namespace CharacterApi.Controllers;
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class CharactersController : ControllerBase public class CharactersController : ControllerBase
{ {
private readonly CharacterStore _characters; private readonly CharacterStore _characters;
private readonly LocationsClient _locations;
public CharactersController(CharacterStore characters)
{ public CharactersController(CharacterStore characters, LocationsClient locations)
_characters = characters; {
} _characters = characters;
_locations = locations;
}
[HttpPost] [HttpPost]
[Authorize(Roles = "USER,SUPER")] [Authorize(Roles = "USER,SUPER")]
@@ -28,12 +30,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);
@@ -53,17 +56,52 @@ public class CharactersController : ControllerBase
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = "USER,SUPER")] [Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Delete(string id) public async Task<IActionResult> Delete(string id)
{ {
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrWhiteSpace(userId)) if (string.IsNullOrWhiteSpace(userId))
return Unauthorized(); return Unauthorized();
var allowAnyOwner = User.IsInRole("SUPER"); var allowAnyOwner = User.IsInRole("SUPER");
var deleted = await _characters.DeleteForOwnerAsync(id, userId, allowAnyOwner); var deleted = await _characters.DeleteForOwnerAsync(id, userId, allowAnyOwner);
if (!deleted) if (!deleted)
return NotFound(); return NotFound();
return Ok("Deleted"); return Ok("Deleted");
} }
}
[HttpPut("{id}/move")]
[Authorize(Roles = "USER,SUPER")]
public async Task<IActionResult> Move(string id, [FromBody] MoveCharacterRequest req, CancellationToken ct)
{
if (req?.Coord is null)
return BadRequest("Coord required");
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrWhiteSpace(userId))
return Unauthorized();
var allowAnyOwner = User.IsInRole("SUPER");
var existing = await _characters.GetForOwnerByIdAsync(id, userId, allowAnyOwner);
if (existing is null)
return NotFound();
var presence = await _locations.UpdatePresenceAsync(id, req.Coord, ct);
if (!presence.Ok)
{
var message = string.IsNullOrWhiteSpace(presence.Body)
? "Location presence update failed"
: presence.Body;
return StatusCode((int)presence.Status, message);
}
var updated = await _characters.UpdateCoordAsync(id, userId, allowAnyOwner, req.Coord);
if (!updated)
{
await _locations.UpdatePresenceAsync(id, existing.Coord, ct);
return StatusCode(500, "Failed to update character coord");
}
return Ok("Moved");
}
}
+26 -13
View File
@@ -1,23 +1,36 @@
# 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"
} }
``` ```
- MoveCharacterRequest (`PUT /api/characters/{id}/move`)
Stored documents (MongoDB)
- Character
```json ```json
{ {
"id": "string (ObjectId)", "coord": {
"ownerUserId": "string", "x": 0,
"name": "string", "y": 0
"createdUtc": "string (ISO-8601 datetime)" }
} }
``` ```
Stored documents (MongoDB)
- Character
```json
{
"id": "string (ObjectId)",
"ownerUserId": "string",
"name": "string",
"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; }
}
@@ -0,0 +1,6 @@
namespace CharacterApi.Models;
public class MoveCharacterRequest
{
public Coord? Coord { get; set; }
}
+9 -4
View File
@@ -5,10 +5,15 @@ using Microsoft.OpenApi.Models;
using System.Text; using System.Text;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddControllers();
// DI // DI
builder.Services.AddSingleton<CharacterStore>(); builder.Services.AddSingleton<CharacterStore>();
builder.Services.AddHttpClient<LocationsClient>(client =>
{
var baseUrl = builder.Configuration["LocationsApi:BaseUrl"] ?? "http://localhost:5002";
client.BaseAddress = new Uri(baseUrl);
});
// Swagger + JWT auth in Swagger // Swagger + JWT auth in Swagger
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
+1
View File
@@ -7,3 +7,4 @@ See `DOCUMENTS.md` for request payloads and stored document shapes.
- `POST /api/characters` Create a character. - `POST /api/characters` Create a character.
- `GET /api/characters` List characters for the current user. - `GET /api/characters` List characters for the current user.
- `DELETE /api/characters/{id}` Delete a character owned by the current user. - `DELETE /api/characters/{id}` Delete a character owned by the current user.
- `PUT /api/characters/{id}/move` Move a character to a new coord.
@@ -21,14 +21,44 @@ public class CharacterStore
public Task CreateAsync(Character character) => _col.InsertOneAsync(character); public Task CreateAsync(Character character) => _col.InsertOneAsync(character);
public Task<List<Character>> GetForOwnerAsync(string ownerUserId) => public Task<List<Character>> GetForOwnerAsync(string ownerUserId) =>
_col.Find(c => c.OwnerUserId == ownerUserId).ToListAsync(); _col.Find(c => c.OwnerUserId == ownerUserId).ToListAsync();
public async Task<bool> DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner) public async Task<Character?> GetForOwnerByIdAsync(string id, string ownerUserId, bool allowAnyOwner)
{ {
var filter = Builders<Character>.Filter.Eq(c => c.Id, id); var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
if (!allowAnyOwner) if (!allowAnyOwner)
{ {
filter = Builders<Character>.Filter.And(
filter,
Builders<Character>.Filter.Eq(c => c.OwnerUserId, ownerUserId)
);
}
return await _col.Find(filter).FirstOrDefaultAsync();
}
public async Task<bool> UpdateCoordAsync(string id, string ownerUserId, bool allowAnyOwner, Coord coord)
{
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
if (!allowAnyOwner)
{
filter = Builders<Character>.Filter.And(
filter,
Builders<Character>.Filter.Eq(c => c.OwnerUserId, ownerUserId)
);
}
var update = Builders<Character>.Update.Set(c => c.Coord, coord);
var result = await _col.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}
public async Task<bool> DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner)
{
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
if (!allowAnyOwner)
{
filter = Builders<Character>.Filter.And( filter = Builders<Character>.Filter.And(
filter, filter,
Builders<Character>.Filter.Eq(c => c.OwnerUserId, ownerUserId) Builders<Character>.Filter.Eq(c => c.OwnerUserId, ownerUserId)
@@ -0,0 +1,35 @@
using CharacterApi.Models;
using System.Net;
using System.Net.Http.Json;
namespace CharacterApi.Services;
public class LocationsClient
{
private readonly HttpClient _http;
private readonly string _internalKey;
public LocationsClient(HttpClient http, IConfiguration cfg)
{
_http = http;
_internalKey = cfg["LocationsApi:InternalKey"] ?? string.Empty;
}
public async Task<(bool Ok, HttpStatusCode Status, string? Body)> UpdatePresenceAsync(
string characterId,
Coord coord,
CancellationToken ct = default)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "api/locations/presence")
{
Content = JsonContent.Create(new { characterId, coord })
};
if (!string.IsNullOrWhiteSpace(_internalKey))
request.Headers.TryAddWithoutValidation("X-Internal-Key", _internalKey);
using var response = await _http.SendAsync(request, ct);
var body = await response.Content.ReadAsStringAsync(ct);
return (response.IsSuccessStatusCode, response.StatusCode, body);
}
}
@@ -1,6 +1,7 @@
{ {
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5001" } } }, "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5001" } } },
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, "LocationsApi": { "BaseUrl": "http://localhost:5002", "InternalKey": "dev-internal-key" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
"Logging": { "LogLevel": { "Default": "Information" } } "Logging": { "LogLevel": { "Default": "Information" } }
} }
+4 -3
View File
@@ -1,7 +1,8 @@
{ {
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5001" } } }, "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5001" } } },
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, "LocationsApi": { "BaseUrl": "http://localhost:5002", "InternalKey": "dev-internal-key" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
"Logging": { "LogLevel": { "Default": "Information" } }, "Logging": { "LogLevel": { "Default": "Information" } },
"AllowedHosts": "*" "AllowedHosts": "*"
} }
@@ -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;
@@ -11,10 +11,12 @@ namespace LocationsApi.Controllers;
public class LocationsController : ControllerBase public class LocationsController : ControllerBase
{ {
private readonly LocationStore _locations; private readonly LocationStore _locations;
private readonly IConfiguration _cfg;
public LocationsController(LocationStore locations) public LocationsController(LocationStore locations, IConfiguration cfg)
{ {
_locations = locations; _locations = locations;
_cfg = cfg;
} }
[HttpPost] [HttpPost]
@@ -24,30 +26,38 @@ 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,
CharacterIds = new List<string>(),
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 +65,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,10 +79,37 @@ 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();
return Ok("Updated"); return Ok("Updated");
} }
[HttpPost("presence")]
[AllowAnonymous]
public async Task<IActionResult> UpdatePresence([FromBody] UpdateLocationPresenceRequest req)
{
var internalKey = _cfg["Internal:Key"];
if (!string.IsNullOrWhiteSpace(internalKey))
{
if (!Request.Headers.TryGetValue("X-Internal-Key", out var provided) || provided != internalKey)
return Unauthorized();
}
if (string.IsNullOrWhiteSpace(req.CharacterId))
return BadRequest("CharacterId required");
if (req.Coord is null)
return BadRequest("Coord required");
var updated = await _locations.UpdatePresenceAsync(req.CharacterId.Trim(), req.Coord);
if (!updated)
return NotFound("Location not found");
return Ok("Updated");
}
} }
+21 -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,29 @@ Inbound JSON documents
"name": "string" "name": "string"
} }
``` ```
`coord` cannot be updated.
- UpdateLocationPresenceRequest (`POST /api/locations/presence`)
```json
{
"characterId": "string",
"coord": {
"x": 0,
"y": 0
}
}
```
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
},
"characterIds": ["string"],
"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,15 @@ 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("characterIds")]
public List<string> CharacterIds { get; set; } = new();
[BsonElement("createdUtc")]
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
} }
@@ -0,0 +1,8 @@
namespace LocationsApi.Models;
public class UpdateLocationPresenceRequest
{
public string CharacterId { get; set; } = string.Empty;
public Coord? Coord { get; set; }
}
@@ -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; }
} }
+3 -2
View File
@@ -4,7 +4,8 @@
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).
- `POST /api/locations/presence` Update which characters are present at a coord (internal).
@@ -1,44 +1,107 @@
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
{
{ "name", new BsonDocument { { "bsonType", "string" } } },
{
"coord", new BsonDocument
{
{ "bsonType", "object" },
{ "required", new BsonArray { "x", "y" } },
{
"properties", new BsonDocument
{
{ "x", new BsonDocument { { "bsonType", "int" } } },
{ "y", new BsonDocument { { "bsonType", "int" } } }
}
}
}
},
{
"characterIds", new BsonDocument
{
{ "bsonType", "array" },
{ "items", new BsonDocument { { "bsonType", "string" } } }
}
},
{ "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) public async Task<bool> UpdateNameAsync(string id, string name)
{ {
var filter = Builders<Location>.Filter.Eq(l => l.Id, id); var filter = Builders<Location>.Filter.Eq(l => l.Id, id);
@@ -46,4 +109,50 @@ public class LocationStore
var result = await _col.UpdateOneAsync(filter, update); var result = await _col.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0; return result.ModifiedCount > 0;
} }
}
public async Task<bool> UpdatePresenceAsync(string characterId, Coord coord)
{
if (string.IsNullOrWhiteSpace(characterId))
return false;
var pullFilter = Builders<Location>.Filter.AnyEq(l => l.CharacterIds, characterId);
var pullUpdate = Builders<Location>.Update.Pull(l => l.CharacterIds, characterId);
await _col.UpdateManyAsync(pullFilter, pullUpdate);
var targetFilter = Builders<Location>.Filter.And(
Builders<Location>.Filter.Eq(l => l.Coord.X, coord.X),
Builders<Location>.Filter.Eq(l => l.Coord.Y, coord.Y)
);
var addUpdate = Builders<Location>.Update.AddToSet(l => l.CharacterIds, characterId);
var result = await _col.UpdateOneAsync(targetFilter, addUpdate);
return result.MatchedCount > 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 },
CharacterIds = new List<string>(),
CreatedUtc = DateTime.UtcNow
};
try
{
_col.InsertOne(origin);
}
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
// Another instance seeded it first.
}
}
}
@@ -1,6 +1,7 @@
{ {
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } }, "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } },
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
"Internal": { "Key": "dev-internal-key" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, "Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
"Logging": { "LogLevel": { "Default": "Information" } } "Logging": { "LogLevel": { "Default": "Information" } }
} }
@@ -1,6 +1,7 @@
{ {
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } }, "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } },
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
"Internal": { "Key": "dev-internal-key" },
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, "Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
"Logging": { "LogLevel": { "Default": "Information" } }, "Logging": { "LogLevel": { "Default": "Information" } },
"AllowedHosts": "*" "AllowedHosts": "*"