Implemented vending machine

This commit is contained in:
MaddoScientisto 2026-03-07 16:49:06 +01:00
commit e5ffb0cf94
32 changed files with 3074 additions and 1992 deletions

File diff suppressed because one or more lines are too long

View file

@ -2316,6 +2316,7 @@
{
"classname" "actor_elevator_1"
"origin" "-176 544 15"
"targetname" "elevator_card"
}
// entity 73
{
@ -3662,6 +3663,13 @@
}
// entity 271
{
"classname" "actor_controlpad"
"origin" "-192 572 36"
"target" "elevator_card"
"activationtype" "Toggle"
}
// entity 272
{
"classname" "func_group"
"_tb_type" "_tb_layer"
"_tb_name" "Shrouds"
@ -3669,7 +3677,7 @@
"_tb_layer_sort_index" "0"
"_tb_layer_hidden" "1"
}
// entity 272
// entity 273
{
"classname" "func_shroud"
"targetname" "secret_001"
@ -3685,7 +3693,7 @@
( 128 800 48 ) ( 128 800 49 ) ( 128 801 48 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 -4 ] 180 1 1
}
}
// entity 273
// entity 274
{
"classname" "func_shroud"
"targetname" "wall_trap_1"
@ -3701,7 +3709,7 @@
( -72 248 52 ) ( -72 248 53 ) ( -72 249 52 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 0 ] 90 1 1
}
}
// entity 274
// entity 275
{
"classname" "func_shroud"
"targetname" "wall_trap_1"
@ -3717,7 +3725,7 @@
( -72 352 52 ) ( -72 352 53 ) ( -72 353 52 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
}
// entity 275
// entity 276
{
"classname" "func_group"
"_tb_type" "_tb_layer"
@ -3725,7 +3733,7 @@
"_tb_id" "15"
"_tb_layer_sort_index" "1"
}
// entity 276
// entity 277
{
"classname" "func_group"
"_tb_type" "_tb_group"
@ -3735,7 +3743,7 @@
"_tb_transformation" "1 0 0 -508 0 1 0 576 0 0 1 32 0 0 0 1"
"_tb_layer" "15"
}
// entity 277
// entity 278
{
"classname" "func_group"
"_tb_type" "_tb_group"
@ -3822,7 +3830,7 @@
( -160 528 16 ) ( -160 528 17 ) ( -160 529 16 ) Manual/Chevron_2 [ 0 1 0 0 ] [ 0 0 -1 -8 ] 0 1 1
}
}
// entity 278
// entity 279
{
"classname" "func_group"
"_tb_type" "_tb_group"

View file

@ -146,7 +146,7 @@
( -96 -192 -16 ) ( -96 -192 -15 ) ( -95 -192 -16 ) special/clip [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -96 -224 8 ) ( -95 -224 8 ) ( -96 -223 8 ) special/clip [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 32 -96 16 ) ( 32 -95 16 ) ( 33 -96 16 ) Floors/Floor_Tiled_0088 [ 1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( 32 -96 16 ) ( 33 -96 16 ) ( 32 -96 17 ) special/clip [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( 32 -96 16 ) ( 33 -96 16 ) ( 32 -96 17 ) Manual/Black [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( 32 -96 16 ) ( 32 -96 17 ) ( 32 -95 16 ) special/clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 270 1 1
}
// brush 16
@ -183,7 +183,7 @@
( -192 -128 -16 ) ( -191 -128 -16 ) ( -192 -127 -16 ) special/clip [ -1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -64 0 16 ) ( -64 1 16 ) ( -63 0 16 ) Floors/Floor_Tiled_0088 [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -64 32 16 ) ( -63 32 16 ) ( -64 32 17 ) special/clip [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -64 0 16 ) ( -64 0 17 ) ( -64 1 16 ) special/clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 90 1 1
( -64 0 16 ) ( -64 0 17 ) ( -64 1 16 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 0 ] 90 1 1
}
// brush 20
{
@ -273,7 +273,7 @@
( -120 -160 16 ) ( -119 -160 16 ) ( -120 -159 16 ) special/clip [ -1 0 0 -24 ] [ 0 -1 0 0 ] 180 1 1
( -88 -128 48 ) ( -88 -127 48 ) ( -87 -128 48 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( -88 -96 48 ) ( -87 -96 48 ) ( -88 -96 49 ) Walls/wall_concrete_003 [ -1 0 0 -8 ] [ 0 0 -1 -16 ] 90 1 1
( -88 -128 48 ) ( -88 -128 49 ) ( -88 -127 48 ) Walls/wall_concrete_003 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 180 1 1
( -88 -128 48 ) ( -88 -128 49 ) ( -88 -127 48 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 -16 ] 180 1 1
}
// brush 30
{
@ -1008,10 +1008,10 @@
{
( -224 -256 16 ) ( -224 -255 16 ) ( -224 -256 17 ) special/clip [ 0 -1 0 0 ] [ 0 0 -1 0 ] 180 1 1
( -224 -256 16 ) ( -224 -256 17 ) ( -223 -256 16 ) special/clip [ 1 0 0 0 ] [ 0 0 -1 0 ] 180 1 1
( -224 -256 16 ) ( -223 -256 16 ) ( -224 -255 16 ) special/clip [ -1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( -224 -256 -16 ) ( -223 -256 -16 ) ( -224 -255 -16 ) special/clip [ -1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -192 -224 48 ) ( -192 -223 48 ) ( -191 -224 48 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( -192 -128 48 ) ( -191 -128 48 ) ( -192 -128 49 ) Manual/Black [ -1 0 0 0 ] [ 0 0 -1 0 ] 180 1 1
( -192 -224 48 ) ( -192 -224 49 ) ( -192 -223 48 ) Walls/wall_concrete_003 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 180 1 1
( -192 -224 48 ) ( -192 -224 49 ) ( -192 -223 48 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 -16 ] 180 1 1
}
// brush 112
{
@ -1020,7 +1020,7 @@
( -96 -320 -16 ) ( -95 -320 -16 ) ( -96 -319 -16 ) special/clip [ -1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( -64 -288 48 ) ( -64 -287 48 ) ( -63 -288 48 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -64 -256 48 ) ( -63 -256 48 ) ( -64 -256 49 ) Walls/Wall0 [ -1 0 0 0 ] [ 0 0 -1 -16 ] 0 1 1
( -192 -288 48 ) ( -192 -288 49 ) ( -192 -287 48 ) Walls/wall_concrete_003 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 90 1 1
( -192 -288 48 ) ( -192 -288 49 ) ( -192 -287 48 ) Manual/Black [ 0 1 0 0 ] [ 0 0 -1 -16 ] 90 1 1
}
// brush 113
{
@ -1056,7 +1056,7 @@
( 8 -72 16 ) ( 9 -72 16 ) ( 8 -71 16 ) special/clip [ -1 0 0 8 ] [ 0 -1 0 -8 ] 270 1 1
( 40 -40 48 ) ( 40 -39 48 ) ( 41 -40 48 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( 40 -96 48 ) ( 41 -96 48 ) ( 40 -96 49 ) Manual/Black [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 40 -40 48 ) ( 40 -40 49 ) ( 40 -39 48 ) Walls/wall_concrete_003 [ 0 1 0 8 ] [ 0 0 -1 -16 ] 270 1 1
( 40 -40 48 ) ( 40 -40 49 ) ( 40 -39 48 ) Manual/Black [ 0 1 0 8 ] [ 0 0 -1 -16 ] 270 1 1
}
// brush 117
{
@ -1193,6 +1193,33 @@
( 240 232 56 ) ( 241 232 56 ) ( 240 232 57 ) Manual/Black [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( 72 232 56 ) ( 72 232 57 ) ( 72 233 56 ) Manual/Black [ 0 1 0 24 ] [ 0 0 -1 0 ] 90 1 1
}
// brush 132
{
( -96 -104 48 ) ( -96 -103 48 ) ( -96 -104 49 ) special/clip [ 0 -1 0 0 ] [ 0 0 -1 0 ] 180 1 1
( -24 -104 48 ) ( -24 -104 49 ) ( -23 -104 48 ) special/clip [ 1 0 0 24 ] [ 0 0 -1 0 ] 270 1 1
( -24 -104 48 ) ( -23 -104 48 ) ( -24 -103 48 ) special/clip [ -1 0 0 -24 ] [ 0 -1 0 0 ] 270 1 1
( 200 -96 52 ) ( 200 -95 52 ) ( 201 -96 52 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( 200 -96 56 ) ( 201 -96 56 ) ( 200 -96 57 ) Walls/WallTop_002 [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -72 -96 56 ) ( -72 -96 57 ) ( -72 -95 56 ) special/clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 270 1 1
}
// brush 133
{
( -72 -104 48 ) ( -72 -103 48 ) ( -72 -104 49 ) special/clip [ 0 -1 0 0 ] [ 0 0 -1 0 ] 90 1 1
( 8 -104 48 ) ( 8 -104 49 ) ( 9 -104 48 ) special/clip [ 1 0 0 -8 ] [ 0 0 -1 0 ] 270 1 1
( 8 -104 48 ) ( 9 -104 48 ) ( 8 -103 48 ) special/clip [ -1 0 0 8 ] [ 0 -1 0 0 ] 270 1 1
( 232 -96 52 ) ( 232 -95 52 ) ( 233 -96 52 ) Manual/Black [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( 232 -96 56 ) ( 233 -96 56 ) ( 232 -96 57 ) Manual/Black [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -40 -96 56 ) ( -40 -96 57 ) ( -40 -95 56 ) special/clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 270 1 1
}
// brush 134
{
( -72 -64 48 ) ( -72 -64 49 ) ( -72 -65 48 ) special/clip [ -1.0718754395722282e-15 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -64 -96 56 ) ( -64 -96 57 ) ( -63 -96 56 ) special/clip [ 1 -1.0718754395722282e-15 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -72 -64 48 ) ( -72 -65 48 ) ( -71 -64 48 ) special/clip [ 1.0718754395722282e-15 1 0 0 ] [ -1 1.0718754395722282e-15 0 0 ] 270 1 1
( -64 -288 52 ) ( -63 -288 52 ) ( -64 -289 52 ) Manual/Black [ -1.0718754395722282e-15 -1 0 0 ] [ -1 1.0718754395722282e-15 0 0 ] 0 1 1
( -72 -40 48 ) ( -71 -40 48 ) ( -72 -40 49 ) Walls/WallTop_002 [ -1 1.0718754395722282e-15 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -64 -288 56 ) ( -64 -289 56 ) ( -64 -288 57 ) Manual/Black [ 1.0718754395722282e-15 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
}
// entity 1
{
@ -1617,7 +1644,7 @@
// entity 71
{
"classname" "marker_spawn_item"
"origin" "-36 -116 22"
"origin" "-124 -28 22"
"resource_path" "res://Resources/Items/DARK_MACHINE_GUN_Item.tres"
}
// entity 72
@ -1661,3 +1688,51 @@
"classname" "actor_box_red"
"origin" "40 88 20"
}
// entity 80
{
"classname" "actor_vendingmachine"
"origin" "-56 -152 28"
}
// entity 81
{
"classname" "actor_terminal"
"origin" "-4 -188 28"
"angles" "0 90 0"
"custom_dialogue" "asdf"
}
// entity 82
{
"classname" "marker_spawn_item"
"origin" "-20 -100 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}
// entity 83
{
"classname" "marker_spawn_item"
"origin" "-20 -132 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}
// entity 84
{
"classname" "marker_spawn_item"
"origin" "-4 -124 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}
// entity 85
{
"classname" "marker_spawn_item"
"origin" "-20 -116 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}
// entity 86
{
"classname" "marker_spawn_item"
"origin" "12 -132 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}
// entity 87
{
"classname" "marker_spawn_item"
"origin" "-4 -140 22"
"resource_path" "res://Resources/Items/Money_Pickup.tres"
}

View file

@ -1,14 +1,24 @@
[gd_scene load_steps=3 format=3 uid="uid://bb2pjblwkb7ub"]
[gd_scene format=3 uid="uid://bb2pjblwkb7ub"]
[ext_resource type="PackedScene" uid="uid://cphr15gnmriw3" path="res://3D/BlockbenchModels/VendingMachine/VendingMachine.gltf" id="1_wtma2"]
[ext_resource type="Script" uid="uid://kt08ji546nms" path="res://Scripts/Actors/3D/VendingMachine3D.cs" id="2_script"]
[sub_resource type="BoxShape3D" id="BoxShape3D_hsg1w"]
size = Vector3(1.0528, 2.01202, 0.990539)
[node name="VendingMachine" type="StaticBody3D"]
[node name="VendingMachine" type="Area3D" unique_id=920711293 groups=["Interactable"]]
collision_layer = 32
collision_mask = 0
script = ExtResource("2_script")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=855169760]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0167541, 0.24654551, 0.00338751)
shape = SubResource("BoxShape3D_hsg1w")
[node name="blockbench_export" parent="." instance=ExtResource("1_wtma2")]
[node name="Body" type="StaticBody3D" parent="." unique_id=1920958687]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Body" unique_id=1000054276]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0167541, 0.24654551, 0.00338751)
shape = SubResource("BoxShape3D_hsg1w")
[node name="blockbench_export" parent="." unique_id=1405020962 instance=ExtResource("1_wtma2")]

View file

@ -1,9 +1,10 @@
[gd_resource type="Resource" script_class="FuncGodotFGDModelPointClass" load_steps=5 format=3 uid="uid://bmfarpfcbbfa5"]
[gd_resource type="Resource" script_class="FuncGodotFGDModelPointClass" format=3 uid="uid://bmfarpfcbbfa5"]
[ext_resource type="Resource" uid="uid://5bc1qysixhmh" path="res://3D/TrenchBroom/EntityDefinitions/base/actor_base.tres" id="1_2x8yp"]
[ext_resource type="PackedScene" uid="uid://bb2pjblwkb7ub" path="res://3D/Scenes/Props/Vending_Machine_3D.tscn" id="2_2x8yp"]
[ext_resource type="Script" uid="uid://d1nwwgcrner8b" path="res://addons/func_godot/src/fgd/func_godot_fgd_point_class_display_descriptor.gd" id="2_8x313"]
[ext_resource type="Script" uid="uid://ldfqjtq0br35" path="res://addons/func_godot/src/fgd/func_godot_fgd_model_point_class.gd" id="3_2x8yp"]
[ext_resource type="Resource" uid="uid://dwc3j47cgj78j" path="res://3D/TrenchBroom/EntityDefinitions/base/requirement_key_base.tres" id="4_req"]
[resource]
script = ExtResource("3_2x8yp")
@ -13,7 +14,15 @@ scene_file = ExtResource("2_2x8yp")
apply_scale_on_map_build = false
classname = "actor_vendingmachine"
description = "Vending Machine"
base_classes = Array[Resource]([ExtResource("1_2x8yp")])
base_classes = Array[Resource]([ExtResource("1_2x8yp"), ExtResource("4_req")])
class_properties = Dictionary[String, Variant]({
"shop_definition_path": "",
"shop_ui_scene_path": ""
})
class_property_descriptions = Dictionary[String, Variant]({
"shop_definition_path": "Optional resource path or uid:// for a VendingShopDefinition. Leave blank to use the scene default.",
"shop_ui_scene_path": "Optional packed scene path or uid:// for the shop UI. Leave blank to use the scene default."
})
meta_properties = Dictionary[String, Variant]({
"model": "{\"path\": \"3D/MapModels/actor_vendingmachine.glb\", \"scale\": 16.0 }",
"size": AABB(-8, -8, -12, 8, 8, 12)

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://boiwvdhs2jjmf"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_14tto"]
[ext_resource type="Resource" uid="uid://qrqsywgiij7i" path="res://Resources/Bullets/3D/simple_enemy_bullet_small_3D.tres" id="1_bdx2k"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_14tto"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_wsvd4"]
@ -23,7 +24,7 @@ metadata/_bullet_graph_layout_v1 = {
script = ExtResource("4_7mmfe")
Title = "Danmaku Room 1"
Description = "Script for the first danmaku room"
Patterns = Array[Object]([SubResource("Resource_bdx2k")])
Patterns = Array[ExtResource("1_14tto")]([SubResource("Resource_bdx2k")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"
metadata/_bullet_graph_layout_v1 = {
"res://Resources/BulletScripts/Danmaku_Room_1.tres": Vector2(-1280, 40),

View file

@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://dibxxg4o015cd"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_wko0l"]
[ext_resource type="Resource" uid="uid://qrqsywgiij7i" path="res://Resources/Bullets/3D/simple_enemy_bullet_small_3D.tres" id="1_ww6vs"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_wko0l"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_tp6n0"]
@ -23,5 +24,5 @@ metadata/_custom_type_script = "uid://bxiprx5nwmpnu"
[resource]
script = ExtResource("4_2smox")
Patterns = Array[Object]([SubResource("Resource_ww6vs")])
Patterns = Array[ExtResource("1_wko0l")]([SubResource("Resource_ww6vs")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"

View file

@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://cx0rbqwvfmm5w"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_3hbyi"]
[ext_resource type="Resource" uid="uid://dugnfpcenki5l" path="res://Resources/Bullets/3D/simple_blue_bullet_3D.tres" id="1_7k2by"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_7k2by"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_knai5"]
@ -19,5 +20,5 @@ metadata/_custom_type_script = "uid://bxiprx5nwmpnu"
[resource]
script = ExtResource("4_ohb2x")
Patterns = Array[Object]([SubResource("Resource_7k2by")])
Patterns = Array[ExtResource("1_3hbyi")]([SubResource("Resource_7k2by")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"

View file

@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://wvcda1h8wa2g"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_8cm4j"]
[ext_resource type="Resource" uid="uid://dpnauedcubupa" path="res://Resources/Bullets/3D/rice_bullet_small_red_3D.tres" id="1_c4f5k"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_8cm4j"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_l6rg6"]
@ -22,5 +23,5 @@ metadata/_custom_type_script = "uid://bxiprx5nwmpnu"
[resource]
script = ExtResource("4_or1wb")
Patterns = Array[Object]([SubResource("Resource_c4f5k")])
Patterns = Array[ExtResource("1_8cm4j")]([SubResource("Resource_c4f5k")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"

View file

@ -1,5 +1,6 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://ddw7u1nudixdv"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_7dhjl"]
[ext_resource type="Resource" uid="uid://dpnauedcubupa" path="res://Resources/Bullets/3D/rice_bullet_small_red_3D.tres" id="1_k1eey"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_7dhjl"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_ou3hf"]
@ -23,5 +24,5 @@ metadata/_custom_type_script = "uid://bxiprx5nwmpnu"
[resource]
script = ExtResource("4_748vc")
Patterns = Array[Object]([SubResource("Resource_k1eey")])
Patterns = Array[ExtResource("1_7dhjl")]([SubResource("Resource_k1eey")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"

View file

@ -1,6 +1,7 @@
[gd_resource type="Resource" script_class="BulletScript3D" format=3 uid="uid://cfhjwydjjjxat"]
[ext_resource type="Resource" uid="uid://qrqsywgiij7i" path="res://Resources/Bullets/3D/simple_enemy_bullet_small_3D.tres" id="1_hnqww"]
[ext_resource type="Script" uid="uid://da0qevb67wh1i" path="res://Scripts/Resources/AttackPattern.cs" id="1_q5dwt"]
[ext_resource type="Script" uid="uid://b5s5mjuk1rng5" path="res://Scripts/Resources/TimeModifier.cs" id="2_hnqww"]
[ext_resource type="Script" uid="uid://bxiprx5nwmpnu" path="res://Scripts/AttackPatterns/ShootingPattern3D.cs" id="3_ko0d0"]
[ext_resource type="Script" uid="uid://w8hcpu68ssq" path="res://Scripts/Resources/BulletScripts/BulletScript3D.cs" id="4_hhjh3"]
@ -14,5 +15,5 @@ metadata/_custom_type_script = "uid://bxiprx5nwmpnu"
[resource]
script = ExtResource("4_hhjh3")
Patterns = Array[Object]([SubResource("Resource_7yyul")])
Patterns = Array[ExtResource("1_q5dwt")]([SubResource("Resource_7yyul")])
metadata/_custom_type_script = "uid://w8hcpu68ssq"

View file

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="CreditsCollection" load_steps=23 format=3 uid="uid://cojsc1rtf41i1"]
[gd_resource type="Resource" script_class="CreditsCollection" format=3 uid="uid://cojsc1rtf41i1"]
[ext_resource type="Script" uid="uid://buq8eurx510ps" path="res://Scripts/Resources/CreditsCollection.cs" id="1_0bwsf"]
[ext_resource type="Script" uid="uid://bc4f4ggvk3ktf" path="res://Scripts/Resources/CreditsEntry.cs" id="1_aaqha"]

View file

@ -13,6 +13,7 @@ ShortName = &""
ItemDescription = &"Ammo for Ice-Based Weapons"
ItemKey = &"ICE_AMMO"
Item = 3
Price = 2
Amount = 5
Max = 30
ConsumeOnUse = true

View file

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="LootItem" load_steps=3 format=3 uid="uid://cltxhkrqp055v"]
[gd_resource type="Resource" script_class="LootItem" format=3 uid="uid://cltxhkrqp055v"]
[ext_resource type="Texture2D" uid="uid://4x3ouxyxjqjc" path="res://Sprites/Items/Credits_Pickup.png" id="1_woor7"]
[ext_resource type="Script" uid="uid://epnwjptvks3t" path="res://Scripts/Resources/LootItem.cs" id="2_swcup"]
@ -10,11 +10,9 @@ ShortName = &""
ItemDescription = &"Can be used to buy things"
ItemKey = &"CREDITS"
Item = 12
Tier = 0
Price = 0
Price = 1
Amount = 1
Max = 10
PickupIfMaxed = false
Max = 99999999
ConsumeOnUse = true
UiType = 6
Selectable = true

View file

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="LootItem" load_steps=3 format=3 uid="uid://cfod8kephnio6"]
[gd_resource type="Resource" script_class="LootItem" format=3 uid="uid://cfod8kephnio6"]
[ext_resource type="Texture2D" uid="uid://bxwcjp25xba1j" path="res://Sprites/Items/Nuclear_Ammo.png" id="1_nas3h"]
[ext_resource type="Script" uid="uid://epnwjptvks3t" path="res://Scripts/Resources/LootItem.cs" id="2_x45it"]
@ -10,14 +10,10 @@ ShortName = &""
ItemDescription = &"Ammo for Nuclear weapons"
ItemKey = &"NUCLEAR_AMMO"
Item = 3
Tier = 0
Price = 0
Price = 5
Amount = 10
Max = 50
PickupIfMaxed = false
ConsumeOnUse = true
UiType = 0
Selectable = false
AutoPickup = true
InventorySprite = ExtResource("1_nas3h")
DropScenePath = &"res://Scenes/Items/GenericItem.tscn"

View file

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="LootItem" load_steps=3 format=3 uid="uid://diqm2ju0xakkt"]
[gd_resource type="Resource" script_class="LootItem" format=3 uid="uid://diqm2ju0xakkt"]
[ext_resource type="Texture2D" uid="uid://b61po207ggn63" path="res://Sprites/Items/Yin_Ammo.png" id="1_jt7l8"]
[ext_resource type="Script" uid="uid://epnwjptvks3t" path="res://Scripts/Resources/LootItem.cs" id="3_ajs4l"]
@ -10,14 +10,10 @@ ShortName = &""
ItemDescription = &"Ammo for Ying-Yang based weapons"
ItemKey = &"YINYANG_AMMO"
Item = 3
Tier = 0
Price = 0
Price = 3
Amount = 10
Max = 50
PickupIfMaxed = false
ConsumeOnUse = true
UiType = 0
Selectable = false
AutoPickup = true
InventorySprite = ExtResource("1_jt7l8")
DropScenePath = &"res://Scenes/Items/GenericItem.tscn"

View file

@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="VendingShopDefinition" format=3 uid="uid://cqj0olnkplmvu"]
[ext_resource type="Script" uid="uid://dg6j01fkmdhwd" path="res://Scripts/Resources/VendingShopDefinition.cs" id="1_definition"]
[ext_resource type="Script" uid="uid://c0o5wfundmjk" path="res://Scripts/Resources/VendingShopEntry.cs" id="2_entry"]
[ext_resource type="Resource" uid="uid://dodwpect0ldjf" path="res://Resources/Items/Heart_Pickup.tres" id="3_health"]
[ext_resource type="Resource" uid="uid://bhbufxodybsw4" path="res://Resources/Items/Shield_Pickup.tres" id="4_shield"]
[ext_resource type="Resource" uid="uid://ct1fa2huvy34n" path="res://Resources/Items/Ammo1.tres" id="5_ammo"]
[sub_resource type="Resource" id="Resource_health"]
script = ExtResource("2_entry")
Item = ExtResource("3_health")
Unlimited = false
StartingQuantity = 4
[sub_resource type="Resource" id="Resource_shield"]
script = ExtResource("2_entry")
Item = ExtResource("4_shield")
Unlimited = false
StartingQuantity = 3
[sub_resource type="Resource" id="Resource_ammo"]
script = ExtResource("2_entry")
Item = ExtResource("5_ammo")
[resource]
script = ExtResource("1_definition")
Entries = Array[ExtResource("2_entry")]([SubResource("Resource_health"), SubResource("Resource_shield"), SubResource("Resource_ammo")])

BIN
SFX/sfx_purchase.wav (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://beanbp4411et"
path="res://.godot/imported/sfx_purchase.wav-fa7f8a65baa4e5e87a38940df7aab2ed.sample"
[deps]
source_file="res://SFX/sfx_purchase.wav"
dest_files=["res://.godot/imported/sfx_purchase.wav-fa7f8a65baa4e5e87a38940df7aab2ed.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

View file

@ -0,0 +1,183 @@
[gd_scene format=3 uid="uid://bnwnkjeuy8vw2"]
[ext_resource type="Theme" uid="uid://dnsadvmunm76k" path="res://Resources/Styles/MainMenuButtons.tres" id="1_theme"]
[ext_resource type="StyleBox" uid="uid://ctw2hju32l3rg" path="res://Resources/Styles/PixelStyleBoxRed.tres" id="2_panel"]
[ext_resource type="Script" uid="uid://dwf1c6gber6qn" path="res://Scripts/UI/VendingMachineShopUi.cs" id="3_script"]
[node name="VendingMachineShopUi" type="CanvasLayer" unique_id=1039113089 node_paths=PackedStringArray("CreditsValueLabel", "ShopItemList", "PreviewTexture", "ItemNameLabel", "DescriptionLabel", "PriceValueLabel", "StockValueLabel", "OwnedValueLabel", "MaxValueLabel", "AllAmmoButton", "BuyButton", "CancelButton", "CloseButton")]
process_mode = 3
layer = 2
script = ExtResource("3_script")
CreditsValueLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow/CreditsRow/CreditsValue")
ShopItemList = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/LeftColumn/ShopItemList")
PreviewTexture = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/PreviewPanel/PreviewTexture")
ItemNameLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/ItemName")
DescriptionLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/Description")
PriceValueLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid/PriceValue")
StockValueLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid/StockValue")
OwnedValueLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid/OwnedValue")
MaxValueLabel = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid/MaxValue")
AllAmmoButton = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/LeftColumn/AllAmmoButton")
BuyButton = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/FooterRow/BuyButton")
CancelButton = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/FooterRow/CancelButton")
CloseButton = NodePath("Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow/CloseButton")
[node name="Overlay" type="Control" parent="." unique_id=1092991846]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Backdrop" type="ColorRect" parent="Overlay" unique_id=1819897996]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.68)
[node name="PanelContainer" type="PanelContainer" parent="Overlay" unique_id=728531908]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -7.0
offset_bottom = 7.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_theme")
theme_override_styles/panel = ExtResource("2_panel")
[node name="MarginContainer" type="MarginContainer" parent="Overlay/PanelContainer" unique_id=1176588702]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="RootColumn" type="VBoxContainer" parent="Overlay/PanelContainer/MarginContainer" unique_id=1296448860]
layout_mode = 2
theme_override_constants/separation = 6
[node name="HeaderRow" type="HBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn" unique_id=828923911]
layout_mode = 2
theme_override_constants/separation = 8
[node name="Title" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow" unique_id=444435654]
layout_mode = 2
size_flags_horizontal = 3
text = "Vending Shop"
[node name="CreditsRow" type="HBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow" unique_id=602893457]
layout_mode = 2
theme_override_constants/separation = 6
[node name="CreditsLabel" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow/CreditsRow" unique_id=45059238]
layout_mode = 2
text = "Credits:"
[node name="CreditsValue" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow/CreditsRow" unique_id=329239546]
layout_mode = 2
text = "0"
[node name="CloseButton" type="Button" parent="Overlay/PanelContainer/MarginContainer/RootColumn/HeaderRow" unique_id=2132068348]
layout_mode = 2
text = "X"
[node name="ContentRow" type="HBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn" unique_id=197741149]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 8
[node name="LeftColumn" type="VBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow" unique_id=536170789]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 6
[node name="ShopItemList" type="ItemList" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/LeftColumn" unique_id=978618349]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
allow_reselect = true
same_column_width = true
[node name="AllAmmoButton" type="Button" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/LeftColumn" unique_id=1067558866]
layout_mode = 2
text = "ALL AMMO"
[node name="RightColumn" type="VBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow" unique_id=872210061]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 4
[node name="PreviewPanel" type="PanelContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn" unique_id=547238106]
custom_minimum_size = Vector2(0, 84)
layout_mode = 2
[node name="PreviewTexture" type="TextureRect" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/PreviewPanel" unique_id=1476414198]
layout_mode = 2
expand_mode = 1
stretch_mode = 5
[node name="ItemName" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn" unique_id=560995581]
layout_mode = 2
text = "Item"
[node name="Description" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn" unique_id=948571316]
clip_contents = true
layout_mode = 2
text = "Description"
autowrap_mode = 3
[node name="StatsGrid" type="GridContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn" unique_id=864466544]
layout_mode = 2
columns = 4
[node name="PriceLabel" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=54171325]
layout_mode = 2
text = "Price"
[node name="PriceValue" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=1742129607]
layout_mode = 2
text = "0"
[node name="OwnedLabel" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=2140810319]
layout_mode = 2
text = "Owned"
[node name="OwnedValue" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=987928714]
layout_mode = 2
text = "0"
[node name="StockLabel" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=1276999766]
layout_mode = 2
text = "Stock"
[node name="StockValue" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=2002064651]
layout_mode = 2
text = "0"
[node name="MaxLabel" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=542385111]
layout_mode = 2
text = "Max"
[node name="MaxValue" type="Label" parent="Overlay/PanelContainer/MarginContainer/RootColumn/ContentRow/RightColumn/StatsGrid" unique_id=493157067]
layout_mode = 2
text = "0"
[node name="FooterRow" type="HBoxContainer" parent="Overlay/PanelContainer/MarginContainer/RootColumn" unique_id=336387742]
layout_mode = 2
theme_override_constants/separation = 6
alignment = 2
[node name="BuyButton" type="Button" parent="Overlay/PanelContainer/MarginContainer/RootColumn/FooterRow" unique_id=670623925]
layout_mode = 2
text = "BUY"
[node name="CancelButton" type="Button" parent="Overlay/PanelContainer/MarginContainer/RootColumn/FooterRow" unique_id=179357201]
layout_mode = 2
text = "CANCEL"

View file

@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cirno.Scripts.Interactables;
using Cirno.Scripts.Resources;
using Cirno.Scripts.UI;
using Cirno.Scripts.Utils;
using Godot;
namespace Cirno.Scripts.Actors._3D;
[Tool]
public partial class VendingMachine3D : Interactable3D
{
public sealed class RuntimeShopEntry
{
public RuntimeShopEntry(VendingShopEntry entry)
{
Entry = entry;
RemainingQuantity = Math.Max(0, entry.StartingQuantity);
}
public VendingShopEntry Entry { get; }
public LootItem Item => Entry.Item;
public bool Unlimited => Entry.Unlimited;
public int RemainingQuantity { get; private set; }
public bool CanPurchase(int purchaseCount = 1)
{
return purchaseCount <= 0 || Unlimited || RemainingQuantity >= purchaseCount;
}
public bool TryConsume(int purchaseCount = 1)
{
if (purchaseCount <= 0 || Unlimited)
{
return true;
}
if (RemainingQuantity < purchaseCount)
{
return false;
}
RemainingQuantity -= purchaseCount;
return true;
}
}
[Signal]
public delegate void ShopOpenedEventHandler();
[Signal]
public delegate void ShopClosedEventHandler();
[Export]
public PackedScene ShopUiScene { get; private set; } =
ResourceLoader.Load<PackedScene>("res://Scenes/HUD/VendingMachineShopUi.tscn");
[Export]
public VendingShopDefinition ShopDefinition { get; private set; } =
ResourceLoader.Load<VendingShopDefinition>("res://Resources/Shops/VendingMachine_Default.tres");
private readonly List<RuntimeShopEntry> _runtimeEntries = [];
private VendingMachineShopUi _activeShopUi;
private bool _runtimeInitialized;
public override void _Ready()
{
AddToGroup("Interactable");
if (Engine.IsEditorHint())
{
return;
}
EnsureRuntimeEntries();
}
public override bool CanActivate()
{
if (Engine.IsEditorHint())
{
return false;
}
EnsureRuntimeEntries();
return !IsShopOpen()
&& ShopUiScene != null
&& _runtimeEntries.Count > 0
&& GameStateManager.Instance?.GameState is not GameState.Shop;
}
public override bool Activate(ActivationType activationType = ActivationType.Toggle)
{
if (Engine.IsEditorHint())
{
return false;
}
var typeToUse = activationType is ActivationType.Toggle ? ActivationType.Use : activationType;
if (typeToUse is not ActivationType.Use)
{
return false;
}
if (!MeetsRequirements() || !CanActivate())
{
return false;
}
EnsureRuntimeEntries();
if (GameStateManager.Instance == null)
{
return false;
}
GameStateManager.Instance.ChangeState(GameState.Shop);
_activeShopUi = ShopUiScene.Instantiate<VendingMachineShopUi>();
_activeShopUi.Init(this);
_activeShopUi.Closed += OnShopUiClosed;
var parent = GetTree().CurrentScene ?? GetTree().Root;
parent.AddChild(_activeShopUi);
EmitSignal(SignalName.ShopOpened);
return true;
}
public override void _ExitTree()
{
if (IsInstanceValid(_activeShopUi))
{
_activeShopUi.Closed -= OnShopUiClosed;
_activeShopUi.QueueFree();
}
_activeShopUi = null;
}
public IReadOnlyList<RuntimeShopEntry> GetEntries()
{
EnsureRuntimeEntries();
return _runtimeEntries;
}
public RuntimeShopEntry FindEntry(StringName itemKey)
{
EnsureRuntimeEntries();
return _runtimeEntries.FirstOrDefault(entry => entry.Item?.ItemKey == itemKey);
}
public bool TryConsumeStock(StringName itemKey, int purchaseCount = 1)
{
var entry = FindEntry(itemKey);
return entry?.TryConsume(purchaseCount) ?? false;
}
// public void _func_godot_apply_properties(Godot.Collections.Dictionary props)
// {
// if (props.TryGetValue("shop_definition_path", out var shopDefinitionPath))
// {
// var path = shopDefinitionPath.AsString();
// if (!string.IsNullOrWhiteSpace(path))
// {
// var loadedDefinition = ResourceLoader.Load<VendingShopDefinition>(path);
// if (loadedDefinition != null)
// {
// ShopDefinition = loadedDefinition;
// _runtimeInitialized = false;
// }
// else
// {
// GD.PrintErr($"Failed to load vending shop definition from '{path}'");
// }
// }
// }
// }
private void EnsureRuntimeEntries()
{
if (_runtimeInitialized)
{
return;
}
_runtimeInitialized = true;
_runtimeEntries.Clear();
if (ShopDefinition == null)
{
return;
}
foreach (var entry in ShopDefinition.Entries.Where(entry => entry?.Item != null))
{
_runtimeEntries.Add(new RuntimeShopEntry(entry));
}
}
private bool IsShopOpen()
{
return IsInstanceValid(_activeShopUi);
}
private void OnShopUiClosed()
{
if (IsInstanceValid(_activeShopUi))
{
_activeShopUi.Closed -= OnShopUiClosed;
}
_activeShopUi = null;
EmitSignal(SignalName.ShopClosed);
}
}

View file

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

View file

@ -8,7 +8,7 @@ using Cirno.Scripts.Resources;
public partial class InventoryManager : Node
{
public static InventoryManager Instance { get; private set; }
public ItemsDatabase ItemsDatabase { get; set; }
private Dictionary<string, ItemContainer> _itemsDict = new();
@ -25,7 +25,7 @@ public partial class InventoryManager : Node
/// Use when equipping a weapon from the inventory screen
/// </summary>
[Signal] public delegate void WeaponEquipEventHandler(string itemKey);
/// <summary>
/// Use when updating the weapon shown on the hud
/// </summary>
@ -36,10 +36,10 @@ public partial class InventoryManager : Node
[Signal]
public delegate void TotalAmmoChangedEventHandler(StringName ammoKey, int count);
[Signal]
public delegate void LoadedAmmoChangedEventHandler(StringName weaponKey, int count);
public override void _Ready()
{
Instance = this;
@ -97,14 +97,14 @@ public partial class InventoryManager : Node
{
EmitSignal(SignalName.TotalAmmoChanged, itemKey, itm.Count);
}
return removed;
}
public bool CanAddItem(string itemKey)
{
var found = TryGetItem(itemKey, out var itm);
if (found)
{
@ -130,41 +130,55 @@ public partial class InventoryManager : Node
public bool AddItem(LootItem item)
{
//var item = new LootItem() { Item = type, Amount = amount };
var maxCount = item.Max <= 0 ? int.MaxValue : item.Max;
if (!_itemsDict.TryGetValue(item.ItemKey, out var itm))
{
var startingCount = Math.Clamp(item.Amount, 0, maxCount);
if (startingCount <= 0)
{
return false;
}
_itemsDict.Add(item.ItemKey, new ItemContainer()
{
Item = item,
Count = item.Amount,
Count = startingCount,
});
GD.Print($"Added new ({item.ItemKey}) {item.Item} x{item.Amount}");
GD.Print($"Added new ({item.ItemKey}) {item.Item} x{startingCount}");
EmitSignal(SignalName.ItemAdded, item, item.Amount);
EmitSignal(SignalName.ItemAdded, item, startingCount);
if (item.Item is ItemTypes.Ammo)
{
EmitSignal(SignalName.TotalAmmoChanged, item.ItemKey, startingCount);
}
}
else
{
if (itm.Count < item.Max)
if (itm.Count >= maxCount)
{
itm.Count += item.Amount;
}
else
{
if (item.PickupIfMaxed)
{
itm.Count = item.Max;
}
else
if (!item.PickupIfMaxed)
{
return false;
}
}
else
{
itm.Count = Math.Clamp(itm.Count + item.Amount, 0, maxCount);
}
EmitSignal(SignalName.ItemAdded, item, itm.Count);
if (item.Item is ItemTypes.Ammo)
{
EmitSignal(SignalName.TotalAmmoChanged, item.ItemKey, itm.Count);
}
}
return true;
}
public bool UseItem(StringName itemKey)
{
if (!_itemsDict.TryGetValue(itemKey, out var itm)) return false;
@ -214,15 +228,15 @@ public partial class InventoryManager : Node
public void Load(Godot.Collections.Dictionary<string, int> items)
{
ItemsDatabase = ResourceLoader.Load<ItemsDatabase>("uid://cdi84udi6gldt");
_itemsDict.Clear();
_itemsDict = items.ToDictionary(x => x.Key, x => new ItemContainer()
{
Item = ItemsDatabase.LootItems.FirstOrDefault(y => y.ItemKey == x.Key),
Item = ItemsDatabase.LootItems.FirstOrDefault(y => y.ItemKey == x.Key),
Count = x.Value
});
GD.Print($"Items after loading: {_itemsDict.Count}");
}

View file

@ -30,6 +30,7 @@ public partial class LootItem : Resource
[Export] public bool AutoPickup { get; private set; } = false;
[Export] public Texture2D InventorySprite;
[Export] public Texture2D LargePreviewSprite;
//[Export] public SpriteFrames WorldSprite;
//[Export] public PackedScene HudItemScene;

View file

@ -0,0 +1,11 @@
using Godot;
using Godot.Collections;
namespace Cirno.Scripts.Resources;
[GlobalClass]
[Tool]
public partial class VendingShopDefinition : Resource
{
[Export] public Array<VendingShopEntry> Entries { get; set; } = [];
}

View file

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

View file

@ -0,0 +1,12 @@
using Godot;
namespace Cirno.Scripts.Resources;
[GlobalClass]
[Tool]
public partial class VendingShopEntry : Resource
{
[Export] public LootItem Item { get; set; }
[Export] public bool Unlimited { get; set; } = true;
[Export] public int StartingQuantity { get; set; } = 1;
}

View file

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

View file

@ -0,0 +1,447 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cirno.Scripts.Actors._3D;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Utils;
using Godot;
namespace Cirno.Scripts.UI;
public partial class VendingMachineShopUi : CanvasLayer
{
private sealed class AmmoPurchasePlan
{
public required VendingMachine3D.RuntimeShopEntry Entry { get; init; }
public required int BundleCount { get; init; }
public required int TotalPrice { get; init; }
}
[Signal]
public delegate void ClosedEventHandler();
[Export] public Label CreditsValueLabel { get; private set; }
[Export] public ItemList ShopItemList { get; private set; }
[Export] public TextureRect PreviewTexture { get; private set; }
[Export] public Label ItemNameLabel { get; private set; }
[Export] public Label DescriptionLabel { get; private set; }
[Export] public Label PriceValueLabel { get; private set; }
[Export] public Label StockValueLabel { get; private set; }
[Export] public Label OwnedValueLabel { get; private set; }
[Export] public Label MaxValueLabel { get; private set; }
[Export] public Button AllAmmoButton { get; private set; }
[Export] public Button BuyButton { get; private set; }
[Export] public Button CancelButton { get; private set; }
[Export] public Button CloseButton { get; private set; }
[Export] public StringName CancelActionName { get; private set; } = "ui_cancel";
[Export] public bool AutoFitToViewport { get; private set; }
[Export] public Vector2 ShopPanelDesignSize { get; private set; } = new(720f, 380f);
[Export(PropertyHint.None, "suffix:px")] public float ViewportMargin { get; private set; } = 24f;
private readonly List<VendingMachine3D.RuntimeShopEntry> _entries = [];
private VendingMachine3D _machine;
private bool _isClosing;
private bool _isReady;
private int _selectedIndex = -1;
private PanelContainer _rootPanel;
public void Init(VendingMachine3D machine)
{
_machine = machine;
if (_isReady)
{
RefreshUi();
}
}
public override void _Ready()
{
ProcessMode = ProcessModeEnum.Always;
_isReady = true;
_rootPanel = GetNode<PanelContainer>("Overlay/PanelContainer");
CloseButton.Pressed += CloseShop;
CancelButton.Pressed += CloseShop;
BuyButton.Pressed += BuySelectedItem;
AllAmmoButton.Pressed += BuyAllAmmo;
ShopItemList.ItemSelected += OnItemSelected;
ShopItemList.ItemActivated += OnItemActivated;
if (AutoFitToViewport && GetViewport() != null)
{
GetViewport().SizeChanged += OnViewportSizeChanged;
}
FitToViewport();
if (InventoryManager.Instance != null)
{
InventoryManager.Instance.ItemAdded += OnInventoryChanged;
InventoryManager.Instance.ItemRemoved += OnInventoryChanged;
}
RefreshUi();
}
public override void _ExitTree()
{
if (AutoFitToViewport && GetViewport() != null)
{
GetViewport().SizeChanged -= OnViewportSizeChanged;
}
if (InventoryManager.Instance != null)
{
InventoryManager.Instance.ItemAdded -= OnInventoryChanged;
InventoryManager.Instance.ItemRemoved -= OnInventoryChanged;
}
}
public override void _Process(double delta)
{
if (_isClosing || GameStateManager.Instance?.GameState is not GameState.Shop)
{
return;
}
if (Input.IsActionJustPressed(CancelActionName))
{
CloseShop();
}
}
private void OnInventoryChanged(LootItem item, int currentAmount)
{
RefreshUi();
}
private void OnInventoryChanged(string itemKey, int currentAmount)
{
RefreshUi();
}
private void RefreshUi()
{
FitToViewport();
PopulateList();
UpdateCredits();
UpdateAllAmmoButton();
UpdateSelectionDetails();
if (_entries.Count > 0)
{
if (_selectedIndex < 0)
{
SelectEntry(0, true);
}
CallDeferred(MethodName.GrabListFocus);
}
}
private void GrabListFocus()
{
ShopItemList?.GrabFocus();
}
private void OnViewportSizeChanged()
{
FitToViewport();
}
private void FitToViewport()
{
if (!AutoFitToViewport || _rootPanel == null)
{
return;
}
var viewport = GetViewport();
if (viewport == null)
{
return;
}
var viewportSize = viewport.GetVisibleRect().Size;
var availableWidth = Mathf.Max(1f, viewportSize.X - (ViewportMargin * 2f));
var availableHeight = Mathf.Max(1f, viewportSize.Y - (ViewportMargin * 2f));
var scale = Mathf.Min(1f, Mathf.Min(availableWidth / ShopPanelDesignSize.X, availableHeight / ShopPanelDesignSize.Y));
var scaledSize = ShopPanelDesignSize * scale;
_rootPanel.SetAnchorsPreset(Control.LayoutPreset.TopLeft);
_rootPanel.Size = ShopPanelDesignSize;
_rootPanel.PivotOffset = Vector2.Zero;
_rootPanel.Scale = new Vector2(scale, scale);
_rootPanel.Position = (viewportSize - scaledSize) * 0.5f;
}
private void PopulateList()
{
_entries.Clear();
ShopItemList.Clear();
if (_machine != null)
{
_entries.AddRange(_machine.GetEntries());
}
for (var index = 0; index < _entries.Count; index++)
{
var entry = _entries[index];
var item = entry.Item;
if (item == null)
{
continue;
}
var displayName = GetDisplayName(item);
var stockLabel = entry.Unlimited ? "INF" : entry.RemainingQuantity.ToString();
var row = $"{displayName} {item.Price}c [{stockLabel}]";
ShopItemList.AddItem(row, item.InventorySprite, true);
ShopItemList.SetItemTooltip(index, item.ItemDescription.ToString());
}
if (_entries.Count == 0)
{
_selectedIndex = -1;
return;
}
_selectedIndex = Math.Clamp(_selectedIndex, 0, _entries.Count - 1);
ShopItemList.Select(_selectedIndex);
}
private void UpdateCredits()
{
var credits = InventoryManager.Instance?.GetItemCount("CREDITS") ?? 0;
CreditsValueLabel.Text = credits.ToString();
}
private void UpdateAllAmmoButton()
{
var plans = BuildAllAmmoPlan(out var totalPrice, out var canPurchase);
AllAmmoButton.Text = plans.Count == 0 ? "ALL AMMO" : $"ALL AMMO ({totalPrice}c)";
AllAmmoButton.Disabled = !canPurchase;
}
private void UpdateSelectionDetails()
{
if (!TryGetSelectedEntry(out var entry))
{
PreviewTexture.Texture = null;
ItemNameLabel.Text = "No Stock";
DescriptionLabel.Text = "This vending machine has no shop entries configured.";
PriceValueLabel.Text = "--";
StockValueLabel.Text = "--";
OwnedValueLabel.Text = "--";
MaxValueLabel.Text = "--";
BuyButton.Text = "BUY";
BuyButton.Disabled = true;
return;
}
var item = entry.Item;
var currentCount = GetCurrentCount(item);
var canBuy = CanBuy(entry);
PreviewTexture.Texture = item.LargePreviewSprite ?? item.InventorySprite;
ItemNameLabel.Text = item.ItemName.ToString();
DescriptionLabel.Text = item.ItemDescription.ToString();
PriceValueLabel.Text = item.Price.ToString();
StockValueLabel.Text = entry.Unlimited ? "Unlimited" : entry.RemainingQuantity.ToString();
OwnedValueLabel.Text = currentCount.ToString();
MaxValueLabel.Text = item.Max.ToString();
BuyButton.Text = $"BUY ({item.Price}c)";
BuyButton.Disabled = !canBuy;
}
private void OnItemSelected(long index)
{
SelectEntry((int)index);
}
private void OnItemActivated(long index)
{
SelectEntry((int)index);
BuySelectedItem();
}
private void SelectEntry(int index, bool forceFocus = false)
{
if (_entries.Count == 0)
{
_selectedIndex = -1;
UpdateSelectionDetails();
return;
}
_selectedIndex = Math.Clamp(index, 0, _entries.Count - 1);
ShopItemList.Select(_selectedIndex);
UpdateSelectionDetails();
if (forceFocus)
{
ShopItemList.GrabFocus();
}
}
private bool TryGetSelectedEntry(out VendingMachine3D.RuntimeShopEntry entry)
{
if (_selectedIndex < 0 || _selectedIndex >= _entries.Count)
{
entry = null;
return false;
}
entry = _entries[_selectedIndex];
return entry?.Item != null;
}
private bool CanBuy(VendingMachine3D.RuntimeShopEntry entry)
{
var item = entry.Item;
if (item == null || InventoryManager.Instance == null)
{
return false;
}
var currentCount = GetCurrentCount(item);
if (currentCount >= item.Max)
{
return false;
}
return entry.CanPurchase()
&& InventoryManager.Instance.CanAddItem(item.ItemKey.ToString())
&& InventoryManager.Instance.GetItemCount("CREDITS") >= item.Price;
}
private void BuySelectedItem()
{
if (!TryGetSelectedEntry(out var entry) || !CanBuy(entry) || InventoryManager.Instance == null)
{
RefreshUi();
return;
}
_machine.TryConsumeStock(entry.Item.ItemKey);
InventoryManager.Instance.RemoveItem("CREDITS", entry.Item.Price);
InventoryManager.Instance.AddItem(entry.Item);
RefreshUi();
}
private void BuyAllAmmo()
{
if (InventoryManager.Instance == null)
{
return;
}
var plans = BuildAllAmmoPlan(out var totalPrice, out var canPurchase);
if (!canPurchase || plans.Count == 0)
{
RefreshUi();
return;
}
InventoryManager.Instance.RemoveItem("CREDITS", totalPrice);
foreach (var plan in plans)
{
_machine.TryConsumeStock(plan.Entry.Item.ItemKey, plan.BundleCount);
for (var purchaseIndex = 0; purchaseIndex < plan.BundleCount; purchaseIndex++)
{
InventoryManager.Instance.AddItem(plan.Entry.Item);
}
}
RefreshUi();
}
private List<AmmoPurchasePlan> BuildAllAmmoPlan(out int totalPrice, out bool canPurchase)
{
totalPrice = 0;
canPurchase = false;
if (InventoryManager.Instance == null)
{
return [];
}
var plans = new List<AmmoPurchasePlan>();
foreach (var entry in _entries.Where(entry => entry.Item?.Item == ItemTypes.Ammo))
{
if (!InventoryManager.Instance.TryGetItem(entry.Item.ItemKey.ToString(), out var ownedItem))
{
continue;
}
if (ownedItem.Count >= entry.Item.Max)
{
continue;
}
var bundleSize = Math.Max(1, entry.Item.Amount);
var missingAmount = entry.Item.Max - ownedItem.Count;
var bundleCount = Mathf.CeilToInt(missingAmount / (float)bundleSize);
if (!entry.CanPurchase(bundleCount))
{
return [];
}
var price = bundleCount * entry.Item.Price;
plans.Add(new AmmoPurchasePlan
{
Entry = entry,
BundleCount = bundleCount,
TotalPrice = price,
});
totalPrice += price;
}
canPurchase = plans.Count > 0 && InventoryManager.Instance.GetItemCount("CREDITS") >= totalPrice;
return plans;
}
private int GetCurrentCount(LootItem item)
{
return InventoryManager.Instance?.GetItemCount(item.ItemKey.ToString()) ?? 0;
}
private void CloseShop()
{
if (_isClosing)
{
return;
}
_isClosing = true;
if (GameStateManager.Instance != null)
{
GameStateManager.Instance.ChangeState(GameState.Playing);
}
EmitSignal(SignalName.Closed);
QueueFree();
}
private static string GetDisplayName(LootItem item)
{
if (!string.IsNullOrWhiteSpace(item.ShortName.ToString()))
{
return item.ShortName.ToString();
}
return item.ItemName.ToString();
}
}

View file

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