refactor locations; save system wip

This commit is contained in:
Rowan 2025-05-10 00:07:50 -05:00
parent 8cf3ae2686
commit 10888b0768
66 changed files with 400 additions and 206 deletions

View file

@ -18,16 +18,20 @@ config/icon="res://icon.svg"
[autoload]
AsyncResourceHandler="*res://src/async_resource_handler.ts"
AsyncResourceHandler="*res://src/global/async_resource_handler.ts"
Console="*res://addons/dev_console/console.tscn"
DebugDraw="*res://src/debug_draw.ts"
MessageBus="*res://src/message_bus.ts"
SaveSerializer="*res://src/save_serializer.ts"
DebugDraw="*res://src/global/debug_draw.ts"
MessageBus="*res://src/global/message_bus.ts"
SaveSerializer="*res://src/save_system/save_serializer.ts"
[editor]
script/search_in_file_extensions=PackedStringArray("gd", "gdshader", "ts")
[global_group]
save=""
[input]
move_left={

View file

@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="Weapon" load_steps=2 format=3 uid="uid://c15pu3ela0g6k"]
[ext_resource type="Script" uid="uid://tv71bu1y658n" path="res://src/weapon.ts" id="1_bp18i"]
[ext_resource type="Script" uid="uid://tv71bu1y658n" path="res://src/item/weapon.ts" id="1_bp18i"]
[resource]
script = ExtResource("1_bp18i")
@ -8,8 +8,8 @@ name = &"Handgun"
description = ""
max_quantity = 99
scene = "uid://ddgak6clk2i2p"
range = 100.0
damage = 1.0
range = 25.0
fire_rate = 0.5
capacity = 9
damage = 1.0
metadata/_custom_type_script = "uid://tv71bu1y658n"

View file

@ -1,6 +1,6 @@
[gd_resource type="Resource" script_class="Item" load_steps=2 format=3 uid="uid://5odv3n0dp2nn"]
[ext_resource type="Script" uid="uid://tgca2vcp2tt4" path="res://src/item.ts" id="1_bbxre"]
[ext_resource type="Script" uid="uid://tgca2vcp2tt4" path="res://src/item/item.ts" id="1_bbxre"]
[resource]
script = ExtResource("1_bbxre")

View file

@ -3,11 +3,11 @@
[ext_resource type="PackedScene" uid="uid://cersx8w4ps2sr" path="res://scenes/player.tscn" id="1_uum5p"]
[ext_resource type="Script" uid="uid://cgbd6grygtxvj" path="res://src/main_camera.ts" id="2_0n32s"]
[ext_resource type="PackedScene" uid="uid://cwrgvwx3lfwf6" path="res://assets/level.glb" id="2_i3oty"]
[ext_resource type="Script" uid="uid://cyll634iylhqg" path="res://src/interactable.ts" id="3_dt0nx"]
[ext_resource type="Script" uid="uid://cyll634iylhqg" path="res://src/interactable/interactable.ts" id="3_dt0nx"]
[ext_resource type="Script" uid="uid://1w0aiix6vgbc" path="res://src/interactable/door.ts" id="3_imxmk"]
[ext_resource type="Resource" uid="uid://5odv3n0dp2nn" path="res://resources/items/key.tres" id="4_3rynd"]
[ext_resource type="Script" uid="uid://c7s3k1qy8pg3s" path="res://src/item_pickup.ts" id="6_qe67v"]
[ext_resource type="Script" uid="uid://bccrrtp3mlw5i" path="res://src/health.ts" id="8_cptob"]
[ext_resource type="Script" uid="uid://c7s3k1qy8pg3s" path="res://src/item/item_pickup.ts" id="6_qe67v"]
[ext_resource type="Script" uid="uid://bccrrtp3mlw5i" path="res://src/entity/health.ts" id="8_cptob"]
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_y41n8"]

View file

@ -1,12 +1,13 @@
[gd_scene load_steps=34 format=3 uid="uid://cersx8w4ps2sr"]
[gd_scene load_steps=35 format=3 uid="uid://cersx8w4ps2sr"]
[ext_resource type="Script" uid="uid://dxiv1svdeoxg2" path="res://src/player.ts" id="2_pdrhn"]
[ext_resource type="Script" uid="uid://hm6oqbvcmigk" path="res://src/player_animation.ts" id="3_26yay"]
[ext_resource type="Script" uid="uid://ny0p0jkmlrv8" path="res://src/player_input.ts" id="3_x6527"]
[ext_resource type="Script" uid="uid://kjthk4tj8qof" path="res://src/interactor.ts" id="5_uk7c1"]
[ext_resource type="Script" uid="uid://dkvbawcldnok5" path="res://src/equipped_weapon.ts" id="6_fjrip"]
[ext_resource type="Script" uid="uid://dbrjcvaqkca21" path="res://src/inventory.ts" id="6_jscba"]
[ext_resource type="Script" uid="uid://dxiv1svdeoxg2" path="res://src/entity/player.ts" id="2_pdrhn"]
[ext_resource type="Script" uid="uid://hm6oqbvcmigk" path="res://src/entity/player_animation.ts" id="3_26yay"]
[ext_resource type="Script" uid="uid://ny0p0jkmlrv8" path="res://src/entity/player_input.ts" id="3_x6527"]
[ext_resource type="Script" uid="uid://kjthk4tj8qof" path="res://src/entity/interactor.ts" id="5_uk7c1"]
[ext_resource type="Script" uid="uid://dkvbawcldnok5" path="res://src/entity/equipped_weapon.ts" id="6_fjrip"]
[ext_resource type="Script" uid="uid://dbrjcvaqkca21" path="res://src/entity/inventory.ts" id="6_jscba"]
[ext_resource type="PackedScene" uid="uid://sgc1gxq4osag" path="res://scenes/player_mesh3.tscn" id="7_fjrip"]
[ext_resource type="Script" uid="uid://bccrrtp3mlw5i" path="res://src/entity/health.ts" id="8_smehm"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1kx10"]
@ -126,13 +127,144 @@ states/Unarmed/node = SubResource("AnimationNodeBlendSpace1D_7bso7")
states/Unarmed/position = Vector2(405, 89)
transitions = ["Start", "Unarmed", SubResource("AnimationNodeStateMachineTransition_cqw7m"), "Unarmed", "Handgun", SubResource("AnimationNodeStateMachineTransition_fjrip"), "Handgun", "Unarmed", SubResource("AnimationNodeStateMachineTransition_smehm"), "Start", "Handgun", SubResource("AnimationNodeStateMachineTransition_ur7pv")]
[node name="Player" type="CharacterBody3D"]
[node name="Player" type="CharacterBody3D" groups=["save"]]
script = ExtResource("2_pdrhn")
metadata/_custom_type_script = "uid://dxiv1svdeoxg2"
[node name="Mesh" parent="." instance=ExtResource("7_fjrip")]
[node name="Skeleton3D" parent="Mesh/AuxScene/Scene/Armature" index="0"]
bones/0/position = Vector3(-1.74887, 91.964, -0.714319)
bones/0/rotation = Quaternion(0.165541, -0.00688532, -0.0607779, 0.984304)
bones/1/position = Vector3(-5.73688e-07, -9.14512e-06, -1.26241e-06)
bones/2/rotation = Quaternion(-0.05258, -0.0688082, 0.0497405, 0.995001)
bones/3/position = Vector3(9.31323e-07, 4.82425e-06, 3.63451e-07)
bones/4/rotation = Quaternion(0.0222017, -0.0213405, 0.0167214, 0.999386)
bones/5/position = Vector3(-1.93715e-07, -5.33462e-06, -3.29316e-06)
bones/6/rotation = Quaternion(0.0349434, -0.0206503, 0.0167813, 0.999035)
bones/7/position = Vector3(7.1526e-07, -1.93864e-05, -4.64916e-06)
bones/8/rotation = Quaternion(-0.51501, 0.549661, -0.50159, -0.425494)
bones/9/position = Vector3(4.03821e-06, 2.32458e-06, -8.17219e-06)
bones/10/position = Vector3(-1.57305e-06, 10.8382, 6.06992e-05)
bones/10/rotation = Quaternion(0.394962, -0.363464, -0.0296416, 0.84322)
bones/11/position = Vector3(4.29218e-06, -5.19505e-06, 6.01416e-06)
bones/12/rotation = Quaternion(-1.62516e-07, 3.07216e-08, -0.699315, 0.714814)
bones/13/position = Vector3(-8.38905e-06, 3.33295e-06, 2.61253e-06)
bones/14/position = Vector3(-3.8591e-06, 28.3289, 4.04304e-06)
bones/14/rotation = Quaternion(-0.136096, 0.156416, -0.0262674, 0.977917)
bones/15/position = Vector3(3.578e-06, -5.33786e-07, -1.20217e-06)
bones/16/rotation = Quaternion(0.585477, -0.0341162, 0.207056, 0.783058)
bones/17/position = Vector3(-5.2056e-06, -2.74507e-06, 2.17486e-07)
bones/18/position = Vector3(-4.54572e-06, 3.60001, -4.16853e-05)
bones/18/rotation = Quaternion(0.507655, 1.75089e-07, 0.0413582, 0.860568)
bones/19/position = Vector3(5.70967e-07, -7.07246e-06, -9.18487e-06)
bones/20/rotation = Quaternion(0.413244, -3.11702e-08, 0.0336662, 0.909998)
bones/21/position = Vector3(-6.99836e-06, -7.14697e-06, -2.1594e-05)
bones/22/position = Vector3(6.30171e-07, 2.11579, -2.0168e-05)
bones/23/position = Vector3(2.23517e-06, -3.43425e-06, -5.65971e-06)
bones/24/position = Vector3(-2.38966e-05, 9.5325, 4.5051e-05)
bones/24/rotation = Quaternion(0.668999, -0.000171639, 0.0840584, 0.738495)
bones/25/position = Vector3(-6.4569e-06, 7.69735e-06, 1.93997e-05)
bones/26/position = Vector3(-1.6476e-05, 3.70001, -1.55696e-05)
bones/26/rotation = Quaternion(0.496633, -8.3819e-09, 0.0404601, 0.867017)
bones/27/position = Vector3(-1.2354e-05, -8.65261e-06, -1.28768e-05)
bones/28/position = Vector3(-4.24592e-06, 2.95001, -9.53893e-06)
bones/28/rotation = Quaternion(0.411496, 1.23197e-07, 0.033524, 0.910795)
bones/31/position = Vector3(-7.56234e-07, 7.47032e-06, -1.1809e-05)
bones/32/rotation = Quaternion(0.665959, -0.0134166, 0.0985026, 0.739335)
bones/33/position = Vector3(-6.06578e-06, 1.30555e-07, 1.69121e-05)
bones/34/position = Vector3(-4.23319e-06, 3.37928, 2.18956e-06)
bones/34/rotation = Quaternion(0.505618, -7.26432e-08, 0.0411921, 0.861774)
bones/36/position = Vector3(-1.18398e-05, 2.88968, 7.61242e-06)
bones/36/rotation = Quaternion(0.404281, -1.18074e-07, 0.032936, 0.914042)
bones/37/position = Vector3(-6.88349e-06, -1.37002e-05, 1.28552e-05)
bones/38/position = Vector3(2.56333e-07, 2.63883, -1.43249e-05)
bones/39/position = Vector3(-4.24683e-07, 1.26579e-05, -1.86824e-05)
bones/40/position = Vector3(2.25984, 9.10828, 0.517869)
bones/40/rotation = Quaternion(0.664054, 0.0213635, 0.0331529, 0.746644)
bones/41/position = Vector3(1.06017e-06, 7.42728e-06, 1.53654e-05)
bones/42/rotation = Quaternion(0.495834, 1.76951e-08, 0.040395, 0.867477)
bones/43/position = Vector3(-7.02744e-06, 7.93018e-06, 5.25886e-07)
bones/44/rotation = Quaternion(0.412061, -1.75642e-08, 0.0335699, 0.910538)
bones/45/position = Vector3(-2.85069e-06, 1.82002e-06, -3.29077e-06)
bones/48/position = Vector3(2.68185, 2.4648, 1.57399)
bones/48/rotation = Quaternion(0.269242, 0.0131664, -0.225248, 0.936269)
bones/49/position = Vector3(-9.0003e-06, -5.24521e-06, 4.05612e-06)
bones/50/position = Vector3(-1.37213e-05, 4.18898, 1.30866e-06)
bones/50/rotation = Quaternion(-0.0573431, -0.0228523, 0.23602, 0.969786)
bones/52/position = Vector3(-1.33398e-07, 3.41628, -7.38503e-06)
bones/52/rotation = Quaternion(-0.0951258, 0.0335536, -0.103347, 0.989517)
bones/53/position = Vector3(-1.13845e-05, 6.58631e-06, -2.40871e-06)
bones/56/rotation = Quaternion(-0.0477065, -0.00417029, 0.0378131, 0.998137)
bones/57/position = Vector3(-1.43051e-06, -1.90437e-05, -4.97699e-06)
bones/58/rotation = Quaternion(-0.0436575, 0.0735426, -0.040091, 0.995529)
bones/59/position = Vector3(3.57626e-07, -2.48402e-05, -4.05312e-06)
bones/61/rotation = Quaternion(-0.583618, -0.494192, 0.510115, -0.393634)
bones/62/position = Vector3(-9.28342e-06, -1.90735e-06, -1.25186e-05)
bones/63/position = Vector3(-6.21295e-06, 10.8377, 3.97934e-05)
bones/63/rotation = Quaternion(0.347965, 0.00733927, 0.168363, 0.922237)
bones/64/position = Vector3(-6.81842e-07, 2.49492e-06, -4.24376e-06)
bones/65/position = Vector3(9.00633e-07, 27.8415, 2.65277e-05)
bones/65/rotation = Quaternion(-1.72295e-07, -8.69017e-09, 0.856711, 0.515797)
bones/66/position = Vector3(1.81322e-06, -7.35846e-06, -1.91975e-06)
bones/67/rotation = Quaternion(-0.00529397, 0.0445442, 0.140527, 0.98906)
bones/68/position = Vector3(-2.66702e-06, 6.75935e-06, 1.82044e-06)
bones/69/rotation = Quaternion(0.247973, -0.0477907, 0.214491, 0.943514)
bones/70/position = Vector3(1.54972e-06, 1.10865e-05, 1.50938e-05)
bones/71/position = Vector3(-9.48468e-06, 4.18709, 6.46207e-06)
bones/71/rotation = Quaternion(-0.0402928, 0.0170358, -0.189645, 0.980878)
bones/72/position = Vector3(4.76837e-06, -6.58631e-06, -1.79932e-05)
bones/73/position = Vector3(3.41813e-06, 3.41839, -3.61406e-05)
bones/73/rotation = Quaternion(-0.150634, 0.0196685, -0.0384138, 0.987647)
bones/74/position = Vector3(6.76513e-06, 4.05312e-06, -9.94626e-07)
bones/75/position = Vector3(1.19018e-05, 2.58059, -2.38419e-06)
bones/77/rotation = Quaternion(0.348536, -0.00216019, 0.00770235, 0.937261)
bones/78/position = Vector3(1.02476e-05, 1.25725e-06, -3.09773e-06)
bones/79/position = Vector3(1.16544e-05, 3.7, -2.38235e-06)
bones/79/rotation = Quaternion(0.316306, -4.82716e-07, -0.0324142, 0.948103)
bones/80/position = Vector3(-4.95017e-06, 2.76518e-06, -1.62069e-05)
bones/81/rotation = Quaternion(0.316011, -2.94298e-07, -0.0323837, 0.948203)
bones/84/position = Vector3(2.00886e-06, -1.55707e-05, 1.65336e-05)
bones/85/rotation = Quaternion(0.3862, -0.00842687, -0.0564331, 0.920648)
bones/86/position = Vector3(-1.63967e-06, 3.17085e-06, -1.61075e-05)
bones/87/rotation = Quaternion(0.360214, -3.91738e-07, -0.0369136, 0.932139)
bones/88/position = Vector3(1.00015e-05, -1.164e-05, -9.66649e-07)
bones/89/rotation = Quaternion(0.359928, -5.00586e-07, -0.0368843, 0.932251)
bones/90/position = Vector3(1.66535e-06, -8.3745e-06, -2.99188e-06)
bones/92/position = Vector3(7.46036e-06, 4.84443e-06, -1.74526e-06)
bones/93/rotation = Quaternion(0.430639, 0.00721853, -0.0951741, 0.897463)
bones/94/position = Vector3(8.98751e-07, 5.35152e-06, -6.46659e-07)
bones/95/rotation = Quaternion(0.410222, -5.0664e-07, -0.0420382, 0.911016)
bones/96/position = Vector3(4.96211e-06, -1.72045e-06, 3.28761e-06)
bones/97/position = Vector3(6.86945e-06, 2.95, 1.36588e-05)
bones/97/rotation = Quaternion(0.408306, -5.79283e-07, -0.0418419, 0.911886)
bones/98/position = Vector3(-3.15467e-07, 5.01274e-06, 4.26586e-07)
bones/99/position = Vector3(-2.55906e-05, 2.64431, 3.08781e-07)
bones/100/position = Vector3(-1.24937e-06, -5.10697e-06, 1.54605e-05)
bones/101/position = Vector3(3.80626, 8.0778, 0.486897)
bones/101/rotation = Quaternion(0.453869, -0.00504631, -0.172035, 0.874289)
bones/102/position = Vector3(-3.4095e-06, 9.84711e-06, 2.19628e-05)
bones/103/rotation = Quaternion(0.441888, -4.63799e-07, -0.0452826, 0.895927)
bones/104/position = Vector3(2.55811e-06, 3.79583e-06, -1.0634e-05)
bones/105/rotation = Quaternion(0.441703, -4.99189e-07, -0.0452642, 0.896019)
bones/106/position = Vector3(-6.14417e-06, -1.13954e-05, 1.6547e-05)
bones/107/position = Vector3(-5.6429e-06, 2.12554, -1.44594e-05)
bones/108/position = Vector3(-9.07481e-06, 9.92674e-06, -5.45842e-07)
bones/109/rotation = Quaternion(0.00912616, 0.499102, 0.859827, -0.107291)
bones/110/position = Vector3(-8.46157e-07, 2.16948e-05, -6.41459e-07)
bones/111/rotation = Quaternion(-0.422236, 0.017693, 0.0133394, 0.906215)
bones/112/position = Vector3(-6.62415e-08, 3.47755e-06, 3.22101e-06)
bones/113/rotation = Quaternion(0.540386, -0.024196, -0.0396063, 0.840136)
bones/115/rotation = Quaternion(0.351334, 3.47398e-05, 1.17676e-05, 0.93625)
bones/119/rotation = Quaternion(0.102035, 0.0420148, 0.992102, -0.0596471)
bones/120/position = Vector3(-1.94257e-07, 7.09908e-06, -4.76177e-06)
bones/121/rotation = Quaternion(-0.339418, 0.0221456, -0.00134624, 0.940374)
bones/122/position = Vector3(-4.23867e-07, -3.05474e-06, -1.07518e-06)
bones/123/rotation = Quaternion(0.426336, -0.0330713, -0.134284, 0.89393)
bones/125/rotation = Quaternion(0.631409, 0.0537683, 0.0373258, 0.772682)
[node name="RightHand" parent="Mesh/AuxScene/Scene/Armature/Skeleton3D" index="2"]
transform = Transform3D(0.678924, 0.327226, -0.657255, 0.571861, 0.325755, 0.752899, 0.460473, -0.88702, 0.0340353, -5.60212, 89.2255, 32.2557)
transform = Transform3D(0.339146, 0.643904, -0.685833, 0.44957, 0.529466, 0.71941, 0.826357, -0.552315, -0.109914, -21.7915, 84.1795, 2.81691)
[node name="Node3D" type="Node3D" parent="Mesh/AuxScene/Scene/Armature/Skeleton3D/RightHand" index="0"]
@ -143,12 +275,14 @@ shape = SubResource("CapsuleShape3D_1kx10")
[node name="Input" type="Node3D" parent="."]
script = ExtResource("3_x6527")
min_range = 0.5
metadata/_custom_type_script = "uid://ny0p0jkmlrv8"
[node name="Interactor" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 1)
input_ray_pickable = false
script = ExtResource("5_uk7c1")
_root_node = NodePath("..")
metadata/_custom_type_script = "uid://kjthk4tj8qof"
[node name="CollisionShape3D" type="CollisionShape3D" parent="Interactor"]
shape = SubResource("SphereShape3D_64co4")
@ -177,4 +311,8 @@ metadata/_custom_type_script = "uid://dkvbawcldnok5"
[node name="FireRate" type="Timer" parent="EquippedWeapon"]
one_shot = true
[node name="Health" type="Node" parent="."]
script = ExtResource("8_smehm")
metadata/_custom_type_script = "uid://bccrrtp3mlw5i"
[editable path="Mesh"]

View file

@ -1,5 +1,5 @@
import { float64, Node, Node3D, NodePath, PackedStringArray, Variant } from 'godot'
import { Damageable } from './health'
import { float64, Node, Node3D, NodePath, Variant } from 'godot'
import { Damageable } from './entity/health'
import { export_ } from 'godot.annotations'
export default class DamageSource extends Node3D {

View file

@ -1,11 +1,11 @@
import { Color, GArray, Node, Node3D, PackedScene, PackedStringArray, PhysicsBody3D, PhysicsRayQueryParameters3D, Signal1, Timer, Vector3 } from 'godot'
import { Color, GArray, Node, Node3D, PackedScene, PhysicsBody3D, PhysicsRayQueryParameters3D, Signal1, Timer, Vector3 } from 'godot'
import { export_file, signal } from 'godot.annotations'
import { export_node, onready } from './annotations'
import Weapon from './weapon'
import AsyncResourceHandler from './async_resource_handler'
import { forward } from './vec'
import DebugDraw from './debug_draw'
import { find_in_anscestors, find_in_descendents, has_method } from './node'
import { export_node, onready } from '../extension/annotations'
import Weapon from '../item/weapon'
import AsyncResourceHandler from '../global/async_resource_handler'
import { forward } from '../extension/vec'
import DebugDraw from '../global/debug_draw'
import { find_in_ancestors, find_in_descendents, has_method } from '../extension/node'
import { Damageable } from './health'
const { MULTIPLY: mul, ADD: add } = Vector3
@ -85,7 +85,7 @@ export default class EquippedWeapon extends Node3D {
if (!result.is_empty()) {
DebugDraw.draw_ray(query, Color.DARK_RED)
const root = find_in_anscestors(result.get('collider'), n => n instanceof PhysicsBody3D)
const root = find_in_ancestors(result.get('collider'), n => n instanceof PhysicsBody3D)
const health = find_in_descendents<Node & Damageable>(root, n => has_method('apply_damage', n))
if (health != null) {
health.apply_damage(this, this._equipped_weapon.damage)

View file

@ -16,6 +16,14 @@ export default class Health extends Node implements Damageable, Recoverable {
@export_(Variant.Type.TYPE_FLOAT)
private _current_health: number = 0
get current_health() {
return this._current_health
}
set current_health(value: number) {
this._current_health = value
}
_ready() {
this._current_health = this._max_health
}

View file

@ -1,6 +1,6 @@
import { Area3D, Callable, Node, NodePath, Variant } from 'godot'
import Interactable from './interactable'
import { export_ } from 'godot.annotations'
import Interactable from '../interactable/interactable'
class InteractableDistance {
static Empty = new InteractableDistance(Infinity)

View file

@ -1,11 +1,9 @@
import { clampi, Node, StringName } from 'godot'
import ItemData from './item'
import { GArrayEnumerator } from './collection/enumerable'
import { Enumerable } from '../addons/enumerable-ts/src/index'
import { find_in_descendents } from './node'
import Item from '../item/item'
import { find_in_descendents } from '../extension/node'
class ItemInstance {
readonly resource: ItemData
readonly resource: Item
get name() {
return this.resource.name
@ -17,7 +15,7 @@ class ItemInstance {
quantity: number
constructor(resource: ItemData, quantity: number = 1) {
constructor(resource: Item, quantity: number = 1) {
this.resource = resource
this.quantity = quantity
}
@ -58,7 +56,7 @@ export default class Inventory extends Node {
return inventory
}
add(item: ItemData, quantity: number = 1) {
add(item: Item, quantity: number = 1) {
const existing_item = this.items.get(item.name)
if (!existing_item) {
@ -68,7 +66,7 @@ export default class Inventory extends Node {
}
}
has(item: ItemData): boolean {
has(item: Item): boolean {
return this.items.has(item.name)
}
}

View file

@ -1,12 +1,16 @@
import { Callable, Callable0, Callable1, CharacterBody3D, Color, float64, ProjectSettings, Variant, Vector2, Vector3 } from 'godot'
import { export_, help, onready } from 'godot.annotations'
import PlayerInput from './player_input'
import DebugDraw from './debug_draw'
import DebugDraw from '../global/debug_draw'
import Interactor from './interactor'
import Health from './health'
import PlayerAnimation, { PlayerAnimationName } from './player_animation'
import EquippedWeapon from './equipped_weapon'
import GameState, { Savable } from '../save_system/game_state'
import PlayerState from '../save_system/player_state'
import SaveState from '../save_system/save_state'
export default class Player extends CharacterBody3D {
export default class Player extends CharacterBody3D implements Savable {
readonly gravity = ProjectSettings.get_setting('physics/3d/default_gravity')
@onready('Input')
@ -18,6 +22,9 @@ export default class Player extends CharacterBody3D {
@onready('Interactor')
readonly interactor!: Interactor
@onready('Health')
readonly health!: Health
@onready('EquippedWeapon')
readonly equipped_weapon!: EquippedWeapon
@ -109,6 +116,25 @@ export default class Player extends CharacterBody3D {
this.player_animation.animation_finished.connect(this._enable_action_callable)
}
on_save_game(save_data: GameState): void {
const state = new PlayerState()
state.position = this.global_position
state.condition = 100
state.scene = this.scene_file_path
save_data.world.push(state)
}
on_before_load_game?(): void {
this.get_parent().remove_child(this)
this.queue_free()
}
on_load_game<T extends SaveState>(state: T): void {
const player_state = state as unknown as PlayerState
this.global_position = player_state.position
this.health.current_health = player_state.condition
}
private _disable_action(value: string) {
if (PlayerAnimationName.Fire === value) {
this._can_act = false

View file

@ -1,7 +1,7 @@
import { AnimationTree, Expression, float64, GArray, GError, Node } from 'godot'
import Player from './player'
import PlayerInput from './player_input'
import { export_expression, onready } from './annotations'
import { export_expression, onready } from '../extension/annotations'
export const PlayerAnimationName = Object.freeze({
Idle: 'Idle',

View file

@ -1,8 +1,8 @@
import { Callable, Callable1, Camera3D, float64, Input, InputEvent, InputEventJoypadButton, InputEventJoypadMotion, InputEventKey, InputEventMouse, InputEventMouseMotion, int32, Node3D, PhysicsRayQueryParameters3D, Signal0, Signal1, Variant, Vector2, Vector3, World3D } from 'godot'
import { export_, signal } from 'godot.annotations'
import InputBuffer from './input_buffer'
import MainCamera from './main_camera'
import MessageBus from './message_bus'
import InputBuffer from '../collection/input_buffer'
import MainCamera from '../main_camera'
import MessageBus from '../global/message_bus'
export const PlayerMovement = Object.freeze({
MoveLeft: 'move_left',

View file

@ -1,4 +1,4 @@
import { GError } from "godot";
import { GError } from 'godot'
export class GodotError extends Error {
error: GError

View file

@ -11,17 +11,24 @@ export function queue_free_children<T extends Node>(node: T) {
}
}
type RequiredField<T, Field extends keyof T> = Omit<T, Field> & Required<Pick<T, Field>>
type RequiredField<T, Key extends keyof T> = Omit<T, Key> & Required<Pick<T, Key>>;
export function has_method(name: StringName, obj: Object): boolean {
return obj.has_method(name)
export function has_methods<T extends Object & {}, U extends [keyof T, ...Array<keyof T>]>(
keys: U,
obj: T
): obj is Object & RequiredField<T, U[number]> {
return keys.every(key => obj.has_method(key as string))
}
export function has_method<T extends Object & {}>(name: StringName, obj: Object): obj is Object & RequiredField<T, keyof T> {
return has_methods([name as keyof Object], obj)
}
export function implements_interface<T>(method_names: StringName[], obj: Object): obj is Object & T {
return method_names.every(name => has_method(name, obj))
}
export function find_in_anscestors<T extends Node>(root: T | null | undefined, predicate: Predicate<Node>, height: number = Infinity): T | null | undefined {
export function find_in_ancestors<T extends Node>(root: T | null | undefined, predicate: Predicate<Node>, height: number = Infinity): T | null | undefined {
if (height < 0 || root == null) {
return
}
@ -30,7 +37,7 @@ export function find_in_anscestors<T extends Node>(root: T | null | undefined, p
return root
}
return find_in_anscestors(root.get_parent(), predicate, height - 1) as T
return find_in_ancestors(root.get_parent(), predicate, height - 1) as T
}
export function find_in_descendents<T extends Node>(root: T, predicate: Predicate<Node>, depth: number = Infinity): T | null | undefined {

View file

@ -1,5 +1,5 @@
import { GArray, GError, Node, Resource, ResourceLoader, ResourceSaver } from 'godot'
import { GodotError } from './godot_error'
import { GodotError } from '../extension/godot_error'
export class ResourceLoadError extends Error { }
@ -16,11 +16,11 @@ export default class AsyncResourceHandler extends Node {
AsyncResourceHandler._instance = this
}
static save<T extends Resource>(path: string, data: T, flags: ResourceSaver.SaverFlags): Promise<void> {
static save<T extends Resource>(path: string, data: T, flags?: ResourceSaver.SaverFlags): Promise<void> {
return this._instance.save(path, data, flags)
}
save<T extends Resource>(path: string, data: T, flags: ResourceSaver.SaverFlags): Promise<void> {
save<T extends Resource>(path: string, data: T, flags: ResourceSaver.SaverFlags = ResourceSaver.SaverFlags.FLAG_NONE): Promise<void> {
return new Promise((resolve, reject) => {
const err = ResourceSaver.save(data, path, flags)

View file

@ -1,8 +1,8 @@
import { Node3D, Variant } from 'godot'
import { export_ } from 'godot.annotations'
import Interactor from '../interactor'
import Item from '../item'
import Inventory from '../inventory'
import Interactor from '../entity/interactor'
import Item from '../item/item'
import Inventory from '../entity/inventory'
export default class Door extends Node3D {
@export_(Variant.Type.TYPE_BOOL)

View file

@ -1,5 +1,5 @@
import { Area3D, Signal1 } from 'godot'
import Interactor from './interactor'
import Interactor from '../entity/interactor'
import { signal } from 'godot.annotations'
export default class Interactable extends Area3D {

View file

@ -1,16 +0,0 @@
import { Resource, StringName, Variant } from 'godot'
import { export_, export_enum, export_multiline } from 'godot.annotations'
export type ItemType = number
export default class Item extends Resource {
@export_(Variant.Type.TYPE_STRING_NAME)
readonly name: StringName = ''
@export_multiline()
readonly description: string = ''
@export_(Variant.Type.TYPE_INT)
readonly max_quantity: number = 99
}

View file

@ -1,8 +1,8 @@
import { Node, Variant } from 'godot'
import { export_, export_range } from 'godot.annotations'
import ItemData from './item'
import Interactor from './interactor'
import Inventory from './inventory'
import Interactor from '../entity/interactor'
import Inventory from '../entity/inventory'
export default class ItemPickup extends Node {
@export_(Variant.Type.TYPE_OBJECT, { class_: ItemData })

View file

@ -1,5 +1,5 @@
import { Variant } from 'godot'
import { export_scene } from './annotations'
import { export_scene } from '../extension/annotations'
import Item from './item'
import { export_ } from 'godot.annotations'
@ -11,7 +11,7 @@ export default class Weapon extends Item {
readonly damage: number = 1
@export_(Variant.Type.TYPE_FLOAT)
readonly range: number = 100
readonly range: number = 25
@export_(Variant.Type.TYPE_FLOAT)
readonly fire_rate!: number

View file

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

View file

@ -1,5 +1,5 @@
import { Camera3D } from 'godot'
import MessageBus from './message_bus'
import MessageBus from './global/message_bus'
export default class MainCamera extends Camera3D {
private static _instance: MainCamera

View file

@ -1,38 +0,0 @@
import { GError, Node, Resource, ResourceLoader } from 'godot'
export default class AsyncResourceLoader extends Node {
private static _instance: AsyncResourceLoader
static get instance() {
return this._instance
}
_ready(): void {
AsyncResourceLoader._instance = this
}
load<T extends Resource>(path: string, hint_string: string = ''): Promise<T> {
return new Promise(async (resolve, reject) => {
const err = ResourceLoader.load_threaded_request(path, hint_string)
if (err != GError.OK) {
return reject(new Error(`failed to load ${path}`))
}
const get_status = () => ResourceLoader.load_threaded_get_status(path)
let status = get_status()
while (status === ResourceLoader.ThreadLoadStatus.THREAD_LOAD_IN_PROGRESS) {
await this.get_tree().process_frame.as_promise()
status = get_status()
}
if (get_status() === ResourceLoader.ThreadLoadStatus.THREAD_LOAD_LOADED) {
return resolve(ResourceLoader.load_threaded_get(path) as T)
} else {
return reject(new Error(`failed to load ${path}`))
}
})
}
}

View file

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

View file

@ -1,87 +0,0 @@
import { GDictionary, Node, PackedScene, Resource, ResourceLoader, ResourceSaver } from 'godot'
import AsyncResourceHandler from './async_resource_handler'
import { has_method } from './node'
interface Serializable {
on_save_game(save_data: GameState): void
on_before_load_game?(): void
on_load_game<T extends SaveState>(state: T): void
}
interface SaveState extends Resource {
scene: string
}
class PlayerState extends Resource implements SaveState {
condition: undefined = undefined
inventory: undefined = undefined
scene: string = ''
}
class ProgressState extends Resource {
variables?: GDictionary
}
export class GameState extends Resource {
player?: PlayerState
progress?: ProgressState
world?: SaveState[]
}
const StateMethod = {
Save: 'on_save_game',
BeforeLoad: 'on_before_load_game',
Load: 'on_load_game'
} as const
type StateMethod = typeof StateMethod[keyof typeof StateMethod]
export default class SaveSerializer extends Node {
private static _save_group: string = 'save'
private static _instance: SaveSerializer
_ready() {
SaveSerializer._instance = this
}
static save() {
this._instance.save()
}
private _announce(method: StateMethod, ...args: any) {
this.get_tree().call_group(
SaveSerializer._save_group,
method,
...args
)
}
save() {
const game_state: GameState = new GameState()
this._announce(StateMethod.Save, game_state)
ResourceSaver.save(game_state, 'user://save1.tres')
}
private async _restore_node<T extends SaveState>(state: T, parent: Node) {
const scene: PackedScene = await AsyncResourceHandler.load(state.scene, 'PackedScene')
const node = scene.instantiate()
parent.add_child(node)
if (has_method(StateMethod.Load, node)) {
// TODO: type check for state method
}
}
async load() {
const loaded_state: GameState = await AsyncResourceHandler.load('user://save1.tres')
this._announce(StateMethod.BeforeLoad)
// TODO: pass world root as 2nd param
const node_promises = loaded_state.world?.map(state => this._restore_node(state, this)) ?? []
return Promise.all(node_promises)
}
}

View file

@ -0,0 +1,19 @@
import { GArray, PropertyHint, Resource, Variant } from 'godot'
import SaveState from './save_state'
import ProgressState from './progress_state'
import { export_, export_array, export_object } from 'godot.annotations'
export default class GameState extends Resource {
//@export_object(ProgressState)
//progress: ProgressState = new ProgressState()
@export_(Variant.Type.TYPE_ARRAY, { class_: SaveState, hint: PropertyHint.PROPERTY_HINT_RESOURCE_TYPE })
world: SaveState[] = []
}
export interface Savable {
on_save_game(save_data: GameState): void
on_before_load_game?(): void
on_load_game<T extends SaveState>(state: T): void
}

View file

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

View file

@ -0,0 +1,12 @@
import { Resource, Variant } from 'godot'
import Item from '../item/item'
import { export_, export_object } from 'godot.annotations'
export default class ItemState extends Resource {
@export_object(Item)
item?: Item
@export_(Variant.Type.TYPE_INT)
quantity?: number
}

View file

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

View file

@ -0,0 +1,19 @@
import { Resource, Variant, Vector3 } from 'godot'
import SaveState from './save_state'
import ItemState from './item_state'
import { export_, export_array } from 'godot.annotations'
export default class PlayerState extends Resource implements SaveState {
@export_(Variant.Type.TYPE_VECTOR3)
position!: Vector3
@export_(Variant.Type.TYPE_FLOAT)
condition!: number
//@export_array(ItemState)
//inventory: ItemState[] = []
@export_(Variant.Type.TYPE_STRING_NAME)
scene!: string
}

View file

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

View file

@ -0,0 +1,4 @@
export default class ProgressState {
variables: Map<string, any> = new Map()
}

View file

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

View file

@ -0,0 +1,91 @@
import { FileAccess, GArray, Node, PackedScene } from 'godot'
import AsyncResourceHandler from '../global/async_resource_handler'
import { implements_interface } from '../extension/node'
import GameState, { Savable } from './game_state'
import SaveState from './save_state'
import { Enumerable } from '../../addons/enumerable-ts/src'
import { GArrayEnumerator } from '../collection/enumerable'
interface FileHandler {
(file: FileAccess): void
}
const open = (path: string, mode: FileAccess.ModeFlags, fn: FileHandler) => {
const fd = FileAccess.open(path, mode)
const result = fn(fd)
fd.flush()
fd.close()
return result
}
const read = (path: string, handler: FileHandler) => open(path, FileAccess.ModeFlags.READ, handler)
const write = (path: string, handler: FileHandler) => open(path, FileAccess.ModeFlags.WRITE, handler)
const StateMethod = {
Save: 'on_save_game',
BeforeLoad: 'on_before_load_game',
Load: 'on_load_game'
} as const
type StateMethod = typeof StateMethod[keyof typeof StateMethod]
export default class SaveSerializer extends Node {
private static _save_group: string = 'save'
private static _instance: SaveSerializer
_ready() {
SaveSerializer._instance = this
this.save()
this.load(this)
}
static save() {
this._instance.save()
}
private _announce(method: StateMethod, ...args: any) {
this.get_tree().call_group(
SaveSerializer._save_group,
method,
...args
)
}
save() {
write('user://save1.save', file => {
const game_state = new GameState()
this._announce(StateMethod.Save, game_state)
//const json = JSON.stringify(game_state)
file.store_var(game_state)
})
}
private async _restore_node<T extends SaveState>(state: T, parent: Node) {
const scene: PackedScene = await AsyncResourceHandler.load(state.scene, 'PackedScene')
const node = scene.instantiate()
parent.add_child(node)
if (implements_interface<Savable>(Object.values(StateMethod), node)) {
node.on_load_game(state)
}
}
async load(world_root: Node) {
read('user://save1.save', file => {
//const state = JSON.parse(file.get_line()) as GameState
const state = file.get_var() as GameState
console.log(state)
this._announce(StateMethod.BeforeLoad)
const promises = []
for (const obj of state.world) {
promises.push(this._restore_node(obj, world_root))
}
return Promise.all(promises)
})
}
}

View file

@ -0,0 +1,4 @@
export default class SaveState {
scene!: string
}

View file

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

View file

@ -58,6 +58,7 @@ declare module "godot" {
FireRate: Timer<{}>,
}
>,
Health: Node<{}>,
}
>,
DirectionalLight3D: DirectionalLight3D<{}>,

View file

@ -43,6 +43,7 @@ declare module "godot" {
FireRate: Timer<{}>,
}
>,
Health: Node<{}>,
},
}
}