Compare commits

...

7 Commits

Author SHA1 Message Date
b0d392627c Small geometry fixes
All checks were successful
linting & formatting / build (push) Successful in 19s
2025-04-25 18:12:14 -06:00
de6606fdcf Distinct animation players for different HUD alerts 2025-04-25 17:57:16 -06:00
217224f9c3 Two-sided signs & additional level development 2025-04-25 17:49:17 -06:00
2078dd14bc Save state checks game & level version compatibility before deserializing 2025-04-25 11:18:03 -06:00
94e993e92d Sickbay rebuild 2025-04-25 11:04:25 -06:00
2e3b301d61 Improved save icon 2025-04-25 10:16:28 -06:00
28024f9cfc i18n strings for title menu 2025-04-25 09:50:40 -06:00
29 changed files with 936 additions and 419 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/level/guide_signs/guide_sign_mess_hall_airlock_C.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bb7qesvjimqjh"
path="res://.godot/imported/guide_sign_mess_hall_airlock_C.png-a0a03442bd8752ca9a6a5d73d6d17e3b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/level/guide_signs/guide_sign_mess_hall_airlock_C.png"
dest_files=["res://.godot/imported/guide_sign_mess_hall_airlock_C.png-a0a03442bd8752ca9a6a5d73d6d17e3b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

BIN
assets/level/guide_signs/guide_sign_mess_hall_airlock_E.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1hug5v5o7g5j"
path="res://.godot/imported/guide_sign_mess_hall_airlock_E.png-021499b7fe2567d7ad89c7c31c812293.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/level/guide_signs/guide_sign_mess_hall_airlock_E.png"
dest_files=["res://.godot/imported/guide_sign_mess_hall_airlock_E.png-021499b7fe2567d7ad89c7c31c812293.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View File

@ -10,6 +10,14 @@ UI_BACK,"⏎ Back"
UI_LOCKED,Locked UI_LOCKED,Locked
UI_QUIT,Quit UI_QUIT,Quit
UI_LOADING,Loading UI_LOADING,Loading
UI_DONE,Done!
UI_SAVING,Saving...
,
TITLE_WARNING_1,!WARNING!
TITLE_WARNING_2,"This is a secure terminal system."
TITLE_WARNING_3,"Unauthorized access is prohibited under penalty of"
TITLE_CONTINUE,Continue
TITLE_NEW_GAME,"New Game"
, ,
PAUSE_HEADING,Paused PAUSE_HEADING,Paused
PAUSE_RESUME,Resume PAUSE_RESUME,Resume

1 keys en
10 UI_LOCKED Locked
11 UI_QUIT Quit
12 UI_LOADING Loading
13 UI_DONE Done!
14 UI_SAVING Saving...
15
16 TITLE_WARNING_1 !WARNING!
17 TITLE_WARNING_2 This is a secure terminal system.
18 TITLE_WARNING_3 Unauthorized access is prohibited under penalty of
19 TITLE_CONTINUE Continue
20 TITLE_NEW_GAME New Game
21
22 PAUSE_HEADING Paused
23 PAUSE_RESUME Resume

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,8 @@ const SAVE_PATH_FMT := "user://{0}.state.res"
## Human-readable name ## Human-readable name
@export var pretty_name: String @export var pretty_name: String
@export var version := 0
func get_save_path() -> String: func get_save_path() -> String:
return SAVE_PATH_FMT.format([id]) return SAVE_PATH_FMT.format([id])

View File

@ -30,6 +30,7 @@ mesh = ExtResource("1_f6xlw")
surface_material_override/0 = SubResource("StandardMaterial3D_2trc8") surface_material_override/0 = SubResource("StandardMaterial3D_2trc8")
[node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D"] [node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D"]
collision_layer = 5
[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D"] [node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_71u53") shape = SubResource("ConcavePolygonShape3D_71u53")

View File

@ -19,7 +19,6 @@ unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.184204, 0.138956, 0.102927) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.184204, 0.138956, 0.102927)
pixel_size = 0.0002 pixel_size = 0.0002
modulate = Color(0, 1, 0.301961, 1) modulate = Color(0, 1, 0.301961, 1)
text = "_"
font = SubResource("SystemFont_twqjh") font = SubResource("SystemFont_twqjh")
font_size = 90 font_size = 90
outline_size = 36 outline_size = 36

View File

@ -1,10 +1,11 @@
[gd_scene load_steps=12 format=3 uid="uid://brknr57xc2cp0"] [gd_scene load_steps=19 format=3 uid="uid://brknr57xc2cp0"]
[ext_resource type="Texture2D" uid="uid://b7ds08rj0yk7j" path="res://assets/ui/corpo_logo/corpo_logo_128_bg.png" id="1_1oh6t"] [ext_resource type="Texture2D" uid="uid://b7ds08rj0yk7j" path="res://assets/ui/corpo_logo/corpo_logo_128_bg.png" id="1_1oh6t"]
[ext_resource type="Texture2D" uid="uid://dixpjnlaj86x2" path="res://assets/ui/corpo_logo/corpo_logo_128.png" id="1_p2l3a"] [ext_resource type="Texture2D" uid="uid://dixpjnlaj86x2" path="res://assets/ui/corpo_logo/corpo_logo_128.png" id="1_p2l3a"]
[ext_resource type="Shader" uid="uid://dnytoirugot2e" path="res://src/shaders/canvas_grunk.gdshader" id="2_q367f"] [ext_resource type="Shader" uid="uid://dnytoirugot2e" path="res://src/shaders/canvas_grunk.gdshader" id="2_q367f"]
[ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="3_8o5hc"] [ext_resource type="FastNoiseLite" uid="uid://cnlvdtx68giv6" path="res://assets/materials/gunk_noise.tres" id="3_8o5hc"]
[ext_resource type="PackedScene" uid="uid://b6dx0ovy15g5o" path="res://src/effects/grunk_2d/grunk_2d.tscn" id="5_xrtbx"] [ext_resource type="PackedScene" uid="uid://b6dx0ovy15g5o" path="res://src/effects/grunk_2d/grunk_2d.tscn" id="5_xrtbx"]
[ext_resource type="Theme" uid="uid://b07fevr214mmr" path="res://src/ui/hud/hud_theme.tres" id="6_t8g1i"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_8o5hc"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_8o5hc"]
@ -50,27 +51,198 @@ shader_parameter/gunk_normal_map = SubResource("NoiseTexture3D_d72jk")
shader_parameter/mask_progress = 1.0 shader_parameter/mask_progress = 1.0
shader_parameter/mask_noise = SubResource("NoiseTexture2D_pgbvb") shader_parameter/mask_noise = SubResource("NoiseTexture2D_pgbvb")
[node name="SaveIcon" type="MarginContainer"] [sub_resource type="Animation" id="Animation_jqm8y"]
offset_right = 40.0 resource_name = "blink"
offset_bottom = 40.0 length = 0.3
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:visible")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.15, 0.3),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [true, false, true]
}
[node name="IconBG" type="TextureRect" parent="."] [sub_resource type="Animation" id="Animation_yor10"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:visible")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [true]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ovpa4"]
_data = {
&"RESET": SubResource("Animation_yor10"),
&"blink": SubResource("Animation_jqm8y")
}
[sub_resource type="Animation" id="Animation_t8g1i"]
resource_name = "display"
length = 2.0
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("HBoxContainer/Label:visible_ratio")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [0.0, 0.0, 1.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:modulate")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1, 2),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
tracks/2/type = "method"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath(".")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(2),
"transitions": PackedFloat32Array(1),
"values": [{
"args": [],
"method": &"queue_free"
}]
}
[sub_resource type="Animation" id="Animation_ovpa4"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("HBoxContainer/Label:visible_ratio")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:modulate")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_lm3su"]
_data = {
&"RESET": SubResource("Animation_ovpa4"),
&"display": SubResource("Animation_t8g1i")
}
[node name="SaveIcon" type="MarginContainer"]
offset_right = 348.0
offset_bottom = 140.0
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2 layout_mode = 2
alignment = 2
[node name="Control" type="Control" parent="HBoxContainer"]
custom_minimum_size = Vector2(140, 140)
layout_mode = 2
[node name="IconBG" type="TextureRect" parent="HBoxContainer/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 = -70.0
offset_top = -70.0
offset_right = 70.0
offset_bottom = 70.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4 size_flags_horizontal = 4
size_flags_vertical = 4 size_flags_vertical = 4
texture = ExtResource("1_1oh6t") texture = ExtResource("1_1oh6t")
stretch_mode = 2 stretch_mode = 2
[node name="SaveIcon" type="TextureRect" parent="."] [node name="SaveIcon" type="TextureRect" parent="HBoxContainer/Control"]
clip_children = 2 clip_children = 2
texture_filter = 3 texture_filter = 3
material = SubResource("ShaderMaterial_8o5hc") material = SubResource("ShaderMaterial_8o5hc")
layout_mode = 2 layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -64.0
offset_top = -64.0
offset_right = 64.0
offset_bottom = 64.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4 size_flags_horizontal = 4
size_flags_vertical = 4 size_flags_vertical = 4
texture = ExtResource("1_p2l3a") texture = ExtResource("1_p2l3a")
stretch_mode = 2 stretch_mode = 2
[node name="Grunk2D" parent="SaveIcon" instance=ExtResource("5_xrtbx")] [node name="Grunk2D" parent="HBoxContainer/Control/SaveIcon" instance=ExtResource("5_xrtbx")]
material = SubResource("ShaderMaterial_t8g1i") material = SubResource("ShaderMaterial_t8g1i")
layout_mode = 1 layout_mode = 1
[node name="Label" type="Label" parent="HBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
theme = ExtResource("6_t8g1i")
theme_override_colors/font_color = Color(0.14, 1, 0.355, 1)
text = "UI_SAVING"
visible_characters = 0
visible_ratio = 0.0
[node name="Blinker" type="Label" parent="HBoxContainer"]
layout_mode = 2
size_flags_vertical = 8
theme = ExtResource("6_t8g1i")
theme_override_colors/font_color = Color(0.14, 1, 0.355, 1)
text = "_"
[node name="AnimationPlayer" type="AnimationPlayer" parent="HBoxContainer/Blinker"]
libraries = {
&"": SubResource("AnimationLibrary_ovpa4")
}
autoplay = "blink"
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_lm3su")
}
autoplay = "display"

View File

@ -2,7 +2,9 @@ class_name PlayerHUD extends Control
@onready var interact_hud: InteractHUD = %InteractHUD @onready var interact_hud: InteractHUD = %InteractHUD
@onready var alert_player: AnimationPlayer = %AlertPlayer @onready var grunk_alert_player: AnimationPlayer = %GrunkAlertPlayer
@onready var tank_alert_player: AnimationPlayer = %TankAlertPlayer
@onready var alert_clear_player: AnimationPlayer = %AlertClearPlayer
func _ready() -> void: func _ready() -> void:
@ -15,12 +17,12 @@ func select_interactive(prop: Interactive) -> void:
func _on_raise_alert(_new_value: int) -> void: func _on_raise_alert(_new_value: int) -> void:
alert_player.play("grunk_alert") grunk_alert_player.play("grunk_alert")
func play_tank_full_alert() -> void: func play_tank_full_alert() -> void:
alert_player.play("tank_full_alert") tank_alert_player.play("tank_full_alert")
func _on_clear_alert() -> void: func _on_clear_alert() -> void:
alert_player.play("alert_clear") alert_clear_player.play("alert_clear")

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=13 format=3 uid="uid://dq1x21tq06dud"] [gd_scene load_steps=17 format=3 uid="uid://dq1x21tq06dud"]
[ext_resource type="Theme" uid="uid://b07fevr214mmr" path="res://src/ui/hud/hud_theme.tres" id="1_lirk3"] [ext_resource type="Theme" uid="uid://b07fevr214mmr" path="res://src/ui/hud/hud_theme.tres" id="1_lirk3"]
[ext_resource type="Script" uid="uid://lrsv0185bfu" path="res://src/ui/hud/player_hud.gd" id="2_j6lpx"] [ext_resource type="Script" uid="uid://lrsv0185bfu" path="res://src/ui/hud/player_hud.gd" id="2_j6lpx"]
@ -8,12 +8,50 @@
[ext_resource type="Script" uid="uid://cjs2fen6jo0g0" path="res://src/ui/rumbler.gd" id="4_ud8na"] [ext_resource type="Script" uid="uid://cjs2fen6jo0g0" path="res://src/ui/rumbler.gd" id="4_ud8na"]
[ext_resource type="FontFile" uid="uid://oq8ue2qrfijg" path="res://assets/fonts/Silkscreen/Silkscreen-Regular.ttf" id="7_iwjh7"] [ext_resource type="FontFile" uid="uid://oq8ue2qrfijg" path="res://assets/fonts/Silkscreen/Silkscreen-Regular.ttf" id="7_iwjh7"]
[sub_resource type="Animation" id="Animation_8np55"]
resource_name = "tank_full_alert"
length = 3.6
step = 0.1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("TankAlert:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.6, 1.2, 1.8, 2.4, 3, 3.6),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="Animation" id="Animation_iwjh7"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("TankAlert:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_c1hvd"]
_data = {
&"RESET": SubResource("Animation_iwjh7"),
&"tank_full_alert": SubResource("Animation_8np55")
}
[sub_resource type="Animation" id="Animation_n6jee"] [sub_resource type="Animation" id="Animation_n6jee"]
length = 0.001 length = 0.001
tracks/0/type = "value" tracks/0/type = "value"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath("GrunkAlertWarning:modulate") tracks/0/path = NodePath("GrunkAlert/GrunkAlertWarning:modulate")
tracks/0/interp = 1 tracks/0/interp = 1
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
@ -25,7 +63,7 @@ tracks/0/keys = {
tracks/1/type = "value" tracks/1/type = "value"
tracks/1/imported = false tracks/1/imported = false
tracks/1/enabled = true tracks/1/enabled = true
tracks/1/path = NodePath("Rumbler/GrunkAlert2:modulate") tracks/1/path = NodePath("GrunkAlert/Rumbler/GrunkAlert2:modulate")
tracks/1/interp = 1 tracks/1/interp = 1
tracks/1/loop_wrap = true tracks/1/loop_wrap = true
tracks/1/keys = { tracks/1/keys = {
@ -34,65 +72,40 @@ tracks/1/keys = {
"update": 0, "update": 0,
"values": [Color(1, 1, 1, 0)] "values": [Color(1, 1, 1, 0)]
} }
tracks/2/type = "value"
tracks/2/imported = false [sub_resource type="Animation" id="Animation_5be8f"]
tracks/2/enabled = true resource_name = "grunk_alert"
tracks/2/path = NodePath("TankAlert:modulate") length = 4.6
tracks/2/interp = 1 step = 0.2
tracks/2/loop_wrap = true tracks/0/type = "value"
tracks/2/keys = { tracks/0/imported = false
"times": PackedFloat32Array(0), tracks/0/enabled = true
"transitions": PackedFloat32Array(1), tracks/0/path = NodePath("GrunkAlert/GrunkAlertWarning:modulate")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 0, "update": 0,
"values": [Color(1, 1, 1, 0)] "values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
} }
tracks/3/type = "value" tracks/1/type = "value"
tracks/3/imported = false tracks/1/imported = false
tracks/3/enabled = true tracks/1/enabled = true
tracks/3/path = NodePath("AlertClearMessage:visible") tracks/1/path = NodePath("GrunkAlert/Rumbler/GrunkAlert2:modulate")
tracks/3/interp = 1 tracks/1/interp = 1
tracks/3/loop_wrap = true tracks/1/loop_wrap = true
tracks/3/keys = { tracks/1/keys = {
"times": PackedFloat32Array(0), "times": PackedFloat32Array(0, 1.6, 2.2, 2.8, 3.4, 4.00479, 4.6),
"transitions": PackedFloat32Array(1), "transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 1, "update": 1,
"values": [false] "values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
} }
tracks/4/type = "value"
tracks/4/imported = false [sub_resource type="AnimationLibrary" id="AnimationLibrary_ud8na"]
tracks/4/enabled = true _data = {
tracks/4/path = NodePath("AlertClearMessage/Line1:visible_ratio") &"RESET": SubResource("Animation_n6jee"),
tracks/4/interp = 1 &"grunk_alert": SubResource("Animation_5be8f")
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("AlertClearMessage/RichTextLabel:visible_ratio")
tracks/5/interp = 1
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/6/type = "value"
tracks/6/imported = false
tracks/6/enabled = true
tracks/6/path = NodePath("AlertClearMessage:modulate")
tracks/6/interp = 1
tracks/6/loop_wrap = true
tracks/6/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Color(1, 1, 1, 1)]
} }
[sub_resource type="Animation" id="Animation_65kmv"] [sub_resource type="Animation" id="Animation_65kmv"]
@ -147,58 +160,61 @@ tracks/3/keys = {
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] "values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
} }
[sub_resource type="Animation" id="Animation_5be8f"] [sub_resource type="Animation" id="Animation_c1hvd"]
resource_name = "grunk_alert" length = 0.001
length = 4.6
step = 0.2
tracks/0/type = "value" tracks/0/type = "value"
tracks/0/imported = false tracks/0/imported = false
tracks/0/enabled = true tracks/0/enabled = true
tracks/0/path = NodePath("GrunkAlertWarning:modulate") tracks/0/path = NodePath("AlertClearMessage:visible")
tracks/0/interp = 2 tracks/0/interp = 1
tracks/0/loop_wrap = true tracks/0/loop_wrap = true
tracks/0/keys = { tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6), "times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1), "transitions": PackedFloat32Array(1),
"update": 0, "update": 1,
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] "values": [false]
} }
tracks/1/type = "value" tracks/1/type = "value"
tracks/1/imported = false tracks/1/imported = false
tracks/1/enabled = true tracks/1/enabled = true
tracks/1/path = NodePath("Rumbler/GrunkAlert2:modulate") tracks/1/path = NodePath("AlertClearMessage:modulate")
tracks/1/interp = 1 tracks/1/interp = 1
tracks/1/loop_wrap = true tracks/1/loop_wrap = true
tracks/1/keys = { tracks/1/keys = {
"times": PackedFloat32Array(0, 1.6, 2.2, 2.8, 3.4, 4.00479, 4.6), "times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1), "transitions": PackedFloat32Array(1),
"update": 1,
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
}
[sub_resource type="Animation" id="Animation_ud8na"]
resource_name = "tank_full_alert"
length = 3.6
step = 0.1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("TankAlert:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.6, 1.2, 1.8, 2.4, 3, 3.6),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 0, "update": 0,
"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] "values": [Color(1, 1, 1, 0)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("AlertClearMessage/Line1:visible_ratio")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("AlertClearMessage/RichTextLabel:visible_ratio")
tracks/3/interp = 1
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [0.0]
} }
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ud8na"] [sub_resource type="AnimationLibrary" id="AnimationLibrary_bu2tv"]
_data = { _data = {
&"RESET": SubResource("Animation_n6jee"), &"RESET": SubResource("Animation_c1hvd"),
&"alert_clear": SubResource("Animation_65kmv"), &"alert_clear": SubResource("Animation_65kmv")
&"grunk_alert": SubResource("Animation_5be8f"),
&"tank_full_alert": SubResource("Animation_ud8na")
} }
[node name="PlayerHUD" type="Control"] [node name="PlayerHUD" type="Control"]
@ -303,58 +319,70 @@ theme_type_variation = &"AlertLabel"
text = "HUD_TANK_WARNING" text = "HUD_TANK_WARNING"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="GrunkAlertWarning" type="HBoxContainer" parent="AlertHUD"] [node name="TankAlertPlayer" type="AnimationPlayer" parent="AlertHUD/TankAlert"]
unique_name_in_owner = true
root_node = NodePath("../..")
libraries = {
&"": SubResource("AnimationLibrary_c1hvd")
}
[node name="GrunkAlert" type="Control" parent="AlertHUD"]
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="GrunkAlertWarning" type="HBoxContainer" parent="AlertHUD/GrunkAlert"]
modulate = Color(1, 1, 1, 0) modulate = Color(1, 1, 1, 0)
layout_mode = 1 layout_mode = 1
anchors_preset = 5 anchors_preset = 5
anchor_left = 0.5 anchor_left = 0.5
anchor_right = 0.5 anchor_right = 0.5
offset_left = -264.0 offset_left = 20.0
offset_right = 264.0 offset_right = 740.0
offset_bottom = 82.0 offset_bottom = 82.0
grow_horizontal = 2 grow_horizontal = 2
theme_type_variation = &"AlertLabel" theme_type_variation = &"AlertLabel"
theme_override_constants/separation = 32 theme_override_constants/separation = 32
alignment = 1 alignment = 1
[node name="Icon" type="Label" parent="AlertHUD/GrunkAlertWarning"] [node name="Icon" type="Label" parent="AlertHUD/GrunkAlert/GrunkAlertWarning"]
layout_mode = 2 layout_mode = 2
theme_type_variation = &"AlertLabel" theme_type_variation = &"AlertLabel"
theme_override_fonts/font = ExtResource("4_2q5it") theme_override_fonts/font = ExtResource("4_2q5it")
text = "" text = ""
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Warning" type="Label" parent="AlertHUD/GrunkAlertWarning"] [node name="Warning" type="Label" parent="AlertHUD/GrunkAlert/GrunkAlertWarning"]
layout_mode = 2 layout_mode = 2
theme_type_variation = &"AlertLabel" theme_type_variation = &"AlertLabel"
text = "HUD_WARNING" text = "HUD_WARNING"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Icon2" type="Label" parent="AlertHUD/GrunkAlertWarning"] [node name="Icon2" type="Label" parent="AlertHUD/GrunkAlert/GrunkAlertWarning"]
layout_mode = 2 layout_mode = 2
theme_type_variation = &"AlertLabel" theme_type_variation = &"AlertLabel"
theme_override_fonts/font = ExtResource("4_2q5it") theme_override_fonts/font = ExtResource("4_2q5it")
text = "" text = ""
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Rumbler" type="Control" parent="AlertHUD"] [node name="Rumbler" type="Control" parent="AlertHUD/GrunkAlert"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -399.906 offset_left = -19.9658
offset_top = -299.03 offset_top = -20.8966
offset_right = -399.906 offset_right = -19.9658
offset_bottom = -299.03 offset_bottom = -20.8966
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("4_ud8na") script = ExtResource("4_ud8na")
intensity = 2.0 intensity = 2.0
metadata/_custom_type_script = "uid://cjs2fen6jo0g0" metadata/_custom_type_script = "uid://cjs2fen6jo0g0"
[node name="GrunkAlert2" type="Control" parent="AlertHUD/Rumbler"] [node name="GrunkAlert2" type="Control" parent="AlertHUD/GrunkAlert/Rumbler"]
modulate = Color(1, 1, 1, 0) modulate = Color(1, 1, 1, 0)
layout_mode = 1 layout_mode = 1
anchors_preset = 5 anchors_preset = 5
@ -367,7 +395,7 @@ grow_horizontal = 2
mouse_filter = 2 mouse_filter = 2
theme_type_variation = &"AlertLabel" theme_type_variation = &"AlertLabel"
[node name="AlertLine1" type="Label" parent="AlertHUD/Rumbler/GrunkAlert2"] [node name="AlertLine1" type="Label" parent="AlertHUD/GrunkAlert/Rumbler/GrunkAlert2"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@ -388,7 +416,7 @@ theme_override_font_sizes/font_size = 666
text = "! GRUNK ALERT !" text = "! GRUNK ALERT !"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="AlertLine2" type="Label" parent="AlertHUD/Rumbler/GrunkAlert2"] [node name="AlertLine2" type="Label" parent="AlertHUD/GrunkAlert/Rumbler/GrunkAlert2"]
layout_mode = 1 layout_mode = 1
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
@ -405,8 +433,16 @@ theme_type_variation = &"AlertLabel"
text = "HUD_ALERT_MSG" text = "HUD_ALERT_MSG"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="GrunkAlertPlayer" type="AnimationPlayer" parent="AlertHUD/GrunkAlert"]
unique_name_in_owner = true
root_node = NodePath("../..")
libraries = {
&"": SubResource("AnimationLibrary_ud8na")
}
[node name="AlertClearMessage" type="VBoxContainer" parent="AlertHUD"] [node name="AlertClearMessage" type="VBoxContainer" parent="AlertHUD"]
visible = false visible = false
modulate = Color(1, 1, 1, 0)
layout_mode = 1 layout_mode = 1
anchors_preset = 5 anchors_preset = 5
anchor_left = 0.5 anchor_left = 0.5
@ -440,8 +476,9 @@ visible_characters = 0
visible_characters_behavior = 1 visible_characters_behavior = 1
visible_ratio = 0.0 visible_ratio = 0.0
[node name="AlertPlayer" type="AnimationPlayer" parent="AlertHUD"] [node name="AlertClearPlayer" type="AnimationPlayer" parent="AlertHUD/AlertClearMessage"]
unique_name_in_owner = true unique_name_in_owner = true
root_node = NodePath("../..")
libraries = { libraries = {
&"": SubResource("AnimationLibrary_ud8na") &"": SubResource("AnimationLibrary_bu2tv")
} }

View File

@ -672,7 +672,7 @@ visible_characters_behavior = 1
[node name="Done" type="Label" parent="TitleScreenContent/VBoxContainer/Loader"] [node name="Done" type="Label" parent="TitleScreenContent/VBoxContainer/Loader"]
layout_mode = 2 layout_mode = 2
text = "Done!" text = "UI_DONE"
visible_characters = 0 visible_characters = 0
visible_characters_behavior = 1 visible_characters_behavior = 1
visible_ratio = 0.0 visible_ratio = 0.0
@ -707,13 +707,13 @@ layout_mode = 2
[node name="Warning" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line1"] [node name="Warning" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line1"]
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("6_cgiy0") theme_override_fonts/font = ExtResource("6_cgiy0")
text = "!WARNING!" text = "TITLE_WARNING_1"
visible_characters = 0 visible_characters = 0
visible_ratio = 0.0 visible_ratio = 0.0
[node name="Warning2" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line1"] [node name="Warning2" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line1"]
layout_mode = 2 layout_mode = 2
text = " This is a secure terminal system." text = "TITLE_WARNING_2"
visible_characters = 0 visible_characters = 0
visible_ratio = 0.0 visible_ratio = 0.0
@ -728,7 +728,7 @@ layout_mode = 2
[node name="Warning3" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line2"] [node name="Warning3" type="Label" parent="TitleScreenContent/VBoxContainer/WarningMessage/VBoxContainer/Line2"]
layout_mode = 2 layout_mode = 2
text = "Unauthorized access is prohibited under penalty of" text = "TITLE_WARNING_3"
visible_characters = 0 visible_characters = 0
visible_ratio = 0.0 visible_ratio = 0.0
@ -777,23 +777,23 @@ unique_name_in_owner = true
visible = false visible = false
layout_mode = 2 layout_mode = 2
disabled = true disabled = true
text = "Continue" text = "TITLE_CONTINUE"
[node name="NewGame" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"] [node name="NewGame" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"]
visible = false visible = false
layout_mode = 2 layout_mode = 2
text = "New Game" text = "TITLE_NEW_GAME"
[node name="Settings" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"] [node name="Settings" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"]
visible = false visible = false
layout_mode = 2 layout_mode = 2
text = "Settings" text = "PAUSE_SETTINGS"
[node name="Quit" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"] [node name="Quit" type="Button" parent="TitleScreenContent/VBoxContainer/MarginContainer/MenuButtons"]
visible = false visible = false
layout_mode = 2 layout_mode = 2
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "Quit" text = "UI_QUIT"
[node name="AnimationPlayer" type="AnimationPlayer" parent="TitleScreenContent"] [node name="AnimationPlayer" type="AnimationPlayer" parent="TitleScreenContent"]
libraries = { libraries = {

View File

@ -1,17 +1,18 @@
class_name SaveState extends Resource class_name SaveState extends Resource
## Serializable container for gameplay state. ## Serializable container for gameplay state.
const CURRENT_VERSION := 0 const CURRENT_VERSION := 1
const PERSISTENT_GROUP := "Persistent" const PERSISTENT_GROUP := "Persistent"
const SERIALIZE_METHOD := "serialize" const SERIALIZE_METHOD := "serialize"
const DESERIALIZE_METHOD := "deserialize" const DESERIALIZE_METHOD := "deserialize"
@export var save_version := CURRENT_VERSION
@export var level_path: String
@export var persistent_state: Dictionary[String, Dictionary] = {} @export var persistent_state: Dictionary[String, Dictionary] = {}
@export_group("Compatibility Data")
@export var save_version := CURRENT_VERSION
@export var level_path: String
@export var level_version := -1
@export_group("WorldManager State") @export_group("WorldManager State")
@export var grunk_tank_limit: int @export var grunk_tank_limit: int
@export var mp3_player_unlocked: bool @export var mp3_player_unlocked: bool
@ -28,14 +29,34 @@ static func node_key(node: Node, world: World) -> String:
func load_to_world(world: World) -> void: func load_to_world(world: World) -> void:
# Check save compatibility
if save_version != SaveState.CURRENT_VERSION:
push_warning(
"Save state version ",
save_version,
" is incompatible with the current game version ",
SaveState.CURRENT_VERSION
)
return
if level_path != world.current_level_scene.resource_path: if level_path != world.current_level_scene.resource_path:
push_warning( push_warning(
"This save is for ", "Save state for level ",
level_path, level_path,
" but the loaded level is for ", " is incompatible with the current level ",
world.current_level_scene.resource_path world.current_level_scene.resource_path
) )
return
if level_version != world.current_level.version:
push_warning(
"Save state for level version ",
level_version,
" is incompatible with the current level version ",
world.current_level.version
)
# Deserialize world state
world.manager.grunk_tank_limit = grunk_tank_limit world.manager.grunk_tank_limit = grunk_tank_limit
world.manager.mp3_player_unlocked = mp3_player_unlocked world.manager.mp3_player_unlocked = mp3_player_unlocked
world.manager.toothbrush_unlocked = toothbrush_unlocked world.manager.toothbrush_unlocked = toothbrush_unlocked
@ -44,9 +65,8 @@ func load_to_world(world: World) -> void:
world.manager.grunk_vault = grunk_vault world.manager.grunk_vault = grunk_vault
world.manager.alert_level = alert_level world.manager.alert_level = alert_level
var persistent := world.get_tree().get_nodes_in_group(PERSISTENT_GROUP) # Deserialize persistent level nodes
for node: Node in world.get_tree().get_nodes_in_group(PERSISTENT_GROUP):
for node: Node in persistent:
var key := SaveState.node_key(node, world) var key := SaveState.node_key(node, world)
if key in persistent_state: if key in persistent_state:
# Node is in our persistent state, so load it with data. # Node is in our persistent state, so load it with data.
@ -59,8 +79,11 @@ func load_to_world(world: World) -> void:
static func serialize(world: World) -> SaveState: static func serialize(world: World) -> SaveState:
var save := SaveState.new() var save := SaveState.new()
# Serialize compatibility data
save.level_path = world.current_level_scene.resource_path save.level_path = world.current_level_scene.resource_path
save.level_version = world.current_level.version
# Serialize world state
save.grunk_tank_limit = world.manager.grunk_tank_limit save.grunk_tank_limit = world.manager.grunk_tank_limit
save.mp3_player_unlocked = world.manager.mp3_player_unlocked save.mp3_player_unlocked = world.manager.mp3_player_unlocked
save.toothbrush_unlocked = world.manager.toothbrush_unlocked save.toothbrush_unlocked = world.manager.toothbrush_unlocked
@ -69,11 +92,8 @@ static func serialize(world: World) -> SaveState:
save.grunk_vault = world.manager.grunk_vault save.grunk_vault = world.manager.grunk_vault
save.alert_level = world.manager.alert_level save.alert_level = world.manager.alert_level
# NOTE: I'm assuming that `persistent` will have the same order ever time the world is loaded. # Serialize persistent level nodes
# This may not be the case. If so, we need to find a different way to uniquely identify nodes. for node: Node in world.get_tree().get_nodes_in_group(PERSISTENT_GROUP):
var persistent := world.get_tree().get_nodes_in_group(PERSISTENT_GROUP)
for node: Node in persistent:
var key := SaveState.node_key(node, world) var key := SaveState.node_key(node, world)
var data: Dictionary = Callable(node, SERIALIZE_METHOD).call() var data: Dictionary = Callable(node, SERIALIZE_METHOD).call()
save.persistent_state[key] = data save.persistent_state[key] = data

View File

@ -15,12 +15,14 @@ class_name World extends Node
@export var kill_screen_scene: PackedScene @export var kill_screen_scene: PackedScene
@export var save_icon_scene: PackedScene
var current_level_scene: PackedScene var current_level_scene: PackedScene
var current_level: Level var current_level: Level
@onready var level_root: Node3D = %LevelRoot @onready var level_root: Node3D = %LevelRoot
@onready var ui_root: Control = %UIRoot @onready var ui_root: Control = %UIRoot
@onready var save_icon: MarginContainer = %SaveIcon @onready var save_icon_container: MarginContainer = %SaveIconContainer
static var instance: World static var instance: World
@ -88,10 +90,9 @@ func on_game_over() -> void:
func save_progress() -> void: func save_progress() -> void:
print("Preparing save state...") print("Preparing save state...")
save_icon.show() save_icon_container.add_child(save_icon_scene.instantiate())
var save := SaveState.serialize(self) var save := SaveState.serialize(self)
var save_path := current_level.get_save_path() var save_path := current_level.get_save_path()
print("Writing save to ", save_path) print("Writing save to ", save_path)
ResourceSaver.save(save, save_path) ResourceSaver.save(save, save_path)
save_icon.hide()
print("Done!") print("Done!")

View File

@ -15,6 +15,7 @@ spook_manager = ExtResource("3_l0av5")
initial_level = ExtResource("4_5kmgb") initial_level = ExtResource("4_5kmgb")
pause_scene = ExtResource("2_6fy3g") pause_scene = ExtResource("2_6fy3g")
kill_screen_scene = ExtResource("6_l0av5") kill_screen_scene = ExtResource("6_l0av5")
save_icon_scene = ExtResource("7_5kmgb")
[node name="LevelRoot" type="Node3D" parent="."] [node name="LevelRoot" type="Node3D" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
@ -29,16 +30,14 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
[node name="SaveIcon" parent="UIRoot" instance=ExtResource("7_5kmgb")] [node name="SaveIconContainer" type="MarginContainer" parent="UIRoot"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false
layout_mode = 1 layout_mode = 1
anchors_preset = 2 anchors_preset = 2
anchor_top = 1.0 anchor_top = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_top = -140.0 offset_top = -40.0
offset_right = 140.0 offset_right = 40.0
offset_bottom = 0.0
grow_vertical = 0 grow_vertical = 0
theme_override_constants/margin_left = 32 theme_override_constants/margin_left = 32
theme_override_constants/margin_bottom = 32 theme_override_constants/margin_bottom = 32

View File

@ -9,7 +9,7 @@
- [x] Point spray - [x] Point spray
- [x] Wide-angle spray - [x] Wide-angle spray
- [x] Tall-angle spray? - [x] Tall-angle spray?
- [ ] Spray-can? - [ ] #maybe Spray-can
- [x] [[toothbrush]] - [x] [[toothbrush]]
- [ ] [[radar]] - [ ] [[radar]]
- [ ] [[mp3 player]] - [ ] [[mp3 player]]
@ -24,6 +24,7 @@
- [ ] [[tripwire]] - [ ] [[tripwire]]
- [ ] [[looker]] - [ ] [[looker]]
- [x] [[listener]] - [x] [[listener]]
- [ ] [[datapad]]
- [x] [[grunk beast]] - [x] [[grunk beast]]
- [x] Design - [x] Design
- [x] Model - [x] Model
@ -115,6 +116,7 @@
- [x] long crate - [x] long crate
- [x] tall crate - [x] tall crate
- [ ] other scatter - [ ] other scatter
- [ ] external station model
- [ ] [[player ship]] props: - [ ] [[player ship]] props:
- [ ] ship - [ ] ship
- [ ] bunk - [ ] bunk

View File

@ -0,0 +1 @@
Personal datapad. Used for showing text notes.

View File

@ -1,8 +1,8 @@
**Dogberry HK-409 utility cargo depot** is a space warehouse which stores goods for future transport in and around the [[Earth-sphere]]. **Utility Cargo Depot HK-409 "Dogtooth"** is a space warehouse which stores goods for future transport in and around the [[Earth-sphere]].
It is a relatively small warehouse in an ultra-high trans-lunar orbit, mainly servicing eccentric Moon-L2 routes as well as potential routes to interplanetary destinations. Currently the only interplanetary destinations in existence are small research outposts orbiting Mars and Venus, but there's a marginal market for speculation on future interplanetary development that keeps Dogberry operating. It is a relatively small warehouse in an ultra-high trans-lunar orbit, mainly servicing eccentric Moon-L2 routes as well as potential routes to interplanetary destinations. Currently the only interplanetary destinations in existence are small research outposts orbiting Mars and Venus, but there's a marginal market for speculation on future interplanetary development that keeps Dogtooth operating.
Dogberry has a fairly common layout for a high-space cargo depot. It consists of the "wet hold", a massive spindly steel superstructure used to hold cargo containers, as well as a "dry hold" cargo bay, a smaller pressurized warehouse used for storing any goods which might be damaged in the vacuum of space. Attached to the cargo bay is a small crew habitat which houses the permanent on-site staff. Dogtooth has a fairly common layout for a high-space cargo depot. It consists of the "wet hold", a massive spindly steel superstructure used to hold cargo containers, as well as a "dry hold" cargo bay, a smaller pressurized warehouse used for storing any goods which might be damaged in the vacuum of space. Attached to the cargo bay is a small crew habitat which houses the permanent on-site staff.
Everyday station functions are mostly automatic, but a 4-man skeleton crew are stationed permanently on-site to handle maintenance of the logistics machinery. This is a minimum operating requirement to remain insurable. Everyday station functions are mostly automatic, but a 4-man skeleton crew are stationed permanently on-site to handle maintenance of the logistics machinery. This is a minimum operating requirement to remain insurable.

View File

@ -1 +1 @@
The player character. Prior to the start of the game, they have been convicted of some bullshit crime and sentenced to slave labor as a Grunker for the [[Corpo]]. They arrive at [[Dogberry]] HK-409 to find it in disarray, completely covered in grunk. The player character. Prior to the start of the game, they have been convicted of some bullshit crime and sentenced to slave labor as a Grunker for the [[Corpo]]. They arrive at [[Dogtooth]] HK-409 to find it in disarray, completely covered in grunk.

View File

@ -1,4 +1,4 @@
*Brooks:* "... Autopsy report: April 19 2448, subject is... Sorry, attending examiner LaMarr Brooks, staff EMT for Dogberry HK-409 cargo depot. Haven't done one of these in a while. Subject is... Identified as, ah, Frank Tannenbaum, staff supervisor for Dogberry HK-409 cargo depot..." *Brooks:* "... Autopsy report: April 19 2448, subject is... Sorry, attending examiner LaMarr Brooks, staff EMT for Utility Cargo Depot HK-409 "Dogtooth". Haven't done one of these in a while. Subject is... Identified as, ah, Frank Tannenbaum, staff supervisor for Utility Cargo Depot HK-409 "Dogtooth"..."
"The body is that of a well-developed male with, ah, extensive traumatic injuries to the hands and periorbital regions. Livor mortis is fixed and consistent with the supine position, rigor mortis is present in the facial musculature and upper extremities. Estimated time of death is... Strike that, observed time of death is approximately 10 minutes prior to exam." "The body is that of a well-developed male with, ah, extensive traumatic injuries to the hands and periorbital regions. Livor mortis is fixed and consistent with the supine position, rigor mortis is present in the facial musculature and upper extremities. Estimated time of death is... Strike that, observed time of death is approximately 10 minutes prior to exam."

View File

@ -1,6 +1,6 @@
Captain's log. April 18, 2448. Captain's log. April 18, 2448.
My name is Captain Frank Tannenbaum. I am the commander and, by all indications, **sole survivor of the Dogberry HK-409 Cargo Depot**. My name is Captain Frank Tannenbaum. I am the commander and, by all indications, **sole survivor of the UCD HK-409 Dogtooth**.
Approximately three weeks ago we came into contact with an "alien organism", a form of "life" utterly unlike anything previously found in the Earth-sphere. It is unbelievably dangerous to humanoid life. It does not feel, but it does "think". It is a cunning hyper-predator, a wretched biological "machine" built for the sole purpose of murder. It somehow managed to evade our security protocols, infiltrating my own "crew" and infesting the entire station from within. _You must believe me when I tell you that if this life-form is allowed to escape this station, **all life on Earth is doomed**._ This is an "extinction-level event". Approximately three weeks ago we came into contact with an "alien organism", a form of "life" utterly unlike anything previously found in the Earth-sphere. It is unbelievably dangerous to humanoid life. It does not feel, but it does "think". It is a cunning hyper-predator, a wretched biological "machine" built for the sole purpose of murder. It somehow managed to evade our security protocols, infiltrating my own "crew" and infesting the entire station from within. _You must believe me when I tell you that if this life-form is allowed to escape this station, **all life on Earth is doomed**._ This is an "extinction-level event".
@ -13,7 +13,7 @@ As I am writing this, I am currently preparing to destroy this station and wipe
I recognize the "gravity" of what I ask, and I do not ask this lightly. I am sorry that fate brought you here, to this baleful place. I wish it had not. Please understand that this is simply the only way to be sure. I recognize the "gravity" of what I ask, and I do not ask this lightly. I am sorry that fate brought you here, to this baleful place. I wish it had not. Please understand that this is simply the only way to be sure.
I have not identified the precise method by which "the adversary" arrived on this station, but deep-space theta-ray bursts seem a likely suspect as per usual. The first indication of trouble was the appearance of a strange creature in the medbay, suspended in a small tank. When I confronted my chief medical officer Doctor Brooks, he claimed it was simply a "pet" which he'd shipped from a previous posting on L2 -- a story which immediately drew my suspicion. Looking back, the doctor's effete manner makes him a natural point of ingress for a being which preys on the mentally-weak. I have not identified the precise method by which "the adversary" arrived on this station, but deep-space theta-ray bursts seem a likely suspect as per usual. The first indication of trouble was the appearance of a strange creature in the medbay, suspended in a small tank. My chief medical officer Doctor Brooks claimed this creature was simply a "pet" which he'd brought from his previous posting on L2 -- a story which naturally drew my suspicion. Looking back, the doctor's effete manner makes him a natural point of ingress for a being which preys on the mentally-weak.
I do not know why I alone appear to be immune to the influence of "the adversary". I am certain it could overpower me if it really tried -- perhaps for the time being it is simply "playing with its food". My "luck" will surely run out soon. It stands to reason, however, that as the strongest, most "physically-optimal" member of the crew, my body may simply possess a natural resistance to "alien infection". One month ago, I began taking daily "natural enhancement supplements", which have improved my "confidence" immensely and may have inadvertently given my body a "superhuman" resistance to foreign influence. I do not know why I alone appear to be immune to the influence of "the adversary". I am certain it could overpower me if it really tried -- perhaps for the time being it is simply "playing with its food". My "luck" will surely run out soon. It stands to reason, however, that as the strongest, most "physically-optimal" member of the crew, my body may simply possess a natural resistance to "alien infection". One month ago, I began taking daily "natural enhancement supplements", which have improved my "confidence" immensely and may have inadvertently given my body a "superhuman" resistance to foreign influence.

View File

@ -5,14 +5,14 @@
{"id":"76bfb48faf224a72","type":"text","text":"# [[Hayes]]","x":-2320,"y":1067,"width":250,"height":60}, {"id":"76bfb48faf224a72","type":"text","text":"# [[Hayes]]","x":-2320,"y":1067,"width":250,"height":60},
{"id":"c06549a773927f30","type":"text","text":"### [[Brooks]] discovers [[Tannenbaum]]\n\n\nConcerned over [[Lundy]]'s disappearance, Brooks searches the station and discovers Tannenbaum, who desperately pleads to Brooks to destroy his eyes as he bleeds out in the cargo bay. Brooks carries Tannenbaum to the [[medbay]] but is too late to save him. He performs an ad-hoc autopsy and details Tannenbaum's condition in an [[autopsy report]].","x":320,"y":1300,"width":390,"height":295}, {"id":"c06549a773927f30","type":"text","text":"### [[Brooks]] discovers [[Tannenbaum]]\n\n\nConcerned over [[Lundy]]'s disappearance, Brooks searches the station and discovers Tannenbaum, who desperately pleads to Brooks to destroy his eyes as he bleeds out in the cargo bay. Brooks carries Tannenbaum to the [[medbay]] but is too late to save him. He performs an ad-hoc autopsy and details Tannenbaum's condition in an [[autopsy report]].","x":320,"y":1300,"width":390,"height":295},
{"id":"2a39b82e5a87236c","type":"text","text":"# [[Brooks]]","x":-2320,"y":1417,"width":250,"height":60}, {"id":"2a39b82e5a87236c","type":"text","text":"# [[Brooks]]","x":-2320,"y":1417,"width":250,"height":60},
{"id":"a3cb91ffcdae0e70","type":"text","text":"### [[Grunk]] arrives on [[Dogberry]]\n[[Lundy]] buys a bag of ketchup chips off the internet, shipped from L1. When the bag arrives he finds his chips \"covered in weird blue shit\" and tosses the bag behind a container in the [[cargo bay]]. He files a [complaint](lundy%20complaint.md) with the vendor.","x":-1960,"y":671,"width":360,"height":259}, {"id":"a3cb91ffcdae0e70","type":"text","text":"### [[Grunk]] arrives on [[Dogtooth]]\n[[Lundy]] buys a bag of ketchup chips off the internet, shipped from L1. When the bag arrives he finds his chips \"covered in weird blue shit\" and tosses the bag behind a container in the [[cargo bay]]. He files a [complaint](lundy%20complaint.md) with the vendor.","x":-1960,"y":671,"width":360,"height":259},
{"id":"7017f4ae48a53413","type":"text","text":"### [[Brooks]] takes a spacewalk\n\nBrooks attempts a spacewalk to break through a window into [[ops center|Ops]] as a last-ditch attempt to save the station. He runs out of air and dies, leaving a [[airlock note|note]] in the airlock giving a terse explanation of what's happened.","x":880,"y":1323,"width":398,"height":250}, {"id":"7017f4ae48a53413","type":"text","text":"### [[Brooks]] takes a spacewalk\n\nBrooks attempts a spacewalk to break through a window into [[ops center|Ops]] as a last-ditch attempt to save the station. He runs out of air and dies, leaving a [[airlock note|note]] in the airlock giving a terse explanation of what's happened.","x":880,"y":1323,"width":398,"height":250},
{"id":"81953d7797166088","type":"text","text":"### [[Dogberry]] hits the O2 tanks\n\nContrary to [[Tannenbaum]]'s plan, the tank does not explode when ruptured. The resulting impact throws some things around, leaving the station in disarray.","x":1447,"y":1732,"width":333,"height":248}, {"id":"81953d7797166088","type":"text","text":"### [[Dogtooth]] hits the O2 tanks\n\nContrary to [[Tannenbaum]]'s plan, the tank does not explode when ruptured. The resulting impact throws some things around, leaving the station in disarray.","x":1447,"y":1732,"width":333,"height":248},
{"id":"740cf013984ca68d","type":"text","text":"### The grunk takes over\n\nWith everyone on the station dead, the grunk slowly expands out of the cargo bay and infests the station. Progress of the infestation is detailed in the [[system journal]]","x":1980,"y":1750,"width":330,"height":212}, {"id":"740cf013984ca68d","type":"text","text":"### The grunk takes over\n\nWith everyone on the station dead, the grunk slowly expands out of the cargo bay and infests the station. Progress of the infestation is detailed in the [[system journal]]","x":1980,"y":1750,"width":330,"height":212},
{"id":"d2a9a71db8cc5cc9","type":"text","text":"# [[Grunker]]","x":-2320,"y":2200,"width":250,"height":60}, {"id":"d2a9a71db8cc5cc9","type":"text","text":"# [[Grunker]]","x":-2320,"y":2200,"width":250,"height":60},
{"id":"2d1e50c411d21a83","type":"text","text":"### Discovery of the [[Dogberry]]\n\nPresumably the derelict Dogberry is discovered months or even years later. Recovery is licensed to the Corpo, who send the [[Grunker]] in to clean up.","x":2460,"y":1757,"width":420,"height":198}, {"id":"2d1e50c411d21a83","type":"text","text":"### Discovery of the [[Dogtooth]]\n\nPresumably the derelict Dogtooth is discovered months or even years later. Recovery is licensed to the Corpo, who send the [[Grunker]] in to clean up.","x":2460,"y":1757,"width":420,"height":198},
{"id":"688b0fbe65e6a7b1","type":"text","text":"### Player gets sentenced\n\nThe game opens with a judge sentencing the player. They get to decide their own sentence: death or _Grunk_.\n\n(If they pick death, the game just ends LMAO)","x":1985,"y":2103,"width":320,"height":255}, {"id":"688b0fbe65e6a7b1","type":"text","text":"### Player gets sentenced\n\nThe game opens with a judge sentencing the player. They get to decide their own sentence: death or _Grunk_.\n\n(If they pick death, the game just ends LMAO)","x":1985,"y":2103,"width":320,"height":255},
{"id":"baac95d571b90296","type":"text","text":"### [[Grunker]] arrives on the [[Dogberry]]\n\nGame starts here!","x":3040,"y":2141,"width":347,"height":179}, {"id":"baac95d571b90296","type":"text","text":"### [[Grunker]] arrives on the [[Dogtooth]]\n\nGame starts here!","x":3040,"y":2141,"width":347,"height":179},
{"id":"139195c903259da8","type":"text","text":"### [[Hayes]] confronts [[Tannenbaum]]","x":-1045,"y":987,"width":265,"height":220}, {"id":"139195c903259da8","type":"text","text":"### [[Hayes]] confronts [[Tannenbaum]]","x":-1045,"y":987,"width":265,"height":220},
{"id":"0f5d3d8b3ad5b537","type":"text","text":"### [[Tannenbaum]] begins taking sketchy supplements\n\nProfoundly insecure in his masculinity, Tannenbaum orders sketchy supplements off the internet. They turn out to be basically amphetamines, and he rapidly succumbs to psychosis. Over the next few weeks he becomes increasingly delusional and paranoid. It all comes to a head when he becomes convinced that [[Hayes]]'s pet shrimp is host to an alien parasite body-snatcher a la _The Thing_. He believes he has discovered a grand conspiracy in which everyone on the crew but him has been replaced by alien shapeshifters (as detailed in his [[manifesto]]), and he decides to take action.","x":-1980,"y":22,"width":400,"height":488}, {"id":"0f5d3d8b3ad5b537","type":"text","text":"### [[Tannenbaum]] begins taking sketchy supplements\n\nProfoundly insecure in his masculinity, Tannenbaum orders sketchy supplements off the internet. They turn out to be basically amphetamines, and he rapidly succumbs to psychosis. Over the next few weeks he becomes increasingly delusional and paranoid. It all comes to a head when he becomes convinced that [[Hayes]]'s pet shrimp is host to an alien parasite body-snatcher a la _The Thing_. He believes he has discovered a grand conspiracy in which everyone on the crew but him has been replaced by alien shapeshifters (as detailed in his [[manifesto]]), and he decides to take action.","x":-1980,"y":22,"width":400,"height":488},
{"id":"b513bb88c1ca09df","type":"text","text":"### [[Lundy]] disappears\n\nGets trapped inside a locked cargo container in the [[cargo bay]] & presumably suffocates.","x":-1062,"y":671,"width":300,"height":259}, {"id":"b513bb88c1ca09df","type":"text","text":"### [[Lundy]] disappears\n\nGets trapped inside a locked cargo container in the [[cargo bay]] & presumably suffocates.","x":-1062,"y":671,"width":300,"height":259},