ravensight-playtest 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -14
- package/addons/ravensight_driver/driver.gd +406 -8
- package/addons/ravensight_simulate/simulate.gd +360 -0
- package/docs/godot-driver.md +70 -8
- package/package.json +4 -3
- package/src/api/README.md +4 -1
- package/src/api/client.js +120 -3
- package/src/api/errors.js +17 -1
- package/src/api/http.js +43 -17
- package/src/assets/formats.js +267 -0
- package/src/assets/index.js +260 -0
- package/src/assets/integration.js +429 -0
- package/src/assets/inventory.js +337 -0
- package/src/assets/measure.js +392 -0
- package/src/cli.js +233 -0
- package/src/commands/assets.js +62 -0
- package/src/commands/check.js +4 -1
- package/src/commands/create.js +734 -0
- package/src/commands/fakeCore.js +16 -1
- package/src/commands/feature.js +845 -0
- package/src/commands/fix.js +647 -70
- package/src/commands/init.js +19 -3
- package/src/commands/lists.js +206 -0
- package/src/commands/profile.js +25 -6
- package/src/commands/punch.js +116 -0
- package/src/commands/replay.js +204 -0
- package/src/commands/resume.js +7 -0
- package/src/commands/run.js +200 -24
- package/src/commands/tape.js +74 -0
- package/src/commands/template.js +70 -0
- package/src/commands/validate.js +414 -0
- package/src/commands/validateHosted.js +292 -0
- package/src/create/scaffold.js +192 -0
- package/src/run/acceptance.js +212 -0
- package/src/run/args.js +18 -1
- package/src/run/checks.js +705 -0
- package/src/run/dimension.js +95 -0
- package/src/run/drivers/godot-observation.js +59 -11
- package/src/run/drivers/godot-project.js +201 -31
- package/src/run/drivers/godot.js +532 -45
- package/src/run/drivers/web.js +18 -0
- package/src/run/evidence.js +170 -0
- package/src/run/featureLoop.js +288 -0
- package/src/run/fixLoop.js +82 -12
- package/src/run/git.js +110 -20
- package/src/run/json.js +65 -9
- package/src/run/labsOutcome.js +136 -0
- package/src/run/liveWork.js +103 -0
- package/src/run/model.js +105 -5
- package/src/run/openPr.js +107 -0
- package/src/run/paths.js +4 -1
- package/src/run/pendingRegister.js +81 -0
- package/src/run/personaLoop.js +111 -4
- package/src/run/profile.js +68 -7
- package/src/run/replay.js +236 -0
- package/src/run/repoTools.js +227 -27
- package/src/run/report.js +7 -1
- package/src/run/resume.js +19 -1
- package/src/run/secretScan.js +35 -0
- package/src/run/settle.js +41 -0
- package/src/run/tools.js +33 -4
- package/src/run/trace.js +124 -0
- package/src/simulation/godotPack.js +112 -0
- package/src/ui/index.js +61 -9
- package/src/upload/allowlist.js +6 -2
- package/src/upload/index.js +5 -4
- package/templates/godot-2d/README.md +36 -0
- package/templates/godot-2d/addons/ravensight/LICENSE +21 -0
- package/templates/godot-2d/addons/ravensight/Ravensight.gd +1823 -0
- package/templates/godot-2d/export_presets.cfg +27 -0
- package/templates/godot-2d/gitignore +10 -0
- package/templates/godot-2d/project.godot +58 -0
- package/templates/godot-2d/scenes/main.tscn +67 -0
- package/templates/godot-2d/scripts/autoload/game_config.gd +83 -0
- package/templates/godot-2d/scripts/autoload/game_state.gd +44 -0
- package/templates/godot-2d/scripts/autoload/telemetry.gd +37 -0
- package/templates/godot-2d/scripts/main.gd +32 -0
- package/templates/godot-2d/scripts/player.gd +10 -0
- package/templates/godot-2d/scripts/state_hook.gd +16 -0
- package/templates/godot-2d/template.json +14 -0
- package/templates/godot-2d/tools/inject_release_key.sh +19 -0
- package/templates/godot-2d/tools/selftest.gd +70 -0
- package/templates/godot-3d/README.md +49 -0
- package/templates/godot-3d/addons/ravensight/LICENSE +21 -0
- package/templates/godot-3d/addons/ravensight/Ravensight.gd +1823 -0
- package/templates/godot-3d/export_presets.cfg +27 -0
- package/templates/godot-3d/gitignore +10 -0
- package/templates/godot-3d/project.godot +56 -0
- package/templates/godot-3d/scenes/main.tscn +154 -0
- package/templates/godot-3d/scripts/autoload/game_config.gd +83 -0
- package/templates/godot-3d/scripts/autoload/game_state.gd +44 -0
- package/templates/godot-3d/scripts/autoload/telemetry.gd +37 -0
- package/templates/godot-3d/scripts/beacon.gd +20 -0
- package/templates/godot-3d/scripts/main.gd +49 -0
- package/templates/godot-3d/scripts/player.gd +19 -0
- package/templates/godot-3d/scripts/state_hook.gd +25 -0
- package/templates/godot-3d/template.json +16 -0
- package/templates/godot-3d/tools/inject_release_key.sh +19 -0
- package/templates/godot-3d/tools/selftest.gd +77 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[preset.0]
|
|
2
|
+
|
|
3
|
+
name="Web"
|
|
4
|
+
platform="Web"
|
|
5
|
+
runnable=true
|
|
6
|
+
export_filter="all_resources"
|
|
7
|
+
include_filter="ravensight_key.release.cfg"
|
|
8
|
+
exclude_filter="ravensight_key.cfg,tools/*"
|
|
9
|
+
export_path="build/web/index.html"
|
|
10
|
+
|
|
11
|
+
[preset.0.options]
|
|
12
|
+
|
|
13
|
+
html/export_icon=true
|
|
14
|
+
|
|
15
|
+
[preset.1]
|
|
16
|
+
|
|
17
|
+
name="Desktop"
|
|
18
|
+
platform="Linux"
|
|
19
|
+
runnable=false
|
|
20
|
+
export_filter="all_resources"
|
|
21
|
+
include_filter="ravensight_key.release.cfg"
|
|
22
|
+
exclude_filter="ravensight_key.cfg,tools/*"
|
|
23
|
+
export_path="build/desktop/game.x86_64"
|
|
24
|
+
|
|
25
|
+
[preset.1.options]
|
|
26
|
+
|
|
27
|
+
binary_format/embed_pck=true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
.godot/
|
|
2
|
+
.ravensight/brief.local.json
|
|
3
|
+
.ravensight/jobs/
|
|
4
|
+
.ravensight/create.json
|
|
5
|
+
.DS_Store
|
|
6
|
+
/build/
|
|
7
|
+
export_credentials.cfg
|
|
8
|
+
# The development key stays on this machine, and the release key is written at build time.
|
|
9
|
+
ravensight_key.cfg
|
|
10
|
+
ravensight_key.release.cfg
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
; Engine configuration file.
|
|
2
|
+
config_version=5
|
|
3
|
+
|
|
4
|
+
[application]
|
|
5
|
+
|
|
6
|
+
config/name="__GAME_NAME__"
|
|
7
|
+
config/description="A placeholder 3D template from ravensight-playtest (Alpha)."
|
|
8
|
+
config/version="0.1.0"
|
|
9
|
+
run/main_scene="res://scenes/main.tscn"
|
|
10
|
+
config/features=PackedStringArray("4.3", "GL Compatibility")
|
|
11
|
+
boot_splash/show_image=false
|
|
12
|
+
|
|
13
|
+
[autoload]
|
|
14
|
+
|
|
15
|
+
GameConfig="*res://scripts/autoload/game_config.gd"
|
|
16
|
+
Ravensight="*res://addons/ravensight/Ravensight.gd"
|
|
17
|
+
Telemetry="*res://scripts/autoload/telemetry.gd"
|
|
18
|
+
GameState="*res://scripts/autoload/game_state.gd"
|
|
19
|
+
|
|
20
|
+
[display]
|
|
21
|
+
|
|
22
|
+
window/size/viewport_width=1280
|
|
23
|
+
window/size/viewport_height=720
|
|
24
|
+
window/stretch/mode="canvas_items"
|
|
25
|
+
window/stretch/aspect="keep"
|
|
26
|
+
|
|
27
|
+
[input]
|
|
28
|
+
|
|
29
|
+
move_left={
|
|
30
|
+
"deadzone": 0.25,
|
|
31
|
+
"events": [Object(InputEventKey,"physical_keycode":65), Object(InputEventKey,"physical_keycode":4194319), Object(InputEventJoypadMotion,"device":-1,"axis":0,"axis_value":-1.0)]
|
|
32
|
+
}
|
|
33
|
+
move_right={
|
|
34
|
+
"deadzone": 0.25,
|
|
35
|
+
"events": [Object(InputEventKey,"physical_keycode":68), Object(InputEventKey,"physical_keycode":4194321), Object(InputEventJoypadMotion,"device":-1,"axis":0,"axis_value":1.0)]
|
|
36
|
+
}
|
|
37
|
+
move_up={
|
|
38
|
+
"deadzone": 0.25,
|
|
39
|
+
"events": [Object(InputEventKey,"physical_keycode":87), Object(InputEventKey,"physical_keycode":4194320), Object(InputEventJoypadMotion,"device":-1,"axis":1,"axis_value":-1.0)]
|
|
40
|
+
}
|
|
41
|
+
move_down={
|
|
42
|
+
"deadzone": 0.25,
|
|
43
|
+
"events": [Object(InputEventKey,"physical_keycode":83), Object(InputEventKey,"physical_keycode":4194322), Object(InputEventJoypadMotion,"device":-1,"axis":1,"axis_value":1.0)]
|
|
44
|
+
}
|
|
45
|
+
action={
|
|
46
|
+
"deadzone": 0.5,
|
|
47
|
+
"events": [Object(InputEventKey,"physical_keycode":32), Object(InputEventKey,"physical_keycode":69), Object(InputEventJoypadButton,"device":-1,"button_index":0)]
|
|
48
|
+
}
|
|
49
|
+
pause={
|
|
50
|
+
"deadzone": 0.5,
|
|
51
|
+
"events": [Object(InputEventKey,"physical_keycode":4194305), Object(InputEventJoypadButton,"device":-1,"button_index":6)]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[rendering]
|
|
55
|
+
|
|
56
|
+
renderer/rendering_method="gl_compatibility"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
[gd_scene load_steps=19 format=3]
|
|
2
|
+
|
|
3
|
+
[ext_resource type="Script" path="res://scripts/main.gd" id="1"]
|
|
4
|
+
[ext_resource type="Script" path="res://scripts/player.gd" id="2"]
|
|
5
|
+
[ext_resource type="Script" path="res://scripts/state_hook.gd" id="3"]
|
|
6
|
+
[ext_resource type="Script" path="res://scripts/beacon.gd" id="4"]
|
|
7
|
+
|
|
8
|
+
[sub_resource type="Environment" id="sky"]
|
|
9
|
+
background_mode = 1
|
|
10
|
+
background_color = Color(0.1, 0.12, 0.16, 1)
|
|
11
|
+
ambient_light_source = 2
|
|
12
|
+
ambient_light_color = Color(0.6, 0.65, 0.75, 1)
|
|
13
|
+
ambient_light_energy = 0.6
|
|
14
|
+
|
|
15
|
+
[sub_resource type="StandardMaterial3D" id="floor_material"]
|
|
16
|
+
albedo_color = Color(0.25, 0.3, 0.36, 1)
|
|
17
|
+
|
|
18
|
+
[sub_resource type="BoxMesh" id="floor_mesh"]
|
|
19
|
+
material = SubResource("floor_material")
|
|
20
|
+
size = Vector3(20, 1, 20)
|
|
21
|
+
|
|
22
|
+
[sub_resource type="BoxShape3D" id="floor_shape"]
|
|
23
|
+
size = Vector3(20, 1, 20)
|
|
24
|
+
|
|
25
|
+
[sub_resource type="StandardMaterial3D" id="player_material"]
|
|
26
|
+
albedo_color = Color(0.95, 0.75, 0.3, 1)
|
|
27
|
+
|
|
28
|
+
[sub_resource type="CapsuleMesh" id="player_mesh"]
|
|
29
|
+
material = SubResource("player_material")
|
|
30
|
+
radius = 0.4
|
|
31
|
+
height = 1.8
|
|
32
|
+
|
|
33
|
+
[sub_resource type="CapsuleShape3D" id="player_shape"]
|
|
34
|
+
radius = 0.4
|
|
35
|
+
height = 1.8
|
|
36
|
+
|
|
37
|
+
[sub_resource type="StandardMaterial3D" id="beacon_material"]
|
|
38
|
+
albedo_color = Color(0.35, 0.85, 0.7, 1)
|
|
39
|
+
emission_enabled = true
|
|
40
|
+
emission = Color(0.2, 0.6, 0.5, 1)
|
|
41
|
+
|
|
42
|
+
[sub_resource type="SphereMesh" id="beacon_mesh"]
|
|
43
|
+
material = SubResource("beacon_material")
|
|
44
|
+
radius = 0.5
|
|
45
|
+
height = 1.0
|
|
46
|
+
|
|
47
|
+
[sub_resource type="SphereShape3D" id="beacon_shape"]
|
|
48
|
+
radius = 0.8
|
|
49
|
+
|
|
50
|
+
[sub_resource type="StandardMaterial3D" id="crate_material"]
|
|
51
|
+
albedo_color = Color(0.7, 0.45, 0.3, 1)
|
|
52
|
+
|
|
53
|
+
[sub_resource type="BoxMesh" id="crate_mesh"]
|
|
54
|
+
material = SubResource("crate_material")
|
|
55
|
+
|
|
56
|
+
[sub_resource type="BoxShape3D" id="crate_shape"]
|
|
57
|
+
|
|
58
|
+
[node name="Main" type="Node3D"]
|
|
59
|
+
script = ExtResource("1")
|
|
60
|
+
|
|
61
|
+
[node name="Sky" type="WorldEnvironment" parent="."]
|
|
62
|
+
environment = SubResource("sky")
|
|
63
|
+
|
|
64
|
+
[node name="Sun" type="DirectionalLight3D" parent="."]
|
|
65
|
+
transform = Transform3D(0.866025, 0.25, -0.433013, 0, 0.866025, 0.5, 0.5, -0.433013, 0.75, 0, 10, 0)
|
|
66
|
+
|
|
67
|
+
[node name="Camera" type="Camera3D" parent="."]
|
|
68
|
+
transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 14, 14)
|
|
69
|
+
current = true
|
|
70
|
+
|
|
71
|
+
[node name="Floor" type="StaticBody3D" parent="."]
|
|
72
|
+
position = Vector3(0, -0.5, 0)
|
|
73
|
+
|
|
74
|
+
[node name="Mesh" type="MeshInstance3D" parent="Floor"]
|
|
75
|
+
mesh = SubResource("floor_mesh")
|
|
76
|
+
|
|
77
|
+
[node name="Shape" type="CollisionShape3D" parent="Floor"]
|
|
78
|
+
shape = SubResource("floor_shape")
|
|
79
|
+
|
|
80
|
+
[node name="Player" type="CharacterBody3D" parent="."]
|
|
81
|
+
position = Vector3(0, 0.95, 0)
|
|
82
|
+
script = ExtResource("2")
|
|
83
|
+
|
|
84
|
+
[node name="Mesh" type="MeshInstance3D" parent="Player"]
|
|
85
|
+
mesh = SubResource("player_mesh")
|
|
86
|
+
|
|
87
|
+
[node name="Shape" type="CollisionShape3D" parent="Player"]
|
|
88
|
+
shape = SubResource("player_shape")
|
|
89
|
+
|
|
90
|
+
[node name="Beacon" type="Area3D" parent="."]
|
|
91
|
+
position = Vector3(4, 0.9, 0)
|
|
92
|
+
script = ExtResource("4")
|
|
93
|
+
|
|
94
|
+
[node name="Mesh" type="MeshInstance3D" parent="Beacon"]
|
|
95
|
+
mesh = SubResource("beacon_mesh")
|
|
96
|
+
|
|
97
|
+
[node name="Shape" type="CollisionShape3D" parent="Beacon"]
|
|
98
|
+
shape = SubResource("beacon_shape")
|
|
99
|
+
|
|
100
|
+
[node name="Crate" type="RigidBody3D" parent="." groups=["crate"]]
|
|
101
|
+
position = Vector3(-3, 3, -2)
|
|
102
|
+
|
|
103
|
+
[node name="Mesh" type="MeshInstance3D" parent="Crate"]
|
|
104
|
+
mesh = SubResource("crate_mesh")
|
|
105
|
+
|
|
106
|
+
[node name="Shape" type="CollisionShape3D" parent="Crate"]
|
|
107
|
+
shape = SubResource("crate_shape")
|
|
108
|
+
|
|
109
|
+
[node name="StateHook" type="Node" parent="."]
|
|
110
|
+
script = ExtResource("3")
|
|
111
|
+
player_path = NodePath("../Player")
|
|
112
|
+
beacon_path = NodePath("../Beacon")
|
|
113
|
+
|
|
114
|
+
[node name="Interface" type="CanvasLayer" parent="."]
|
|
115
|
+
|
|
116
|
+
[node name="Hud" type="Label" parent="Interface"]
|
|
117
|
+
offset_left = 8.0
|
|
118
|
+
offset_top = 6.0
|
|
119
|
+
offset_right = 632.0
|
|
120
|
+
offset_bottom = 30.0
|
|
121
|
+
text = ""
|
|
122
|
+
|
|
123
|
+
[node name="Settings" type="Control" parent="Interface"]
|
|
124
|
+
layout_mode = 3
|
|
125
|
+
anchors_preset = 15
|
|
126
|
+
anchor_right = 1.0
|
|
127
|
+
anchor_bottom = 1.0
|
|
128
|
+
|
|
129
|
+
[node name="Panel" type="PanelContainer" parent="Interface/Settings"]
|
|
130
|
+
layout_mode = 1
|
|
131
|
+
anchors_preset = 8
|
|
132
|
+
anchor_left = 0.5
|
|
133
|
+
anchor_top = 0.5
|
|
134
|
+
anchor_right = 0.5
|
|
135
|
+
anchor_bottom = 0.5
|
|
136
|
+
offset_left = -160.0
|
|
137
|
+
offset_top = -60.0
|
|
138
|
+
offset_right = 160.0
|
|
139
|
+
offset_bottom = 60.0
|
|
140
|
+
|
|
141
|
+
[node name="Rows" type="VBoxContainer" parent="Interface/Settings/Panel"]
|
|
142
|
+
layout_mode = 2
|
|
143
|
+
|
|
144
|
+
[node name="Title" type="Label" parent="Interface/Settings/Panel/Rows"]
|
|
145
|
+
layout_mode = 2
|
|
146
|
+
text = "Settings"
|
|
147
|
+
|
|
148
|
+
[node name="Analytics" type="CheckBox" parent="Interface/Settings/Panel/Rows"]
|
|
149
|
+
layout_mode = 2
|
|
150
|
+
text = "Share anonymous analytics"
|
|
151
|
+
|
|
152
|
+
[node name="Hint" type="Label" parent="Interface/Settings/Panel/Rows"]
|
|
153
|
+
layout_mode = 2
|
|
154
|
+
text = "Esc to go back"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## Runs before the Ravensight autoload and hands it this build's key, so the key is
|
|
3
|
+
## never typed into a script or committed.
|
|
4
|
+
##
|
|
5
|
+
## Where the key comes from, first match wins:
|
|
6
|
+
## 1. --ravensight-key-file=<path> after -- on the command line
|
|
7
|
+
## 2. the RAVENSIGHT_INGEST_KEY environment variable
|
|
8
|
+
## 3. res://ravensight_key.release.cfg, in an exported build only
|
|
9
|
+
## 4. res://ravensight_key.cfg, the development key on this machine
|
|
10
|
+
## --no-key skips all four. Both files are ignored by git. With no key the SDK is
|
|
11
|
+
## inert and the game plays exactly the same.
|
|
12
|
+
|
|
13
|
+
const GAME_VERSION := "0.1.0"
|
|
14
|
+
const DEVELOPMENT_KEY := "res://ravensight_key.cfg"
|
|
15
|
+
const RELEASE_KEY := "res://ravensight_key.release.cfg"
|
|
16
|
+
|
|
17
|
+
## Which of the four answered: "argument", "environment", "release", "development" or "none".
|
|
18
|
+
var key_source := "none"
|
|
19
|
+
|
|
20
|
+
func _ready() -> void:
|
|
21
|
+
var found := find_key(_user_args(), OS.has_feature("editor"))
|
|
22
|
+
key_source = String(found["source"])
|
|
23
|
+
if not String(found["key"]).is_empty():
|
|
24
|
+
Ravensight.ingest_key = String(found["key"])
|
|
25
|
+
if not String(found["api_url"]).is_empty():
|
|
26
|
+
Ravensight.api_url = String(found["api_url"])
|
|
27
|
+
Ravensight.optional = true
|
|
28
|
+
Ravensight.game_version = GAME_VERSION
|
|
29
|
+
Ravensight.distribution = _build_label()
|
|
30
|
+
|
|
31
|
+
## The key this run should use, and what it came from. No side effects, so a test
|
|
32
|
+
## can call it.
|
|
33
|
+
func find_key(args: Dictionary, in_editor: bool) -> Dictionary:
|
|
34
|
+
var none := {"key": "", "api_url": "", "source": "none"}
|
|
35
|
+
if args.has("no-key"):
|
|
36
|
+
return none
|
|
37
|
+
if args.has("ravensight-key-file"):
|
|
38
|
+
var from_file := _read_key(String(args["ravensight-key-file"]))
|
|
39
|
+
if not String(from_file["key"]).is_empty():
|
|
40
|
+
from_file["source"] = "argument"
|
|
41
|
+
return from_file
|
|
42
|
+
var from_environment := OS.get_environment("RAVENSIGHT_INGEST_KEY").strip_edges()
|
|
43
|
+
if not from_environment.is_empty():
|
|
44
|
+
return {"key": from_environment, "api_url": OS.get_environment("RAVENSIGHT_API_URL").strip_edges(), "source": "environment"}
|
|
45
|
+
if not in_editor:
|
|
46
|
+
var release := _read_key(RELEASE_KEY)
|
|
47
|
+
if not String(release["key"]).is_empty():
|
|
48
|
+
release["source"] = "release"
|
|
49
|
+
return release
|
|
50
|
+
var development := _read_key(DEVELOPMENT_KEY)
|
|
51
|
+
if not String(development["key"]).is_empty():
|
|
52
|
+
development["source"] = "development"
|
|
53
|
+
return development
|
|
54
|
+
return none
|
|
55
|
+
|
|
56
|
+
func _read_key(path: String) -> Dictionary:
|
|
57
|
+
var answer := {"key": "", "api_url": "", "source": "none"}
|
|
58
|
+
if not FileAccess.file_exists(path):
|
|
59
|
+
return answer
|
|
60
|
+
var file := ConfigFile.new()
|
|
61
|
+
if file.load(path) != OK:
|
|
62
|
+
return answer
|
|
63
|
+
answer["key"] = String(file.get_value("ravensight", "ingest_key", "")).strip_edges()
|
|
64
|
+
answer["api_url"] = String(file.get_value("ravensight", "api_url", "")).strip_edges()
|
|
65
|
+
return answer
|
|
66
|
+
|
|
67
|
+
func _user_args() -> Dictionary:
|
|
68
|
+
var out := {}
|
|
69
|
+
for raw in OS.get_cmdline_user_args():
|
|
70
|
+
var text := String(raw).trim_prefix("--")
|
|
71
|
+
var split := text.find("=")
|
|
72
|
+
if split >= 0:
|
|
73
|
+
out[text.substr(0, split)] = text.substr(split + 1)
|
|
74
|
+
else:
|
|
75
|
+
out[text] = "true"
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
func _build_label() -> String:
|
|
79
|
+
if OS.has_feature("web"):
|
|
80
|
+
return "web"
|
|
81
|
+
if OS.has_feature("editor"):
|
|
82
|
+
return "editor"
|
|
83
|
+
return "native-" + OS.get_name().to_lower()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## What the game is, in numbers. Nothing here draws anything. A persona reads this
|
|
3
|
+
## through scripts/state_hook.gd, and the playable check reads core_loops_completed.
|
|
4
|
+
|
|
5
|
+
## A milestone calls complete_core_loop() once for each pass of the game's core loop.
|
|
6
|
+
signal core_loop_completed(total: int)
|
|
7
|
+
|
|
8
|
+
var score := 0
|
|
9
|
+
var level := 1
|
|
10
|
+
var deaths := 0
|
|
11
|
+
var core_loops_completed := 0
|
|
12
|
+
var started := false
|
|
13
|
+
|
|
14
|
+
## The game started playing. Reported once a run.
|
|
15
|
+
func start_game() -> void:
|
|
16
|
+
if started:
|
|
17
|
+
return
|
|
18
|
+
started = true
|
|
19
|
+
Telemetry.track("game_play_started", {"level": "level_%d" % level}, "level_start")
|
|
20
|
+
|
|
21
|
+
## One whole pass of the core loop, which is what "playable" means here.
|
|
22
|
+
func complete_core_loop() -> void:
|
|
23
|
+
core_loops_completed += 1
|
|
24
|
+
Telemetry.track("core_loop_completed", {"level": "level_%d" % level, "total": core_loops_completed}, "level_complete")
|
|
25
|
+
# The moment worth not losing if the player closes the game now.
|
|
26
|
+
Ravensight.flush()
|
|
27
|
+
core_loop_completed.emit(core_loops_completed)
|
|
28
|
+
|
|
29
|
+
func add_score(amount: int) -> void:
|
|
30
|
+
score += amount
|
|
31
|
+
|
|
32
|
+
func record_death() -> void:
|
|
33
|
+
deaths += 1
|
|
34
|
+
Telemetry.track("player_died", {"level": "level_%d" % level, "deaths": deaths}, "death")
|
|
35
|
+
|
|
36
|
+
## Plain values only, so it can cross to a playtest as JSON.
|
|
37
|
+
func snapshot() -> Dictionary:
|
|
38
|
+
return {
|
|
39
|
+
"started": started,
|
|
40
|
+
"score": score,
|
|
41
|
+
"level": level,
|
|
42
|
+
"deaths": deaths,
|
|
43
|
+
"core_loops_completed": core_loops_completed,
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## Every gameplay event goes through track(), which always says what the event
|
|
3
|
+
## means. Ravensight reads a level start, a level completion or a death from the
|
|
4
|
+
## reserved rs_kind property first, and only guesses from the event's name when it
|
|
5
|
+
## is missing. A natural name such as "room_restarted" or "run_finished" would
|
|
6
|
+
## otherwise be read as something it is not, so nothing here leaves that to chance.
|
|
7
|
+
|
|
8
|
+
const KINDS := ["level_start", "level_complete", "death", "none"]
|
|
9
|
+
|
|
10
|
+
## The last event this run asked to send, name and data, for a test to read. Set
|
|
11
|
+
## whether or not the SDK has a key.
|
|
12
|
+
var last_event := {}
|
|
13
|
+
|
|
14
|
+
## Queue an event. `kind` is one of KINDS. Anything else is sent as "none", with a
|
|
15
|
+
## warning, because a wrong kind would move the game's funnel.
|
|
16
|
+
func track(event_name: String, data: Dictionary = {}, kind: String = "none") -> void:
|
|
17
|
+
var prepared := prepare(data, kind)
|
|
18
|
+
last_event = {"name": event_name, "data": prepared}
|
|
19
|
+
Ravensight.track_event(event_name, prepared)
|
|
20
|
+
|
|
21
|
+
## The event's data as it will be sent.
|
|
22
|
+
func prepare(data: Dictionary, kind: String) -> Dictionary:
|
|
23
|
+
var out := data.duplicate()
|
|
24
|
+
if KINDS.has(kind):
|
|
25
|
+
out["rs_kind"] = kind
|
|
26
|
+
else:
|
|
27
|
+
push_warning("Telemetry: unknown kind %s, sent as none" % kind)
|
|
28
|
+
out["rs_kind"] = "none"
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
## The player's own choice: the settings overlay calls this. The SDK saves it and
|
|
32
|
+
## it holds on every later run. False stops all analytics, including what is queued.
|
|
33
|
+
func set_analytics_enabled(value: bool) -> void:
|
|
34
|
+
Ravensight.set_tracking_enabled(value)
|
|
35
|
+
|
|
36
|
+
func analytics_enabled() -> bool:
|
|
37
|
+
return not FileAccess.file_exists(Ravensight.OPTOUT_PATH)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
extends Area3D
|
|
2
|
+
## What the player walks to. It moves somewhere else on the floor each time. The first
|
|
3
|
+
## spot is in the scene; later ones come from the global random numbers, which a
|
|
4
|
+
## playtest seeds, so a replay sees them land in the same places.
|
|
5
|
+
|
|
6
|
+
signal collected
|
|
7
|
+
|
|
8
|
+
const REACH := 8.0
|
|
9
|
+
|
|
10
|
+
func _ready() -> void:
|
|
11
|
+
body_entered.connect(_on_body_entered)
|
|
12
|
+
|
|
13
|
+
func _on_body_entered(body: Node3D) -> void:
|
|
14
|
+
if body is CharacterBody3D:
|
|
15
|
+
collect()
|
|
16
|
+
|
|
17
|
+
## One pass of the core loop.
|
|
18
|
+
func collect() -> void:
|
|
19
|
+
position = Vector3(randf_range(-REACH, REACH), position.y, randf_range(-REACH, REACH))
|
|
20
|
+
collected.emit()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
extends Node3D
|
|
2
|
+
## The one scene the template starts on: walk to the beacon, it moves, and that is one
|
|
3
|
+
## pass of the core loop. Fall off the floor and you start again from the middle.
|
|
4
|
+
|
|
5
|
+
const FALL_LIMIT := -10.0
|
|
6
|
+
|
|
7
|
+
@onready var hud: Label = $Interface/Hud
|
|
8
|
+
@onready var settings: Control = $Interface/Settings
|
|
9
|
+
@onready var analytics: CheckBox = $Interface/Settings/Panel/Rows/Analytics
|
|
10
|
+
@onready var player: CharacterBody3D = $Player
|
|
11
|
+
@onready var start_position: Vector3 = $Player.position
|
|
12
|
+
|
|
13
|
+
func _ready() -> void:
|
|
14
|
+
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
15
|
+
for node in [$Player, $Beacon, $Crate]:
|
|
16
|
+
node.process_mode = Node.PROCESS_MODE_PAUSABLE
|
|
17
|
+
settings.visible = false
|
|
18
|
+
analytics.button_pressed = Telemetry.analytics_enabled()
|
|
19
|
+
analytics.toggled.connect(Telemetry.set_analytics_enabled)
|
|
20
|
+
$Beacon.collected.connect(_on_beacon_collected)
|
|
21
|
+
GameState.start_game()
|
|
22
|
+
_refresh()
|
|
23
|
+
|
|
24
|
+
func _unhandled_input(event: InputEvent) -> void:
|
|
25
|
+
if event.is_action_pressed("pause"):
|
|
26
|
+
_toggle_settings()
|
|
27
|
+
get_viewport().set_input_as_handled()
|
|
28
|
+
|
|
29
|
+
func _physics_process(_delta: float) -> void:
|
|
30
|
+
if player.position.y < FALL_LIMIT:
|
|
31
|
+
GameState.record_death()
|
|
32
|
+
player.position = start_position
|
|
33
|
+
player.velocity = Vector3.ZERO
|
|
34
|
+
|
|
35
|
+
func _process(_delta: float) -> void:
|
|
36
|
+
_refresh()
|
|
37
|
+
|
|
38
|
+
func _on_beacon_collected() -> void:
|
|
39
|
+
GameState.add_score(1)
|
|
40
|
+
GameState.complete_core_loop()
|
|
41
|
+
|
|
42
|
+
func _toggle_settings() -> void:
|
|
43
|
+
settings.visible = not settings.visible
|
|
44
|
+
get_tree().paused = settings.visible
|
|
45
|
+
if settings.visible:
|
|
46
|
+
analytics.grab_focus()
|
|
47
|
+
|
|
48
|
+
func _refresh() -> void:
|
|
49
|
+
hud.text = "Score %d Loops %d Esc for settings" % [GameState.score, GameState.core_loops_completed]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
extends CharacterBody3D
|
|
2
|
+
## A placeholder player: a capsule that walks on the floor and jumps. Swap the mesh for
|
|
3
|
+
## your own model. Left and right move along x, up and down along z.
|
|
4
|
+
|
|
5
|
+
const SPEED := 6.0
|
|
6
|
+
const JUMP_SPEED := 5.0
|
|
7
|
+
|
|
8
|
+
var gravity := float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
|
|
9
|
+
|
|
10
|
+
func _physics_process(delta: float) -> void:
|
|
11
|
+
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
|
|
12
|
+
velocity.x = direction.x * SPEED
|
|
13
|
+
velocity.z = direction.y * SPEED
|
|
14
|
+
if is_on_floor():
|
|
15
|
+
if Input.is_action_just_pressed("action"):
|
|
16
|
+
velocity.y = JUMP_SPEED
|
|
17
|
+
else:
|
|
18
|
+
velocity.y -= gravity * delta
|
|
19
|
+
move_and_slide()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## Lets a playtest read the game. The driver calls _ravensight_state() on every node
|
|
3
|
+
## in the ravensight_state group. Add what a player can see to this dictionary as the
|
|
4
|
+
## game grows: a persona reads it, and a replay compares it. Positions are in world
|
|
5
|
+
## units, x and z along the floor and y up.
|
|
6
|
+
|
|
7
|
+
@export var player_path: NodePath
|
|
8
|
+
@export var beacon_path: NodePath
|
|
9
|
+
|
|
10
|
+
func _ready() -> void:
|
|
11
|
+
add_to_group("ravensight_state")
|
|
12
|
+
|
|
13
|
+
func _ravensight_state() -> Dictionary:
|
|
14
|
+
var state := GameState.snapshot()
|
|
15
|
+
var player := get_node_or_null(player_path) as CharacterBody3D
|
|
16
|
+
if player != null:
|
|
17
|
+
state["player_position"] = _rounded(player.position)
|
|
18
|
+
state["player_on_floor"] = player.is_on_floor()
|
|
19
|
+
var beacon := get_node_or_null(beacon_path) as Node3D
|
|
20
|
+
if beacon != null:
|
|
21
|
+
state["beacon_position"] = _rounded(beacon.position)
|
|
22
|
+
return state
|
|
23
|
+
|
|
24
|
+
func _rounded(at: Vector3) -> Array:
|
|
25
|
+
return [snappedf(at.x, 0.01), snappedf(at.y, 0.01), snappedf(at.z, 0.01)]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "godot-3d",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"engine": "godot",
|
|
5
|
+
"dimension": "3d",
|
|
6
|
+
"stage": "alpha",
|
|
7
|
+
"min_engine": "4.3",
|
|
8
|
+
"sdk": {
|
|
9
|
+
"version": "0.3.1",
|
|
10
|
+
"source": "https://github.com/Reality-Software-Entertainment/ravensight-godot",
|
|
11
|
+
"tag": "v0.3.1",
|
|
12
|
+
"commit": "997d53edfd40641e88f40fd712df711dbb116f78",
|
|
13
|
+
"path": "addons/ravensight/Ravensight.gd",
|
|
14
|
+
"sha256": "e7a5dc6f5f0edb62c3265867da2e8da74f5ff3ed2145a8b162d77f2dbb69733e"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Writes the public release key into the project just before an export, and removes
|
|
3
|
+
# it after, so it is in the exported build and never in git:
|
|
4
|
+
#
|
|
5
|
+
# RAVENSIGHT_RELEASE_KEY=gt_pub_... sh tools/inject_release_key.sh godot --headless --export-release Web build/index.html
|
|
6
|
+
#
|
|
7
|
+
# Everything after the script's name is the export command. The development key is
|
|
8
|
+
# never exported: export_presets.cfg leaves it out by name.
|
|
9
|
+
set -eu
|
|
10
|
+
: "${RAVENSIGHT_RELEASE_KEY:?Set RAVENSIGHT_RELEASE_KEY to this game public key, the one that starts gt_pub_.}"
|
|
11
|
+
case "$RAVENSIGHT_RELEASE_KEY" in
|
|
12
|
+
gt_pub_*) ;;
|
|
13
|
+
*) echo "A build anyone can download needs a public key, the one that starts gt_pub_." >&2; exit 2 ;;
|
|
14
|
+
esac
|
|
15
|
+
file="ravensight_key.release.cfg"
|
|
16
|
+
trap 'rm -f "$file"' EXIT
|
|
17
|
+
umask 077
|
|
18
|
+
printf '[ravensight]\ningest_key="%s"\n' "$RAVENSIGHT_RELEASE_KEY" > "$file"
|
|
19
|
+
"$@"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
extends SceneTree
|
|
2
|
+
## Proves the parts of this project a game depends on, headless, with no key:
|
|
3
|
+
## godot --headless --path . --script tools/selftest.gd
|
|
4
|
+
## Prints one line per check and exits non-zero if any failed.
|
|
5
|
+
|
|
6
|
+
var failed := 0
|
|
7
|
+
|
|
8
|
+
func _initialize() -> void:
|
|
9
|
+
# Autoloads and the scene have to be ready first.
|
|
10
|
+
process_frame.connect(_run, CONNECT_ONE_SHOT)
|
|
11
|
+
|
|
12
|
+
func _check(name: String, passed: bool) -> void:
|
|
13
|
+
print("%s %s" % ["ok " if passed else "FAIL", name])
|
|
14
|
+
if not passed:
|
|
15
|
+
failed += 1
|
|
16
|
+
|
|
17
|
+
func _run() -> void:
|
|
18
|
+
var game_config: Node = root.get_node("GameConfig")
|
|
19
|
+
var telemetry: Node = root.get_node("Telemetry")
|
|
20
|
+
var game_state: Node = root.get_node("GameState")
|
|
21
|
+
var sdk: Node = root.get_node("Ravensight")
|
|
22
|
+
|
|
23
|
+
# Every kind reaches the event as rs_kind, and a bad one reads as none.
|
|
24
|
+
for kind in ["level_start", "level_complete", "death", "none"]:
|
|
25
|
+
_check("rs_kind %s" % kind, telemetry.prepare({}, kind)["rs_kind"] == kind)
|
|
26
|
+
_check("an unknown kind is sent as none", telemetry.prepare({}, "nonsense")["rs_kind"] == "none")
|
|
27
|
+
var original := {"a": 1}
|
|
28
|
+
telemetry.prepare(original, "none")
|
|
29
|
+
_check("prepare leaves the caller's data alone", not original.has("rs_kind"))
|
|
30
|
+
|
|
31
|
+
# The key loader's order, without a key on this machine being read.
|
|
32
|
+
var dir := OS.get_user_data_dir().path_join("selftest")
|
|
33
|
+
DirAccess.make_dir_recursive_absolute(dir)
|
|
34
|
+
var path := dir.path_join("key.cfg")
|
|
35
|
+
var file := ConfigFile.new()
|
|
36
|
+
file.set_value("ravensight", "ingest_key", "gt_live_selftest")
|
|
37
|
+
file.set_value("ravensight", "api_url", "http://127.0.0.1:9")
|
|
38
|
+
file.save(path)
|
|
39
|
+
var found: Dictionary = game_config.find_key({"ravensight-key-file": path}, true)
|
|
40
|
+
_check("a key file argument wins", found["source"] == "argument" and found["key"] == "gt_live_selftest" and found["api_url"] == "http://127.0.0.1:9")
|
|
41
|
+
_check("--no-key answers none", game_config.find_key({"no-key": "true", "ravensight-key-file": path}, true)["source"] == "none")
|
|
42
|
+
DirAccess.remove_absolute(path)
|
|
43
|
+
|
|
44
|
+
# The opt-out is saved where the SDK reads it, and a later run would honour it.
|
|
45
|
+
var was_on: bool = telemetry.analytics_enabled()
|
|
46
|
+
telemetry.set_analytics_enabled(false)
|
|
47
|
+
_check("opting out is saved", not telemetry.analytics_enabled())
|
|
48
|
+
telemetry.set_analytics_enabled(true)
|
|
49
|
+
_check("opting back in clears it", telemetry.analytics_enabled())
|
|
50
|
+
telemetry.set_analytics_enabled(was_on)
|
|
51
|
+
|
|
52
|
+
# What the playable check reads.
|
|
53
|
+
var before: int = game_state.core_loops_completed
|
|
54
|
+
game_state.complete_core_loop()
|
|
55
|
+
_check("a completed loop is counted", game_state.core_loops_completed == before + 1)
|
|
56
|
+
_check("it is reported as a level completion", telemetry.last_event["data"]["rs_kind"] == "level_complete")
|
|
57
|
+
|
|
58
|
+
# The hook a playtest reads, on the real scene.
|
|
59
|
+
var scene: Node = load("res://scenes/main.tscn").instantiate()
|
|
60
|
+
root.add_child(scene)
|
|
61
|
+
var hook: Node = scene.get_node("StateHook")
|
|
62
|
+
_check("the state node is in the group", hook.is_in_group("ravensight_state"))
|
|
63
|
+
var state: Dictionary = hook._ravensight_state()
|
|
64
|
+
_check("the state says how many loops", state.has("core_loops_completed"))
|
|
65
|
+
_check("the root is 3D", scene is Node3D)
|
|
66
|
+
_check("the state says where the player is, in three axes", state.has("player_position") and (state["player_position"] as Array).size() == 3)
|
|
67
|
+
_check("the state says where the beacon is", state.has("beacon_position"))
|
|
68
|
+
var loops: int = game_state.core_loops_completed
|
|
69
|
+
var spot: Vector3 = scene.get_node("Beacon").position
|
|
70
|
+
scene.get_node("Beacon").collect()
|
|
71
|
+
_check("collecting the beacon is one pass of the loop", game_state.core_loops_completed == loops + 1)
|
|
72
|
+
_check("the beacon moves on", scene.get_node("Beacon").position != spot)
|
|
73
|
+
_check("the SDK is the public one", String(sdk.VERSION).length() > 0)
|
|
74
|
+
scene.queue_free()
|
|
75
|
+
|
|
76
|
+
print("selftest %s" % ("failed" if failed > 0 else "passed"))
|
|
77
|
+
quit(1 if failed > 0 else 0)
|