Compare commits
17 Commits
9c55853930
...
multy
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c2227432c | |||
| 84433160fe | |||
| 588d11a9ff | |||
| 3df2a0af58 | |||
| 832e539a96 | |||
| 4e6c645938 | |||
| 1869dbf054 | |||
| 5e0b58a984 | |||
| 329bb1f984 | |||
| e27ddfe3f1 | |||
| 4b8e017d64 | |||
| 5e6b403a43 | |||
| 3f1b9aea38 | |||
| 885c6f49f0 | |||
| 485ca899ab | |||
| 57ddfb0355 | |||
| f162578c3b |
@@ -0,0 +1,103 @@
|
||||
extends Node3D
|
||||
|
||||
@export var target_group : String # Items with this tag can be added to the container
|
||||
@export var meal_positions: Array[Node3D] = []
|
||||
@export var side_positions: Array[Node3D] = []
|
||||
|
||||
@onready var area_3d: Area3D = $Area3D
|
||||
@onready var xr_pickable: XRToolsPickable = get_parent() as XRToolsPickable
|
||||
|
||||
@onready var _meal_container: Node3D = $MealContainer
|
||||
@onready var _side_container: Node3D = $SidesContainer
|
||||
|
||||
var contained_items: Array[FoodItem] # Expose nice list of others to read
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
area_3d.body_entered.connect(_on_body_entered)
|
||||
if not area_3d:
|
||||
push_error("Area3D node not found in container.gd")
|
||||
if not xr_pickable:
|
||||
push_error("XRPickable node not found in container.gd")
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
# Container fill mutates world state (reparent/freeze); run only on the
|
||||
# world owner. Item transforms replicate to clients via each item's
|
||||
# MultiplayerSynchronizer (global_transform), so contents follow the plate.
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
if not body.is_in_group(target_group):
|
||||
return
|
||||
|
||||
var picked_by = xr_pickable.get_picked_up_by()
|
||||
if picked_by and picked_by.is_in_group("station"):
|
||||
|
||||
# If compatible and enough space, add item
|
||||
var food_item = body.get_node("FoodItem")
|
||||
print("Container in station found %s of type %s: %s" % [target_group, FoodItem.Type.keys()[food_item.type], body.name])
|
||||
if food_item.type == FoodItem.Type.MEAL and _meal_container.get_child_count() < meal_positions.size():
|
||||
print("Container adding meal")
|
||||
_add_item(body, meal_positions, _meal_container)
|
||||
if food_item.type == FoodItem.Type.SIDE and _side_container.get_child_count() < side_positions.size():
|
||||
print("Container adding side")
|
||||
_add_item(body, side_positions, _side_container)
|
||||
|
||||
|
||||
func _add_item(item: Node3D, positions: Array[Node3D], container_root: Node3D) -> void:
|
||||
var pickable = item as XRToolsPickable
|
||||
var rigidbody = item as RigidBody3D
|
||||
|
||||
# Drop item
|
||||
if pickable:
|
||||
if pickable.is_picked_up():
|
||||
pickable.drop()
|
||||
pickable.enabled = false
|
||||
|
||||
# Freeze the rigid body and disable its collisions so it doesn't fight the container
|
||||
if rigidbody:
|
||||
rigidbody.freeze = true
|
||||
rigidbody.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||
rigidbody.process_mode = PROCESS_MODE_DISABLED
|
||||
# Optional: Disable collision layer/mask so it doesn't bump into other food
|
||||
rigidbody.collision_layer = 0
|
||||
rigidbody.collision_mask = 0
|
||||
|
||||
item.reparent(container_root, false) # false = discard global transform
|
||||
|
||||
# Get target position index
|
||||
var target_slot_index = container_root.get_child_count() - 1
|
||||
if target_slot_index < positions.size():
|
||||
item.position = positions[target_slot_index].position
|
||||
item.rotation = positions[target_slot_index].rotation
|
||||
|
||||
# Add to list
|
||||
var food_node = item.get_node_or_null("FoodItem")
|
||||
if food_node:
|
||||
contained_items.append(food_node as FoodItem)
|
||||
|
||||
func clear() -> void:
|
||||
# delete all nodes in containers and contained_items
|
||||
pass
|
||||
|
||||
|
||||
#plate (Pickalbe)
|
||||
#XRGrapPoints
|
||||
#container (script) (meal positions[1], side positions[4])
|
||||
#area
|
||||
#meals
|
||||
#meal - burger
|
||||
#sides
|
||||
#side - chips
|
||||
#side - onion rings
|
||||
|
||||
#tray (Pickalbe)
|
||||
#XRGrapPoints
|
||||
#container (script) (meal positions[4], side positions[0])
|
||||
#food items
|
||||
#meals
|
||||
#meal - cookie
|
||||
#meal - cookie
|
||||
#meal - cookie
|
||||
#meal - cookie
|
||||
@@ -0,0 +1 @@
|
||||
uid://6lyhyial1fh5
|
||||
@@ -0,0 +1,21 @@
|
||||
[gd_scene format=3 uid="uid://du31pqeytu8as"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6lyhyial1fh5" path="res://Containers/container.gd" id="1_r4cle"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
||||
radius = 0.2
|
||||
|
||||
[node name="Container" type="Node3D" unique_id=377793422 node_paths=PackedStringArray("meal_positions")]
|
||||
script = ExtResource("1_r4cle")
|
||||
meal_positions = [null]
|
||||
|
||||
[node name="Area3D" type="Area3D" parent="." unique_id=126983781]
|
||||
collision_layer = 2
|
||||
collision_mask = 65540
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D" unique_id=286964882]
|
||||
shape = SubResource("SphereShape3D_vlqg6")
|
||||
|
||||
[node name="MealContainer" type="Node3D" parent="." unique_id=258551044]
|
||||
|
||||
[node name="SidesContainer" type="Node3D" parent="." unique_id=1464979199]
|
||||
@@ -0,0 +1,97 @@
|
||||
[gd_scene format=3 uid="uid://bp3v1jl8pctro"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bfrlpbsqg5lqr" path="res://addons/godot-xr-tools/objects/pickable.gd" id="1_nq5sa"]
|
||||
[ext_resource type="PackedScene" uid="uid://du31pqeytu8as" path="res://Containers/container.tscn" id="1_s5yhh"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="2_3bkoa"]
|
||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="3_6v47p"]
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_dhtvl"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vlcpl"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
||||
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="7_net"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||
properties/0/path = NodePath(".:global_transform")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
||||
height = 0.0635376
|
||||
radius = 0.20996094
|
||||
|
||||
[sub_resource type="Resource" id="Resource_2hcq0"]
|
||||
script = ExtResource("4_dhtvl")
|
||||
closed_pose = ExtResource("3_6v47p")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_dlngj"]
|
||||
script = ExtResource("4_dhtvl")
|
||||
closed_pose = ExtResource("6_el51w")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[node name="Plate" type="RigidBody3D" unique_id=190487773]
|
||||
collision_layer = 4
|
||||
collision_mask = 196615
|
||||
freeze_mode = 1
|
||||
script = ExtResource("1_nq5sa")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1365637864]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.007171631, 0)
|
||||
shape = SubResource("CylinderShape3D_kek77")
|
||||
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=1767596918]
|
||||
autosmooth = true
|
||||
smoothing_angle = 103.9
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=410722205]
|
||||
radius = 0.2
|
||||
height = 0.03
|
||||
sides = 16
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCylinder3D" unique_id=1041060379]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.010312311, 0)
|
||||
operation = 2
|
||||
radius = 0.15
|
||||
height = 0.03
|
||||
sides = 16
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=1827566539]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.027119078, 0)
|
||||
radius = 0.15
|
||||
height = 0.02
|
||||
|
||||
[node name="GrabPointHandLeft" parent="." unique_id=317886291 instance=ExtResource("2_3bkoa")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.1699356, 0.04601878, -0.08606844)
|
||||
hand_pose = SubResource("Resource_2hcq0")
|
||||
|
||||
[node name="GrabPointHandRight" parent="." unique_id=248026585 instance=ExtResource("5_vlcpl")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.16517048, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_dlngj")
|
||||
|
||||
[node name="NetPickable" type="Node" parent="."]
|
||||
script = ExtResource("7_net")
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("Repl_item")
|
||||
|
||||
[node name="Container" parent="." unique_id=377793422 node_paths=PackedStringArray("meal_positions", "side_positions") instance=ExtResource("1_s5yhh")]
|
||||
target_group = "platalbe_item"
|
||||
meal_positions = [NodePath("MealPositions/Slot1")]
|
||||
side_positions = [NodePath("SidePositions/Slot1"), NodePath("SidePositions/Slot2"), NodePath("SidePositions/Slot3"), NodePath("SidePositions/Slot4")]
|
||||
|
||||
[node name="MealPositions" type="Node3D" parent="Container" unique_id=1682052877]
|
||||
|
||||
[node name="Slot1" type="Node3D" parent="Container/MealPositions" unique_id=306927548]
|
||||
|
||||
[node name="SidePositions" type="Node3D" parent="Container" unique_id=1112098031]
|
||||
|
||||
[node name="Slot1" type="Node3D" parent="Container/SidePositions" unique_id=1314895915]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05862564, 0, -0.098608926)
|
||||
|
||||
[node name="Slot2" type="Node3D" parent="Container/SidePositions" unique_id=46117678]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.090641126, 0, -0.08815916)
|
||||
|
||||
[node name="Slot3" type="Node3D" parent="Container/SidePositions" unique_id=1994697744]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.08457637, 0, 0.08950478)
|
||||
|
||||
[node name="Slot4" type="Node3D" parent="Container/SidePositions" unique_id=379534402]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06938871, 0, 0.072598234)
|
||||
@@ -6,10 +6,13 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_wb51u"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="5_wqyr2"]
|
||||
[ext_resource type="Animation" uid="uid://bediglpx0rj7i" path="res://addons/godot-xr-tools/hands/animations/left/Grip 5.res" id="6_ftab3"]
|
||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_wb51u"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="8_wqyr2"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="9_wb51u"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_vlqg6"]
|
||||
height = 0.11
|
||||
radius = 0.13
|
||||
height = 0.10708985
|
||||
radius = 0.107910156
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wkb0k"]
|
||||
script = ExtResource("4_wb51u")
|
||||
@@ -24,10 +27,16 @@ metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hoqox"]
|
||||
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_hwyyd"]
|
||||
script = ExtResource("8_wqyr2")
|
||||
ingredient_id = &"cooked_burger"
|
||||
result = ExtResource("9_wb51u")
|
||||
metadata/_custom_type_script = "uid://coidnv8b2yvxr"
|
||||
|
||||
[node name="BurgerBuns" unique_id=1088240294 instance=ExtResource("1_g1t48")]
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.06281482, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05776599, 0)
|
||||
shape = SubResource("CylinderShape3D_vlqg6")
|
||||
|
||||
[node name="GrabPointHandRight" parent="." index="1" unique_id=141610509 instance=ExtResource("2_rwqhn")]
|
||||
@@ -45,22 +54,26 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="0" unique_id=110174152]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_hoqox")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="1" unique_id=2003491826]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_hoqox")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3DBuns/CSGCylinder3D3" index="0" unique_id=391028348]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
material = SubResource("StandardMaterial3D_hoqox")
|
||||
|
||||
[node name="CombinableItem" parent="." index="4" unique_id=996936271 instance=ExtResource("7_wb51u")]
|
||||
id = &"burger_buns"
|
||||
recipes = Array[ExtResource("8_wqyr2")]([SubResource("Resource_hwyyd")])
|
||||
@@ -6,17 +6,18 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_mgacb"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vw7i6"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_cp3eg"]
|
||||
[ext_resource type="Script" path="res://combinable_item.gd" id="7_comb"]
|
||||
[ext_resource type="Script" path="res://combine_zone.gd" id="8_zone"]
|
||||
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="7_net"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||
properties/0/path = NodePath(".:global_transform")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
||||
custom_solver_bias = 0.1
|
||||
height = 0.1
|
||||
radius = 0.1
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_combine"]
|
||||
size = Vector3(0.16, 0.16, 0.16)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_e28iu"]
|
||||
albedo_color = Color(0.06879827, 0.06879828, 0.06879828, 1)
|
||||
|
||||
@@ -52,12 +53,8 @@ hand_pose = SubResource("Resource_lc22d")
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="CombinableItem" type="Node" parent="." index="4"]
|
||||
script = ExtResource("7_comb")
|
||||
id = &"charcoal"
|
||||
[node name="NetPickable" type="Node" parent="." index="4"]
|
||||
script = ExtResource("7_net")
|
||||
|
||||
[node name="CombineZone" type="Area3D" parent="." index="5"]
|
||||
script = ExtResource("8_zone")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="CombineZone"]
|
||||
shape = SubResource("BoxShape3D_combine")
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="5"]
|
||||
replication_config = SubResource("Repl_item")
|
||||
@@ -6,11 +6,17 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_hc3f7"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_a0a5b"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_bdp75"]
|
||||
[ext_resource type="Script" uid="uid://bs7cxydoxk1cx" path="res://cookable_item.gd" id="7_hc3f7"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://Charcoal.tscn" id="8_a0a5b"]
|
||||
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://combinable_item.gd" id="9_comb"]
|
||||
[ext_resource type="Script" uid="uid://e3im02nq5cye" path="res://combine_zone.gd" id="10_zone"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://combine_recipe.gd" id="11_recipe"]
|
||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_bdp75"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://Items/Charcoal.tscn" id="8_a0a5b"]
|
||||
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="10_65ccv"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="11_recipe"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
||||
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="12_net"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||
properties/0/path = NodePath(".:global_transform")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
||||
size = Vector3(0.1, 0.1, 0.1)
|
||||
@@ -32,15 +38,13 @@ script = ExtResource("4_hc3f7")
|
||||
closed_pose = ExtResource("6_bdp75")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_recipe_cube"]
|
||||
[sub_resource type="Resource" id="Resource_i8ixk"]
|
||||
script = ExtResource("11_recipe")
|
||||
ingredient_id = &"cube"
|
||||
result = ExtResource("8_a0a5b")
|
||||
metadata/_custom_type_script = "uid://coidnv8b2yvxr"
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_combine"]
|
||||
size = Vector3(0.16, 0.16, 0.16)
|
||||
|
||||
[node name="PickableObject" unique_id=1675596942 instance=ExtResource("1_dbtw8")]
|
||||
[node name="PickableObject" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_dbtw8")]
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
shape = SubResource("BoxShape3D_m1xbq")
|
||||
@@ -56,18 +60,18 @@ hand_pose = SubResource("Resource_lc22d")
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="CookableItem" type="Node" parent="." index="4" unique_id=1139889801]
|
||||
script = ExtResource("7_hc3f7")
|
||||
cooking_time = 2.0
|
||||
turns_into = ExtResource("8_a0a5b")
|
||||
|
||||
[node name="CombinableItem" type="Node" parent="." index="5" unique_id=1265358312]
|
||||
script = ExtResource("9_comb")
|
||||
[node name="CombinableItem" parent="." index="4" unique_id=996936271 instance=ExtResource("7_bdp75")]
|
||||
id = &"cube"
|
||||
recipes = Array[ExtResource("11_recipe")]([SubResource("Resource_recipe_cube")])
|
||||
recipes = Array[ExtResource("11_recipe")]([SubResource("Resource_i8ixk")])
|
||||
|
||||
[node name="CombineZone" type="Area3D" parent="." index="6" unique_id=1412308162]
|
||||
script = ExtResource("10_zone")
|
||||
[node name="CookableItem" parent="." index="5" unique_id=280820828 instance=ExtResource("10_65ccv")]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="CombineZone" index="0" unique_id=1926800015]
|
||||
shape = SubResource("BoxShape3D_combine")
|
||||
[node name="FoodItem" parent="." index="6" unique_id=63948206 instance=ExtResource("11_vjw41")]
|
||||
id = "cube"
|
||||
type = 1
|
||||
|
||||
[node name="NetPickable" type="Node" parent="." index="7"]
|
||||
script = ExtResource("12_net")
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="8"]
|
||||
replication_config = SubResource("Repl_item")
|
||||
@@ -7,15 +7,18 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="5_w8sii"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="6_mvnl5"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_wqxjj"]
|
||||
[ext_resource type="Script" uid="uid://bs7cxydoxk1cx" path="res://cookable_item.gd" id="8_t4l03"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://Charcoal.tscn" id="9_x1g8b"]
|
||||
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://combinable_item.gd" id="10_qe0ug"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://combine_recipe.gd" id="11_dq5p0"]
|
||||
[ext_resource type="Script" uid="uid://e3im02nq5cye" path="res://combine_zone.gd" id="12_7llnd"]
|
||||
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="9_hwyyd"]
|
||||
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://Items/cooked_burger.tscn" id="9_r8jvx"]
|
||||
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="12_net"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||
properties/0/path = NodePath(".:global_transform")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.063323975
|
||||
radius = 0.12011719
|
||||
height = 0.0338974
|
||||
radius = 0.08544922
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bvwq1"]
|
||||
albedo_texture = ExtResource("2_r8jvx")
|
||||
@@ -30,23 +33,16 @@ script = ExtResource("5_w8sii")
|
||||
closed_pose = ExtResource("7_wqxjj")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_recipe_cube"]
|
||||
script = ExtResource("11_dq5p0")
|
||||
ingredient_id = &"cube"
|
||||
result = ExtResource("9_x1g8b")
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_combine"]
|
||||
size = Vector3(0.16, 0.16, 0.16)
|
||||
|
||||
[node name="burger" unique_id=1675596942 instance=ExtResource("1_fco8w")]
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0005453825, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.00077831745, 0)
|
||||
shape = SubResource("CylinderShape3D_fco8w")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="." index="1" unique_id=59319982]
|
||||
radius = 0.1
|
||||
radius = 0.08300781
|
||||
height = 0.03
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_bvwq1")
|
||||
|
||||
[node name="GrabPointHandLeft" parent="." index="2" unique_id=1571481674 instance=ExtResource("3_qixic")]
|
||||
@@ -57,18 +53,11 @@ hand_pose = SubResource("Resource_lc22d")
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="CookableItem" type="Node" parent="." index="4" unique_id=1139889801]
|
||||
script = ExtResource("8_t4l03")
|
||||
cooking_time = 2.0
|
||||
turns_into = ExtResource("9_x1g8b")
|
||||
[node name="CookableItem" parent="." index="4" unique_id=280820828 instance=ExtResource("9_hwyyd")]
|
||||
turns_into = ExtResource("9_r8jvx")
|
||||
|
||||
[node name="CombinableItem" type="Node" parent="." index="5" unique_id=1265358312]
|
||||
script = ExtResource("10_qe0ug")
|
||||
id = &"burger"
|
||||
recipes = Array[ExtResource("11_dq5p0")]([SubResource("Resource_recipe_cube")])
|
||||
[node name="NetPickable" type="Node" parent="." index="5"]
|
||||
script = ExtResource("12_net")
|
||||
|
||||
[node name="CombineZone" type="Area3D" parent="." index="6" unique_id=1412308162]
|
||||
script = ExtResource("12_7llnd")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="CombineZone" index="0" unique_id=1926800015]
|
||||
shape = SubResource("BoxShape3D_combine")
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="6"]
|
||||
replication_config = SubResource("Repl_item")
|
||||
@@ -0,0 +1,60 @@
|
||||
[gd_scene format=3 uid="uid://cfc4ho67u4r5e"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c8l60rnugru40" path="res://addons/godot-xr-tools/objects/pickable.tscn" id="1_ut7mg"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="3_pkevi"]
|
||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="4_dq7qw"]
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="5_ychvb"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="6_etdv2"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_rl64h"]
|
||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="10_ut7mg"]
|
||||
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="11_5ch2b"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="11_rjayc"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="11_ut7mg"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.04777527
|
||||
radius = 0.08300781
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
||||
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_lc22d"]
|
||||
script = ExtResource("5_ychvb")
|
||||
closed_pose = ExtResource("4_dq7qw")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_qyiot"]
|
||||
script = ExtResource("5_ychvb")
|
||||
closed_pose = ExtResource("7_rl64h")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wi1yl"]
|
||||
script = ExtResource("11_rjayc")
|
||||
ingredient_id = &"burger_buns"
|
||||
result = ExtResource("11_ut7mg")
|
||||
metadata/_custom_type_script = "uid://coidnv8b2yvxr"
|
||||
|
||||
[node name="CookedBurger" unique_id=1675596942 instance=ExtResource("1_ut7mg")]
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0072289705, 0)
|
||||
shape = SubResource("CylinderShape3D_fco8w")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="." index="1" unique_id=59319982]
|
||||
radius = 0.08300781
|
||||
height = 0.03
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_6l01i")
|
||||
|
||||
[node name="GrabPointHandLeft" parent="." index="2" unique_id=1571481674 instance=ExtResource("3_pkevi")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.067650706, 0.04601878, -0.08606844)
|
||||
hand_pose = SubResource("Resource_lc22d")
|
||||
|
||||
[node name="GrabPointHandRight" parent="." index="3" unique_id=514404634 instance=ExtResource("6_etdv2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="CombinableItem" parent="." index="4" unique_id=434304805 instance=ExtResource("10_ut7mg")]
|
||||
recipes = Array[ExtResource("11_rjayc")]([SubResource("Resource_wi1yl")])
|
||||
|
||||
[node name="CookableItem" parent="." index="5" unique_id=280820828 instance=ExtResource("11_5ch2b")]
|
||||
@@ -0,0 +1,81 @@
|
||||
[gd_scene format=3 uid="uid://b3m2ag8g5rj4r"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c8l60rnugru40" path="res://addons/godot-xr-tools/objects/pickable.tscn" id="1_3gf3l"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="2_0mkco"]
|
||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="3_6tnvi"]
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_cljtj"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_dunns"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_fh0f6"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_yy3y8"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.10392761
|
||||
radius = 0.096191406
|
||||
|
||||
[sub_resource type="Resource" id="Resource_lc22d"]
|
||||
script = ExtResource("4_cljtj")
|
||||
closed_pose = ExtResource("3_6tnvi")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_qyiot"]
|
||||
script = ExtResource("4_cljtj")
|
||||
closed_pose = ExtResource("6_fh0f6")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3gf3l"]
|
||||
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
||||
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
||||
|
||||
[node name="Hamburger" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_3gf3l")]
|
||||
gravity_scale = 0.4
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
||||
shape = SubResource("CylinderShape3D_fco8w")
|
||||
|
||||
[node name="GrabPointHandLeft" parent="." index="1" unique_id=1571481674 instance=ExtResource("2_0mkco")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.067650706, 0.04601878, -0.08606844)
|
||||
hand_pose = SubResource("Resource_lc22d")
|
||||
|
||||
[node name="GrabPointHandRight" parent="." index="2" unique_id=514404634 instance=ExtResource("5_dunns")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="CSGCombiner3DBuns" type="CSGCombiner3D" parent="." index="3" unique_id=678881053]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.021904588, 0.025076538, -0.0044593215)
|
||||
autosmooth = true
|
||||
smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="0" unique_id=776920274]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.022625268, -0.03479519, 0.0027926378)
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_3gf3l")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="1" unique_id=448728548]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.07384333, 0.015895892, 0)
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_3gf3l")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3DBuns/CSGCylinder3D3" index="0" unique_id=837229235]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
material = SubResource("StandardMaterial3D_3gf3l")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="." index="4" unique_id=59319982]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02356495, 0)
|
||||
radius = 0.08300781
|
||||
height = 0.03
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_6l01i")
|
||||
|
||||
[node name="FoodItem" parent="." index="5" unique_id=63948206 instance=ExtResource("7_yy3y8")]
|
||||
type = 0
|
||||
@@ -0,0 +1,49 @@
|
||||
extends Control
|
||||
|
||||
## The 2D UI rendered inside the world-space menu (XRToolsViewport2DIn3D).
|
||||
## Lets the player type an IP on a numeric keypad and pick Host / Join / Solo.
|
||||
## Pure UI: it emits intents; main.gd performs the networking.
|
||||
|
||||
signal host_pressed()
|
||||
signal join_pressed(ip: String)
|
||||
signal solo_pressed()
|
||||
|
||||
#var _ip := "127.0.0.1"
|
||||
var _ip := "51.175.10.161"
|
||||
|
||||
@onready var _ip_label: Label = %IPLabel
|
||||
@onready var _status: Label = %Status
|
||||
@onready var _keypad: GridContainer = %Keypad
|
||||
@onready var _host_btn: Button = %HostButton
|
||||
@onready var _join_btn: Button = %JoinButton
|
||||
@onready var _solo_btn: Button = %SoloButton
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for child in _keypad.get_children():
|
||||
if child is Button:
|
||||
child.pressed.connect(_on_key.bind(child.text))
|
||||
_host_btn.pressed.connect(func(): host_pressed.emit())
|
||||
_join_btn.pressed.connect(func(): join_pressed.emit(_ip))
|
||||
_solo_btn.pressed.connect(func(): solo_pressed.emit())
|
||||
_refresh()
|
||||
|
||||
|
||||
func _on_key(key: String) -> void:
|
||||
match key:
|
||||
"DEL":
|
||||
_ip = _ip.substr(0, max(0, _ip.length() - 1))
|
||||
_:
|
||||
if _ip.length() < 21:
|
||||
_ip += key
|
||||
_refresh()
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
_ip_label.text = _ip if not _ip.is_empty() else "_"
|
||||
|
||||
|
||||
## Called by main.gd (via network_menu) to show connection feedback.
|
||||
func set_status(text: String) -> void:
|
||||
if _status:
|
||||
_status.text = text
|
||||
@@ -0,0 +1 @@
|
||||
uid://dvivyrspjcjgm
|
||||
@@ -0,0 +1,172 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://Net/join_panel.gd" id="1_panel"]
|
||||
|
||||
[node name="JoinPanel" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_panel")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.09, 0.1, 0.13, 1)
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme_override_constants/margin_left = 24
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 24
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="Title" type="Label" parent="Margin/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "VRyHungry — Multiplayer"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="IPLabel" type="Label" parent="Margin/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 34
|
||||
text = "127.0.0.1"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Status" type="Label" parent="Margin/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.8, 0.8, 0.5, 1)
|
||||
theme_override_font_sizes/font_size = 18
|
||||
text = "Enter host IP, then Join"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Keypad" type="GridContainer" parent="Margin/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/h_separation = 8
|
||||
theme_override_constants/v_separation = 8
|
||||
columns = 3
|
||||
|
||||
[node name="B1" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "1"
|
||||
|
||||
[node name="B2" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "2"
|
||||
|
||||
[node name="B3" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "3"
|
||||
|
||||
[node name="B4" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "4"
|
||||
|
||||
[node name="B5" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "5"
|
||||
|
||||
[node name="B6" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "6"
|
||||
|
||||
[node name="B7" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "7"
|
||||
|
||||
[node name="B8" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "8"
|
||||
|
||||
[node name="B9" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "9"
|
||||
|
||||
[node name="BDot" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "."
|
||||
|
||||
[node name="B0" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "0"
|
||||
|
||||
[node name="BDel" type="Button" parent="Margin/VBox/Keypad"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "DEL"
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="Margin/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="HostButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
custom_minimum_size = Vector2(0, 56)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "HOST"
|
||||
|
||||
[node name="JoinButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
custom_minimum_size = Vector2(0, 56)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "JOIN"
|
||||
|
||||
[node name="SoloButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
custom_minimum_size = Vector2(0, 56)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "SOLO"
|
||||
@@ -0,0 +1,319 @@
|
||||
extends Node
|
||||
|
||||
## Client-server session manager for VRyHungry (listen-server model).
|
||||
##
|
||||
## Registered as the "NetworkManager" autoload. Owns transport (ENet), tracks
|
||||
## the session, and is the single place that reassigns multiplayer authority
|
||||
## (only the server does so). The world scene (main.gd) registers its spawners
|
||||
## here via [method register_world]; higher layers (players, items, stations)
|
||||
## build on top of this in later phases.
|
||||
|
||||
const DEFAULT_PORT := 24565
|
||||
const MAX_CLIENTS := 7
|
||||
|
||||
## Emitted on every peer (including the server for its own local player) when a
|
||||
## player peer joins. On the server this fires for each remote peer; the server
|
||||
## uses it to spawn that peer's player.
|
||||
signal player_joined(peer_id: int)
|
||||
signal player_left(peer_id: int)
|
||||
signal session_started(is_server: bool)
|
||||
signal session_ended()
|
||||
signal connection_failed()
|
||||
|
||||
# World hooks, registered by main.gd once the scene tree exists.
|
||||
var _world: Node = null
|
||||
var _players_spawner: MultiplayerSpawner = null
|
||||
var _items_spawner: MultiplayerSpawner = null
|
||||
|
||||
var _log_file: FileAccess
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_open_log()
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||
multiplayer.connected_to_server.connect(_on_connected_to_server)
|
||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
|
||||
# --- Public API ------------------------------------------------------------
|
||||
|
||||
## Start hosting. The host is peer 1 and also plays (listen server).
|
||||
func host(port: int = DEFAULT_PORT) -> Error:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_server(port, MAX_CLIENTS)
|
||||
if err != OK:
|
||||
log_line("HOST failed to create_server on port %d: %s" % [port, error_string(err)])
|
||||
return err
|
||||
multiplayer.multiplayer_peer = peer
|
||||
log_line("HOST started on port %d (peer id %d)" % [port, multiplayer.get_unique_id()])
|
||||
session_started.emit(true)
|
||||
# The host's own local player joins immediately.
|
||||
_on_player_present(multiplayer.get_unique_id())
|
||||
return OK
|
||||
|
||||
|
||||
## Join an existing host.
|
||||
func join(address: String = "127.0.0.1", port: int = DEFAULT_PORT) -> Error:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_client(address, port)
|
||||
if err != OK:
|
||||
log_line("JOIN failed to create_client %s:%d: %s" % [address, port, error_string(err)])
|
||||
return err
|
||||
multiplayer.multiplayer_peer = peer
|
||||
log_line("JOIN connecting to %s:%d ..." % [address, port])
|
||||
return OK
|
||||
|
||||
|
||||
## Leave the session and tear down transport.
|
||||
func leave() -> void:
|
||||
_go_offline()
|
||||
log_line("Session ended")
|
||||
session_ended.emit()
|
||||
|
||||
|
||||
# Restore Godot's default OfflineMultiplayerPeer (rather than leaving the peer
|
||||
# null), so is_multiplayer_authority()/get_unique_id() keep working while we are
|
||||
# back in single-player / menu state.
|
||||
func _go_offline() -> void:
|
||||
if multiplayer.multiplayer_peer:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
||||
|
||||
# --- Item spawning ---------------------------------------------------------
|
||||
|
||||
## Spawn a networked item. Server-only when online (replicates to all peers via
|
||||
## the ItemsSpawner); works directly when offline. Returns the new node on the
|
||||
## machine that owns spawning, else null.
|
||||
func spawn_item(scene_path: String, xform: Transform3D) -> Node:
|
||||
if is_online() and not is_server():
|
||||
return null
|
||||
var data := {"scene": scene_path, "xform": xform}
|
||||
if is_online() and _items_spawner:
|
||||
return _items_spawner.spawn(data)
|
||||
# Offline: instantiate directly under the world.
|
||||
var inst := _spawn_item_from_data(data)
|
||||
if _world and inst:
|
||||
_world.add_child(inst)
|
||||
return inst
|
||||
|
||||
|
||||
# MultiplayerSpawner custom spawn function: runs on every peer to build the node
|
||||
# from the replicated payload.
|
||||
func _spawn_item_from_data(data: Variant) -> Node:
|
||||
var scene: PackedScene = load(data["scene"])
|
||||
if not scene:
|
||||
push_error("spawn_item: could not load scene %s" % str(data.get("scene")))
|
||||
return null
|
||||
var inst := scene.instantiate()
|
||||
if inst is Node3D:
|
||||
inst.transform = data["xform"]
|
||||
return inst
|
||||
|
||||
|
||||
# --- Item grab-authority transfer -----------------------------------------
|
||||
|
||||
## A client (or host) requests authority over an item it just grabbed. Runs on
|
||||
## the server. If the item was snapped into a station, the station releases it
|
||||
## so the grabber cleanly takes ownership.
|
||||
@rpc("any_peer", "reliable")
|
||||
func request_item_authority(item_path: NodePath) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
var sender := multiplayer.get_remote_sender_id()
|
||||
# Assign authority + held state first (disables the item on the server so its
|
||||
# snap zone won't re-grab it), then release it from any station.
|
||||
_set_item_authority.rpc(item_path, sender)
|
||||
var item := get_node_or_null(item_path)
|
||||
if item:
|
||||
_release_from_snap_zones(item)
|
||||
|
||||
|
||||
## A player releases an item, forwarding its throw velocity so the server can
|
||||
## resume simulating it. Runs on the server. If released next to a station, the
|
||||
## server snaps it in (server-authoritative placement).
|
||||
@rpc("any_peer", "reliable")
|
||||
func release_item_authority(item_path: NodePath, lin: Vector3, ang: Vector3) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
_set_item_authority.rpc(item_path, 1)
|
||||
var item := get_node_or_null(item_path)
|
||||
if item is RigidBody3D:
|
||||
item.freeze = false
|
||||
item.linear_velocity = lin
|
||||
item.angular_velocity = ang
|
||||
_try_snap_into_station(item)
|
||||
|
||||
|
||||
# All station snap zones in the world (nodes in the "station" group).
|
||||
func _station_snap_zones() -> Array:
|
||||
var zones := []
|
||||
for station in get_tree().get_nodes_in_group("station"):
|
||||
var zone = station.get_node_or_null("XRToolsSnapZone")
|
||||
if zone:
|
||||
zones.append(zone)
|
||||
return zones
|
||||
|
||||
|
||||
# If the item is snapped into any station, drop it from that station.
|
||||
func _release_from_snap_zones(item: Node) -> void:
|
||||
for zone in _station_snap_zones():
|
||||
if zone.picked_up_object == item:
|
||||
zone.drop_object()
|
||||
|
||||
|
||||
# Snap the item into the nearest empty station snap zone within grab range.
|
||||
func _try_snap_into_station(item: Node) -> void:
|
||||
if not (item is Node3D):
|
||||
return
|
||||
for zone in _station_snap_zones():
|
||||
if is_instance_valid(zone.picked_up_object):
|
||||
continue
|
||||
if zone.global_position.distance_to(item.global_position) <= zone.grab_distance:
|
||||
zone.pick_up_object(item)
|
||||
return
|
||||
|
||||
|
||||
# Server broadcasts an authority assignment so every peer agrees on who owns the
|
||||
# item (set_multiplayer_authority is a local call and must run everywhere).
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _set_item_authority(item_path: NodePath, peer: int) -> void:
|
||||
var item := get_node_or_null(item_path)
|
||||
if not item:
|
||||
return
|
||||
item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable
|
||||
var np := item.get_node_or_null("NetPickable")
|
||||
if np:
|
||||
np.net_held_by = 0 if peer == 1 else peer
|
||||
np.apply_held_state()
|
||||
|
||||
|
||||
func is_server() -> bool:
|
||||
return is_online() and multiplayer.is_server()
|
||||
|
||||
|
||||
## True only when a real ENet session is active. Godot installs a default
|
||||
## OfflineMultiplayerPeer, so a non-null peer alone does not mean "online".
|
||||
func is_online() -> bool:
|
||||
var p := multiplayer.multiplayer_peer
|
||||
return p != null and not (p is OfflineMultiplayerPeer)
|
||||
|
||||
|
||||
## True on the machine that owns authoritative world logic: the server when
|
||||
## online, or the single player when offline. Station logic and spawning should
|
||||
## only run where this is true, so state has one source of truth.
|
||||
func owns_world() -> bool:
|
||||
return not is_online() or is_server()
|
||||
|
||||
|
||||
# --- Station work-progress seam -------------------------------------------
|
||||
|
||||
## Reusable entry point for a client to contribute work to a station (e.g. a
|
||||
## future chopping/gesture station). The client detects the gesture locally and
|
||||
## calls this; the server validates and accumulates. Timer-driven stations like
|
||||
## the Hob don't need it, but it is the drop-in seam for input-driven ones.
|
||||
@rpc("any_peer", "reliable")
|
||||
func submit_work(station_path: NodePath, amount: float) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
var station := get_node_or_null(station_path)
|
||||
if station and station.has_method("add_work"):
|
||||
station.add_work(multiplayer.get_remote_sender_id(), amount)
|
||||
|
||||
|
||||
## Called by main.gd once the world scene is ready, passing its spawners.
|
||||
func register_world(world: Node, players_spawner: MultiplayerSpawner, items_spawner: MultiplayerSpawner) -> void:
|
||||
_world = world
|
||||
_players_spawner = players_spawner
|
||||
_items_spawner = items_spawner
|
||||
if _items_spawner:
|
||||
_items_spawner.spawn_function = _spawn_item_from_data
|
||||
log_line("World registered (players_spawner=%s items_spawner=%s)" % [str(players_spawner != null), str(items_spawner != null)])
|
||||
|
||||
|
||||
## Called by main.gd after it has registered the world and connected its
|
||||
## player_joined/left listeners. Kicks off any command-line driven session so
|
||||
## that session signals never fire before the world is listening.
|
||||
func world_ready() -> void:
|
||||
_handle_cmdline()
|
||||
|
||||
|
||||
# --- Session signal handlers ----------------------------------------------
|
||||
|
||||
func _on_peer_connected(peer_id: int) -> void:
|
||||
log_line("peer_connected: %d" % peer_id)
|
||||
# Only the server reacts by materialising that peer's player.
|
||||
if is_server():
|
||||
_on_player_present(peer_id)
|
||||
|
||||
func _on_peer_disconnected(peer_id: int) -> void:
|
||||
log_line("peer_disconnected: %d" % peer_id)
|
||||
if is_server():
|
||||
_on_player_absent(peer_id)
|
||||
|
||||
func _on_connected_to_server() -> void:
|
||||
log_line("connected_to_server (my id=%d)" % multiplayer.get_unique_id())
|
||||
session_started.emit(false)
|
||||
|
||||
func _on_connection_failed() -> void:
|
||||
log_line("connection_failed")
|
||||
_go_offline()
|
||||
connection_failed.emit()
|
||||
|
||||
func _on_server_disconnected() -> void:
|
||||
log_line("server_disconnected")
|
||||
_go_offline()
|
||||
session_ended.emit()
|
||||
|
||||
|
||||
# Player materialise/dematerialise. Phase 2 wires these to the PlayersSpawner;
|
||||
# for now they announce presence so the transport layer is independently testable.
|
||||
func _on_player_present(peer_id: int) -> void:
|
||||
log_line("player_present: %d" % peer_id)
|
||||
player_joined.emit(peer_id)
|
||||
|
||||
func _on_player_absent(peer_id: int) -> void:
|
||||
log_line("player_absent: %d" % peer_id)
|
||||
player_left.emit(peer_id)
|
||||
|
||||
|
||||
# --- Command-line driven test bootstrap -----------------------------------
|
||||
|
||||
func _handle_cmdline() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if args.has("--server"):
|
||||
log_line("cmdline: --server")
|
||||
host()
|
||||
elif args.has("--join"):
|
||||
var idx := args.find("--join")
|
||||
var addr := "127.0.0.1"
|
||||
if idx + 1 < args.size():
|
||||
addr = args[idx + 1]
|
||||
log_line("cmdline: --join %s" % addr)
|
||||
join(addr)
|
||||
|
||||
|
||||
# --- Logging ---------------------------------------------------------------
|
||||
|
||||
func _open_log() -> void:
|
||||
var dir := OS.get_environment("TEMP")
|
||||
if dir.is_empty():
|
||||
dir = OS.get_environment("TMPDIR")
|
||||
if dir.is_empty():
|
||||
dir = "user://"
|
||||
var path := dir.path_join("vryhungry_net_%d.log" % OS.get_process_id())
|
||||
_log_file = FileAccess.open(path, FileAccess.WRITE)
|
||||
log_line("=== NetworkManager log (pid %d) ===" % OS.get_process_id())
|
||||
|
||||
func log_line(s: String) -> void:
|
||||
var id := 0
|
||||
var p := multiplayer.multiplayer_peer
|
||||
if p != null and p.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||
id = multiplayer.get_unique_id()
|
||||
var line := "[NET %d] %s" % [id, s]
|
||||
print(line)
|
||||
if _log_file:
|
||||
_log_file.store_line(line)
|
||||
_log_file.flush()
|
||||
@@ -0,0 +1 @@
|
||||
uid://deefoory0vqmm
|
||||
@@ -0,0 +1,37 @@
|
||||
extends Node3D
|
||||
|
||||
## World-space main menu: hosts the 2D join_panel on an XRToolsViewport2DIn3D so
|
||||
## the player can point at it with a controller laser. Re-emits the panel's
|
||||
## intents; main.gd drives the actual networking.
|
||||
|
||||
signal host_pressed()
|
||||
signal join_pressed(ip: String)
|
||||
signal solo_pressed()
|
||||
|
||||
@onready var _vp: XRToolsViewport2DIn3D = $Viewport2Din3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# The viewport instantiates its 2D scene during its own _ready; connect once
|
||||
# that has happened.
|
||||
_connect_scene.call_deferred()
|
||||
|
||||
|
||||
func _connect_scene() -> void:
|
||||
_vp.connect_scene_signal("host_pressed", func(): host_pressed.emit())
|
||||
_vp.connect_scene_signal("join_pressed", func(ip): join_pressed.emit(ip))
|
||||
_vp.connect_scene_signal("solo_pressed", func(): solo_pressed.emit())
|
||||
|
||||
|
||||
func show_menu() -> void:
|
||||
visible = true
|
||||
|
||||
|
||||
func hide_menu() -> void:
|
||||
visible = false
|
||||
|
||||
|
||||
func set_status(text: String) -> void:
|
||||
var scene := _vp.get_scene_instance()
|
||||
if scene and scene.has_method("set_status"):
|
||||
scene.set_status(text)
|
||||
@@ -0,0 +1 @@
|
||||
uid://1o2nca75po1f
|
||||
@@ -0,0 +1,13 @@
|
||||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://Net/network_menu.gd" id="1_menu"]
|
||||
[ext_resource type="PackedScene" uid="uid://clujaf3u776a3" path="res://addons/godot-xr-tools/objects/viewport_2d_in_3d.tscn" id="2_vp"]
|
||||
[ext_resource type="PackedScene" path="res://Net/join_panel.tscn" id="3_panel"]
|
||||
|
||||
[node name="NetworkMenu" type="Node3D"]
|
||||
script = ExtResource("1_menu")
|
||||
|
||||
[node name="Viewport2Din3D" parent="." instance=ExtResource("2_vp")]
|
||||
screen_size = Vector2(0.6, 0.4)
|
||||
scene = ExtResource("3_panel")
|
||||
viewport_size = Vector2(600, 400)
|
||||
@@ -0,0 +1,48 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://cnetplayer0001"]
|
||||
|
||||
[ext_resource type="Script" path="res://Player/player.gd" id="1_player"]
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_player"]
|
||||
properties/0/path = NodePath(".:head_xform")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:left_xform")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
properties/2/path = NodePath(".:right_xform")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 1
|
||||
properties/3/path = NodePath(".:net_heartbeat")
|
||||
properties/3/spawn = true
|
||||
properties/3/replication_mode = 1
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Mat_head"]
|
||||
albedo_color = Color(0.85, 0.7, 0.55, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="Mesh_head"]
|
||||
material = SubResource("Mat_head")
|
||||
size = Vector3(0.18, 0.24, 0.16)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Mat_hand"]
|
||||
albedo_color = Color(0.2, 0.5, 0.9, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="Mesh_hand"]
|
||||
material = SubResource("Mat_hand")
|
||||
size = Vector3(0.09, 0.05, 0.12)
|
||||
|
||||
[node name="Player" type="Node3D"]
|
||||
script = ExtResource("1_player")
|
||||
|
||||
[node name="Avatar" type="Node3D" parent="."]
|
||||
|
||||
[node name="Head" type="MeshInstance3D" parent="Avatar"]
|
||||
mesh = SubResource("Mesh_head")
|
||||
|
||||
[node name="LeftHand" type="MeshInstance3D" parent="Avatar"]
|
||||
mesh = SubResource("Mesh_hand")
|
||||
|
||||
[node name="RightHand" type="MeshInstance3D" parent="Avatar"]
|
||||
mesh = SubResource("Mesh_hand")
|
||||
|
||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("Repl_player")
|
||||
@@ -0,0 +1,80 @@
|
||||
extends Node3D
|
||||
|
||||
## One instance per connected peer. The owning peer is the multiplayer
|
||||
## authority. The local (authority) player builds the full XR rig from
|
||||
## XROrigin.tscn and feeds its head/hand poses into synced properties; remote
|
||||
## players show a lightweight avatar driven by those synced poses.
|
||||
|
||||
const RIG_SCENE := preload("res://XROrigin.tscn")
|
||||
|
||||
# Synced avatar pose. Authority writes these from the XR trackers each frame;
|
||||
# remotes read them onto the Avatar. net_heartbeat is a monotonic counter used
|
||||
# to confirm replication is live (also handy for debugging).
|
||||
@export var head_xform: Transform3D = Transform3D.IDENTITY
|
||||
@export var left_xform: Transform3D = Transform3D.IDENTITY
|
||||
@export var right_xform: Transform3D = Transform3D.IDENTITY
|
||||
@export var net_heartbeat: int = 0
|
||||
|
||||
var _rig: Node3D
|
||||
var _camera: Node3D
|
||||
var _left: Node3D
|
||||
var _right: Node3D
|
||||
var _is_local := false
|
||||
|
||||
@onready var _avatar: Node3D = $Avatar
|
||||
@onready var _avatar_head: Node3D = $Avatar/Head
|
||||
@onready var _avatar_left: Node3D = $Avatar/LeftHand
|
||||
@onready var _avatar_right: Node3D = $Avatar/RightHand
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# The spawner names each player by its owning peer id.
|
||||
set_multiplayer_authority(str(name).to_int())
|
||||
|
||||
|
||||
# (replication config is authored in Player.tscn's MultiplayerSynchronizer)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_is_local = is_multiplayer_authority()
|
||||
if _is_local:
|
||||
_setup_local()
|
||||
else:
|
||||
_setup_remote()
|
||||
if Engine.has_singleton("NetworkManager") or get_node_or_null("/root/NetworkManager"):
|
||||
NetworkManager.log_line("Player %s ready (authority=%d local=%s)" % [name, get_multiplayer_authority(), str(_is_local)])
|
||||
|
||||
|
||||
func _setup_local() -> void:
|
||||
# We see through the headset; hide our own avatar mesh.
|
||||
_avatar.visible = false
|
||||
_rig = RIG_SCENE.instantiate()
|
||||
add_child(_rig)
|
||||
_camera = _rig.get_node_or_null("XRCamera3D")
|
||||
_left = _rig.get_node_or_null("XRControllerLeftHand")
|
||||
_right = _rig.get_node_or_null("XRControllerRightHand")
|
||||
_enable_xr()
|
||||
|
||||
|
||||
func _enable_xr() -> void:
|
||||
var xr := XRServer.find_interface("OpenXR")
|
||||
if xr and xr.is_initialized():
|
||||
get_viewport().use_xr = true
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
|
||||
func _setup_remote() -> void:
|
||||
_avatar.visible = true
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _is_local:
|
||||
if _camera:
|
||||
head_xform = _camera.global_transform
|
||||
left_xform = _left.global_transform
|
||||
right_xform = _right.global_transform
|
||||
net_heartbeat += 1
|
||||
else:
|
||||
_avatar_head.global_transform = head_xform
|
||||
_avatar_left.global_transform = left_xform
|
||||
_avatar_right.global_transform = right_xform
|
||||
@@ -0,0 +1 @@
|
||||
uid://ndrfa2xle033
|
||||
@@ -0,0 +1,19 @@
|
||||
[gd_scene format=3 uid="uid://3lr2dhy62rhk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://Prefabs/combinable_item.gd" id="1_q21ti"]
|
||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="2_anbu7"]
|
||||
[ext_resource type="Script" uid="uid://e3im02nq5cye" path="res://Prefabs/combine_zone.gd" id="4_0om48"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_combine"]
|
||||
size = Vector3(0.16, 0.16, 0.16)
|
||||
|
||||
[node name="CombinableItem" type="Node3D" unique_id=996936271]
|
||||
script = ExtResource("1_q21ti")
|
||||
id = &"some_food_item"
|
||||
recipes = Array[ExtResource("2_anbu7")]([null])
|
||||
|
||||
[node name="CombineZone" type="Area3D" parent="." unique_id=1514178016]
|
||||
script = ExtResource("4_0om48")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="CombineZone" unique_id=706892516]
|
||||
shape = SubResource("BoxShape3D_combine")
|
||||
@@ -8,7 +8,7 @@ extends Area3D
|
||||
## incoming item is consumed.
|
||||
|
||||
# The pickable this zone belongs to (its parent).
|
||||
@onready var _item: XRToolsPickable = get_parent() as XRToolsPickable
|
||||
@onready var _item: XRToolsPickable = get_parent().get_parent() as XRToolsPickable
|
||||
|
||||
# The base item's recipe data.
|
||||
@onready var _combinable: CombinableItem = _find_combinable(_item)
|
||||
@@ -28,7 +28,7 @@ func _ready() -> void:
|
||||
set_deferred("monitorable", false)
|
||||
|
||||
if not _item:
|
||||
push_error("CombineZone must be a child of an XRToolsPickable.")
|
||||
push_error("CombineZone must be a grand child of an XRToolsPickable.")
|
||||
return
|
||||
if not _combinable:
|
||||
push_error("CombineZone requires a CombinableItem sibling on ", _item.name, ".")
|
||||
@@ -54,6 +54,12 @@ func _on_body_entered(body: Node3D) -> void:
|
||||
if _combining or body == _item:
|
||||
return
|
||||
|
||||
# Combining is a world-state change: only the server/offline host runs it.
|
||||
# (Clients never have the base item snapped, so their zone won't monitor, but
|
||||
# guard explicitly in case of overlap during authority handoff.)
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
|
||||
var other := _find_combinable(body)
|
||||
if not other:
|
||||
return
|
||||
@@ -75,11 +81,9 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
||||
_combining = false
|
||||
return
|
||||
|
||||
# Spawn the result at the base item's location, in world space.
|
||||
# Spawn the result (networked) at the base item's location, in world space.
|
||||
var base_transform := _item.global_transform
|
||||
var result_instance: Node3D = result.instantiate()
|
||||
_item.get_tree().current_scene.add_child(result_instance)
|
||||
result_instance.global_transform = base_transform
|
||||
var result_instance := NetworkManager.spawn_item(result.resource_path, base_transform)
|
||||
|
||||
# Free the base item and consume the incoming item.
|
||||
snap_zone.drop_object()
|
||||
@@ -90,7 +94,8 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
||||
other_body.queue_free()
|
||||
|
||||
# Snap the result into the now-empty zone.
|
||||
snap_zone.pick_up_object(result_instance)
|
||||
if result_instance:
|
||||
snap_zone.pick_up_object(result_instance)
|
||||
|
||||
|
||||
# Find a CombinableItem among the direct children of [param node].
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://7earnjgdmwgx"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bs7cxydoxk1cx" path="res://Prefabs/cookable_item.gd" id="1_6hwx6"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://Items/Charcoal.tscn" id="2_rpt7j"]
|
||||
|
||||
[node name="CookableItem" type="Node" unique_id=280820828]
|
||||
script = ExtResource("1_6hwx6")
|
||||
cooking_time = 2.0
|
||||
turns_into = ExtResource("2_rpt7j")
|
||||
@@ -0,0 +1,8 @@
|
||||
class_name FoodItem
|
||||
extends Node
|
||||
|
||||
# Define the Enum at the top of your script
|
||||
enum Type { MEAL, SIDE, INGREDIENT, NONE }
|
||||
|
||||
@export var id: String
|
||||
@export var type: Type = Type.NONE
|
||||
@@ -0,0 +1 @@
|
||||
uid://5tpoohkopryv
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_scene format=3 uid="uid://bfj80jh13t6e5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5tpoohkopryv" path="res://Prefabs/food_item.gd" id="1_0na5k"]
|
||||
|
||||
[node name="FoodItem" type="Node" unique_id=63948206]
|
||||
script = ExtResource("1_0na5k")
|
||||
id = "hamburger"
|
||||
item_type = "Meal"
|
||||
@@ -0,0 +1,75 @@
|
||||
class_name NetPickable
|
||||
extends Node
|
||||
|
||||
## Networks an XRToolsPickable (RigidBody3D). Default authority is the server,
|
||||
## which simulates free items and syncs their transform. Grabbing an item with a
|
||||
## hand transfers authority to the grabbing client (its local kinematic grab
|
||||
## driver then drives the synced transform); releasing returns authority and the
|
||||
## throw velocity to the server. Non-authority peers keep the body frozen and
|
||||
## follow the synced transform, so only one machine ever simulates an item.
|
||||
|
||||
@onready var _item: XRToolsPickable = get_parent() as XRToolsPickable
|
||||
|
||||
var net_held_by := 0 # peer id currently holding (0 = free); kept consistent via NetworkManager
|
||||
var _was_authority := true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not _item:
|
||||
push_error("NetPickable must be a child of an XRToolsPickable")
|
||||
return
|
||||
_item.grabbed.connect(_on_grabbed)
|
||||
_item.released.connect(_on_released)
|
||||
_was_authority = _item.is_multiplayer_authority()
|
||||
_apply_authority_state()
|
||||
|
||||
|
||||
## Enable the item only where it is free or where we are its current holder, so
|
||||
## a peer's snap zone / hand never grabs an item another peer is holding. Called
|
||||
## by NetworkManager whenever net_held_by changes. Note: net_held_by is set per
|
||||
## peer by RPC (not by a synchronizer), so the holder keeps its item enabled
|
||||
## while everyone else disables their copy.
|
||||
func apply_held_state() -> void:
|
||||
if not _item:
|
||||
return
|
||||
_item.enabled = net_held_by == 0 or net_held_by == multiplayer.get_unique_id()
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if not NetworkManager.is_online():
|
||||
return
|
||||
var mine := _item.is_multiplayer_authority()
|
||||
if mine != _was_authority:
|
||||
_was_authority = mine
|
||||
_apply_authority_state()
|
||||
|
||||
|
||||
# Configure physics for whether we own this item or merely mirror it.
|
||||
func _apply_authority_state() -> void:
|
||||
if not _item:
|
||||
return
|
||||
if _item.is_multiplayer_authority():
|
||||
# We own physics: simulate the item whenever it is not held.
|
||||
if not _item.is_picked_up():
|
||||
_item.freeze = false
|
||||
else:
|
||||
# Someone else owns it: follow the synced transform kinematically.
|
||||
_item.freeze = true
|
||||
_item.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||
|
||||
|
||||
func _on_grabbed(_pickable: Node3D, by: Node3D) -> void:
|
||||
if not NetworkManager.is_online():
|
||||
return
|
||||
# Only hand grabs transfer authority; snap-zone grabs are server-driven.
|
||||
if not (by is XRToolsFunctionPickup):
|
||||
return
|
||||
NetworkManager.request_item_authority.rpc_id(1, _item.get_path())
|
||||
|
||||
|
||||
func _on_released(_pickable: Node3D, by: Node3D) -> void:
|
||||
if not NetworkManager.is_online():
|
||||
return
|
||||
if not (by is XRToolsFunctionPickup):
|
||||
return
|
||||
NetworkManager.release_item_authority.rpc_id(1, _item.get_path(), _item.linear_velocity, _item.angular_velocity)
|
||||
@@ -0,0 +1 @@
|
||||
uid://6n784li8n5ck
|
||||
@@ -0,0 +1,132 @@
|
||||
[gd_scene format=3 uid="uid://do6mrslyqe5qe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_o1b3t"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_irf4n"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_ctden"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="5_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="6_eoxxy"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="7_n8fyw"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="8_rraok"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="9_sq0s2"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="10_ay2w6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="11_qn1ku"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="12_hfwnn"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
||||
material = ExtResource("3_irf4n")
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_i5j2u"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_dlkho"]
|
||||
radius = 0.3
|
||||
|
||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||
background_mode = 2
|
||||
sky = ExtResource("7_n8fyw")
|
||||
reflected_light_source = 2
|
||||
ssr_enabled = true
|
||||
ssao_enabled = true
|
||||
ssil_enabled = true
|
||||
sdfgi_enabled = true
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||
script = ExtResource("1_57ppd")
|
||||
|
||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_o1b3t")]
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
||||
|
||||
[node name="Floor" type="StaticBody3D" parent="." unique_id=122279514]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor" unique_id=958841648]
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor/CollisionShape3D" unique_id=1939997056]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||
mesh = SubResource("BoxMesh_24d3s")
|
||||
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_ctden")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
||||
|
||||
[node name="Counter" type="StaticBody3D" parent="." unique_id=1487893288 groups=["station"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.73186195, 0.8981018, -1.484)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Counter" unique_id=692568688]
|
||||
shape = SubResource("BoxShape3D_24d3s")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Counter/CollisionShape3D" unique_id=127773521]
|
||||
mesh = SubResource("BoxMesh_i5j2u")
|
||||
|
||||
[node name="Label3D" type="Label3D" parent="Counter/CollisionShape3D" unique_id=149978729]
|
||||
transform = Transform3D(0.9999992, 0, 0, 0, 0.99999946, 0, 0, 0, 0.9999992, 0.002797456, 0.6309581, -0.2812457)
|
||||
text = "Counter"
|
||||
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="Counter" unique_id=1647380272 groups=["station"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
||||
collision_layer = 65536
|
||||
collision_mask = 65536
|
||||
script = ExtResource("5_57ppd")
|
||||
snap_mode = 1
|
||||
initial_object = NodePath("../../Plate")
|
||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="Counter/XRToolsSnapZone" unique_id=969340130]
|
||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.000975132, 0)
|
||||
shape = SubResource("SphereShape3D_dlkho")
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||
environment = SubResource("Environment_bvwq1")
|
||||
|
||||
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("8_rraok")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8162017, 1.6081157, -1.4714175)
|
||||
|
||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("9_sq0s2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.832131, 0.40028095, -1.4813508)
|
||||
|
||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("10_ay2w6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.72025335, 1.4458435, -1.5107731)
|
||||
|
||||
[node name="burger" parent="." unique_id=1417604760 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1439226, 1.4195822, -1.2004558)
|
||||
|
||||
[node name="burger2" parent="." unique_id=1584460510 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1396115, 1.5300478, -1.2016745)
|
||||
|
||||
[node name="burger3" parent="." unique_id=1884095916 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1424646, 1.4969791, -1.210453)
|
||||
|
||||
[node name="burger4" parent="." unique_id=1844818864 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1414003, 1.4543622, -1.210453)
|
||||
|
||||
[node name="Hamburger" parent="." unique_id=761091445 instance=ExtResource("12_hfwnn")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.18456237, 1.5438088, -1.3716147)
|
||||
|
||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.042440414, 1.4792972, -1.0473135)
|
||||
|
||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -0.029038578, 1.4639391, -1.1673055)
|
||||
|
||||
[node name="PickableObject3" parent="." unique_id=879935619 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.15074077, 1.4792972, -1.0473135)
|
||||
|
||||
[node name="PickableObject4" parent="." unique_id=198541902 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -0.13733894, 1.4639391, -1.1673055)
|
||||
|
||||
[node name="Hamburger2" parent="." unique_id=1349934579 instance=ExtResource("12_hfwnn")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15603885, 1.5329368, -1.6386203)
|
||||
|
||||
[node name="PickableObject5" parent="." unique_id=1021333509 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.06786254, 1.4792972, -1.0473135)
|
||||
|
||||
[node name="PickableObject6" parent="." unique_id=1268843669 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.08126438, 1.4639391, -1.1673055)
|
||||
@@ -1,25 +1,28 @@
|
||||
[gd_scene format=3 uid="uid://do6mrslyqe5qe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_2yrrf"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_lc11c"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_wlcqk"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Hob.tscn" id="4_cposl"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Counter.tscn" id="5_rbb5l"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://PickupCube.tscn" id="6_65p64"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="7_a81vl"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://BurgerBuns.tscn" id="8_wlcqk"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://BurgerBunsDispenser.tscn" id="9_wlcqk"]
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_o1b3t"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_irf4n"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_ctden"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="5_di04w"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="6_eoxxy"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="7_n8fyw"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="8_rraok"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="9_sq0s2"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="10_ay2w6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="11_qn1ku"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="12_hfwnn"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
||||
material = ExtResource("3_wlcqk")
|
||||
material = ExtResource("3_irf4n")
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||
background_mode = 2
|
||||
sky = ExtResource("7_a81vl")
|
||||
sky = ExtResource("7_n8fyw")
|
||||
reflected_light_source = 2
|
||||
ssr_enabled = true
|
||||
ssao_enabled = true
|
||||
@@ -27,9 +30,9 @@ ssil_enabled = true
|
||||
sdfgi_enabled = true
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||
script = ExtResource("1_2yrrf")
|
||||
script = ExtResource("1_57ppd")
|
||||
|
||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_lc11c")]
|
||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_o1b3t")]
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
||||
@@ -44,26 +47,44 @@ shape = SubResource("BoxShape3D_vlqg6")
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||
mesh = SubResource("BoxMesh_24d3s")
|
||||
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_cposl")]
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_ctden")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
||||
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("5_rbb5l")]
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("5_di04w")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.73186195, 0.8981018, -1.484)
|
||||
|
||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("6_65p64")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.70505303, 1.5761534, -1.0317149)
|
||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.5842906, 1.5761534, -1.8163961)
|
||||
|
||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("6_65p64")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.28315204, 1.5607953, -1.0657519)
|
||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("6_eoxxy")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.14558735, 1.5607953, -1.8242048)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||
environment = SubResource("Environment_bvwq1")
|
||||
|
||||
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("8_wlcqk")]
|
||||
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("8_rraok")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8162017, 1.6081157, -1.4714175)
|
||||
|
||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("9_wlcqk")]
|
||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("9_sq0s2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.832131, 0.40028095, -1.4813508)
|
||||
|
||||
[node name="BurgerBuns2" parent="." unique_id=1909843084 instance=ExtResource("8_wlcqk")]
|
||||
[node name="BurgerBuns2" parent="." unique_id=1909843084 instance=ExtResource("8_rraok")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1282921, 1.4976386, -1.0923254)
|
||||
|
||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("10_ay2w6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.53572595, 1.4458435, -1.135189)
|
||||
|
||||
[node name="burger" parent="." unique_id=1417604760 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.91607785, 1.4235804, -1.0899508)
|
||||
|
||||
[node name="burger2" parent="." unique_id=1584460510 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.23121053, 1.4235804, -1.3225337)
|
||||
|
||||
[node name="burger3" parent="." unique_id=1884095916 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.22395861, 1.4235804, -1.1413177)
|
||||
|
||||
[node name="burger4" parent="." unique_id=1844818864 instance=ExtResource("11_qn1ku")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.40808713, 1.4235804, -1.1413177)
|
||||
|
||||
[node name="Hamburger" parent="." unique_id=761091445 instance=ExtResource("12_hfwnn")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5295425, 1.994761, -1.1201752)
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene format=3 uid="uid://c6rift56ql3f8"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://item_dispenser.gd" id="1_myl3s"]
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://Stations/item_dispenser.gd" id="1_myl3s"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="1_p30r1"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://BurgerBuns.tscn" id="2_1q74o"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="2_1q74o"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
|
||||
radius = 0.3
|
||||
@@ -44,21 +44,21 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns" unique_id=1328221793]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns" unique_id=686624728]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns/CSGCylinder3D3" unique_id=1113894081]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
@@ -71,21 +71,21 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns2" unique_id=190780737]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns2" unique_id=557804311]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns2/CSGCylinder3D3" unique_id=605379447]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
@@ -98,21 +98,21 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns3" unique_id=1686142490]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns3" unique_id=1773290691]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns3/CSGCylinder3D3" unique_id=801922493]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
@@ -125,21 +125,21 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns4" unique_id=547208858]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns4" unique_id=185898007]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns4/CSGCylinder3D3" unique_id=2117029178]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
@@ -152,21 +152,21 @@ smoothing_angle = 138.9
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns5" unique_id=102374914]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.004122257, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns5" unique_id=189923990]
|
||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.048392475, 0.029736161, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D/CSGCombiner3DBuns5/CSGCylinder3D3" unique_id=1980594001]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||
radius = 0.1
|
||||
radius = 0.085
|
||||
height = 0.04
|
||||
sides = 16
|
||||
cone = true
|
||||
@@ -7,8 +7,9 @@
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_i5j2u"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_dlkho"]
|
||||
radius = 0.3
|
||||
|
||||
[node name="Counter" type="StaticBody3D" unique_id=1487893288]
|
||||
[node name="Counter" type="StaticBody3D" unique_id=1487893288 groups=["station"]]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=692568688]
|
||||
shape = SubResource("BoxShape3D_24d3s")
|
||||
@@ -20,12 +21,12 @@ mesh = SubResource("BoxMesh_i5j2u")
|
||||
transform = Transform3D(0.9999992, 0, 0, 0, 0.99999946, 0, 0, 0, 0.9999992, 0.002797456, 0.6309581, -0.2812457)
|
||||
text = "Counter"
|
||||
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="." unique_id=1647380272]
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="." unique_id=1647380272 groups=["station"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
||||
collision_layer = 65536
|
||||
collision_mask = 65536
|
||||
script = ExtResource("1_dlkho")
|
||||
snap_mode = 1
|
||||
snap_mode = 0
|
||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=969340130]
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://j7caslh27nor"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bsn8vhv5adxdo" path="res://hob.gd" id="1_7jc4g"]
|
||||
[ext_resource type="Script" uid="uid://bsn8vhv5adxdo" path="res://Stations/hob.gd" id="1_7jc4g"]
|
||||
[ext_resource type="Texture2D" uid="uid://b7n8d4krwbcvl" path="res://Textures/5.png" id="2_0sasq"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_er1dp"]
|
||||
|
||||
@@ -14,8 +14,14 @@ uv1_triplanar = true
|
||||
material = SubResource("StandardMaterial3D_m5xe2")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
||||
radius = 0.3
|
||||
|
||||
[node name="Hob" type="StaticBody3D" unique_id=1687971542]
|
||||
[sub_resource type="SceneReplicationConfig" id="Repl_hob"]
|
||||
properties/0/path = NodePath(".:progress")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
|
||||
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
||||
script = ExtResource("1_7jc4g")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1283846023]
|
||||
@@ -33,9 +39,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
||||
collision_layer = 65536
|
||||
collision_mask = 65536
|
||||
script = ExtResource("2_er1dp")
|
||||
snap_mode = 1
|
||||
snap_mode = 0
|
||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=370920392]
|
||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.000975132, 0)
|
||||
shape = SubResource("SphereShape3D_vlqg6")
|
||||
|
||||
[node name="ProgressSync" type="MultiplayerSynchronizer" parent="."]
|
||||
replication_config = SubResource("Repl_hob")
|
||||
@@ -0,0 +1,62 @@
|
||||
extends StaticBody3D
|
||||
|
||||
## Cooking station. The cook timer and item conversion run only on the machine
|
||||
## that owns world logic (server or offline host); `progress` is replicated to
|
||||
## clients for UI via a MultiplayerSynchronizer. This is the reference
|
||||
## implementation of the reusable "station work-progress" pattern: a server-owned
|
||||
## progress value advanced by an input source (here, a timer), which converts the
|
||||
## held item once it reaches the threshold and spawns the result over the network.
|
||||
|
||||
@export var cook_speed := 1.0
|
||||
@onready var snap_zone: XRToolsSnapZone = $XRToolsSnapZone
|
||||
|
||||
var item = null
|
||||
var progress: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if snap_zone.initial_object:
|
||||
item = snap_zone.initial_object
|
||||
|
||||
|
||||
func convert_item(cookable: CookableItem) -> void:
|
||||
var old_pickable = snap_zone.picked_up_object
|
||||
if not old_pickable:
|
||||
push_warning("Hob finished cooking item, but snap zone is missing its reference")
|
||||
return
|
||||
|
||||
var original_transform: Transform3D = old_pickable.global_transform
|
||||
|
||||
# Spawn the result over the network (server) or locally (offline).
|
||||
var result := NetworkManager.spawn_item(cookable.turns_into.resource_path, original_transform)
|
||||
|
||||
# Remove the cooked item (despawn replicates for spawner-managed items).
|
||||
snap_zone.drop_object()
|
||||
old_pickable.queue_free()
|
||||
|
||||
# Snap the result into the now-empty zone.
|
||||
if result:
|
||||
snap_zone.pick_up_object(result)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
# Only the world owner runs cooking logic; clients receive `progress` + the
|
||||
# converted item via replication.
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
|
||||
# Find the CookableItem in the held object.
|
||||
var cookable = null
|
||||
if snap_zone.picked_up_object:
|
||||
var matches = snap_zone.picked_up_object.get_children().filter(func(c): return c is CookableItem)
|
||||
if matches.size() > 0:
|
||||
cookable = matches[0]
|
||||
|
||||
# Advance progress and convert once cooked.
|
||||
if cookable:
|
||||
progress += cook_speed * delta
|
||||
if progress >= cookable.cooking_time:
|
||||
progress = 0.0
|
||||
convert_item(cookable)
|
||||
else:
|
||||
progress = 0.0
|
||||
@@ -13,10 +13,10 @@ func _ready() -> void:
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
# Only the world owner dispenses, so every client doesn't spawn its own copy.
|
||||
if not NetworkManager.owns_world():
|
||||
return
|
||||
if not snap_zone.picked_up_object:
|
||||
var new_item = item_scene.instantiate()
|
||||
|
||||
# Add the new item to the hob's parent (so it lives in the main world space)
|
||||
get_parent().add_child(new_item)
|
||||
new_item.global_transform = snap_zone.transform
|
||||
snap_zone.pick_up_object(new_item)
|
||||
var new_item := NetworkManager.spawn_item(item_scene.resource_path, snap_zone.global_transform)
|
||||
if new_item:
|
||||
snap_zone.pick_up_object(new_item)
|
||||
@@ -0,0 +1,56 @@
|
||||
- Move recipes into a Yaml file that is read, and handles symetry through a Static manager class?
|
||||
A `FoodItem` has an ID.
|
||||
Example:
|
||||
```yaml
|
||||
combining: # The combinging of two items, entries always have a list of exactly two items
|
||||
cooked_burger:
|
||||
- cooked_burger
|
||||
- burger_buns
|
||||
dough:
|
||||
- flour
|
||||
- water
|
||||
|
||||
cooking:
|
||||
cooked_burger:
|
||||
ingredient: raw_burger
|
||||
time: 3
|
||||
charcoal:
|
||||
ingredient: cooked_burger
|
||||
time: 4
|
||||
charcoal:
|
||||
ingredient: toast
|
||||
time: 2
|
||||
|
||||
augmenting: # Process of adding an item onto another without changing it's type, but toggling attributes.
|
||||
cooked_burger:
|
||||
has_tomato: sliced_tomato
|
||||
has_cheese: cheese
|
||||
salad:
|
||||
has_tomato: sliced_tomato
|
||||
has_olives: olives
|
||||
|
||||
chopping:
|
||||
salad:
|
||||
ingredient: lettuce
|
||||
work: 50 # Wave arms faster makes work go faster
|
||||
|
||||
rolling:
|
||||
pie_base:
|
||||
ingredient: dough
|
||||
work: 80
|
||||
```
|
||||
```py
|
||||
on_area_enter(other):
|
||||
var result = RecipeManager.getCombination(my_id, other_id)
|
||||
if result:
|
||||
convertInto(result)
|
||||
```
|
||||
|
||||
- Counters are stations like hobs that convert items by chopping, rolling ect depending on `RecipeManager`,
|
||||
and only converts items after hand gestures are done enough?
|
||||
|
||||
- Plates are lists of `FoodItems`?
|
||||
Plate[`Hamburger`] accepts `Chips` => Plate[`Hamburger`, `Chips`]
|
||||
Plate[`Hamburger`, `Chips`] accepts `Cheese` => Plate[`Hamburger(Cheese=true)`, `Chips`]
|
||||
The logic for combining has to be expanded for plates.
|
||||
- Plates have a property `is_dirty` they get after a customer clears the plate, and is disabled after being processed in `Sink`
|
||||
@@ -10,6 +10,4 @@ uv1_triplanar = true
|
||||
|
||||
[resource]
|
||||
next_pass = SubResource("StandardMaterial3D_0gbf8")
|
||||
metallic = 1.0
|
||||
metallic_specular = 0.0
|
||||
roughness = 0.0
|
||||
albedo_color = Color(0.35156325, 0.3515627, 0.3515629, 1)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
[ext_resource type="PackedScene" uid="uid://diyu06cw06syv" path="res://addons/godot-xr-tools/player/player_body.tscn" id="4_6xr3x"]
|
||||
[ext_resource type="PackedScene" uid="uid://b6bk2pj8vbj28" path="res://addons/godot-xr-tools/functions/movement_turn.tscn" id="4_rd8py"]
|
||||
[ext_resource type="Script" uid="uid://ck4yn3hxuobj7" path="res://addons/godot-xr-tools/player/player_body.gd" id="5_rd8py"]
|
||||
[ext_resource type="PackedScene" uid="uid://cqhw276realc" path="res://addons/godot-xr-tools/functions/function_pointer.tscn" id="6_ptr"]
|
||||
|
||||
[node name="XROrigin3D" type="XROrigin3D" unique_id=2055526621]
|
||||
|
||||
@@ -25,6 +26,10 @@ strafe = true
|
||||
[node name="FunctionPickup" parent="XRControllerLeftHand" unique_id=2133415351 instance=ExtResource("3_rd8py")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.009340696, -0.0074635968, 0.024033919)
|
||||
|
||||
[node name="FunctionPointer" parent="XRControllerLeftHand" instance=ExtResource("6_ptr")]
|
||||
show_laser = 2
|
||||
show_target = true
|
||||
|
||||
[node name="XRControllerRightHand" type="XRController3D" parent="." unique_id=202756852]
|
||||
tracker = &"right_hand"
|
||||
|
||||
@@ -39,6 +44,10 @@ smooth_turn_speed = 3.5
|
||||
[node name="FunctionPickup" parent="XRControllerRightHand" unique_id=1876210450 instance=ExtResource("3_rd8py")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0074635968, 0.024033919)
|
||||
|
||||
[node name="FunctionPointer" parent="XRControllerRightHand" instance=ExtResource("6_ptr")]
|
||||
show_laser = 2
|
||||
show_target = true
|
||||
|
||||
[node name="PlayerBody" type="CharacterBody3D" parent="." unique_id=1444632058 groups=["player_body"] instance=ExtResource("4_6xr3x")]
|
||||
process_priority = -100
|
||||
process_physics_priority = -100
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2022-2026 HolonProduction
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,305 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Manages the loading and saving of other data.
|
||||
|
||||
|
||||
const __UUID := preload("../uuid/uuid.gd")
|
||||
const __Category := preload("category.gd")
|
||||
const __Layout := preload("layout.gd")
|
||||
const __Stage := preload("stage.gd")
|
||||
const __Task := preload("task.gd")
|
||||
const __KanbanResource := preload("kanban_resource.gd")
|
||||
|
||||
var layout: __Layout:
|
||||
set(value):
|
||||
if layout:
|
||||
layout.changed.disconnect(__notify_changed)
|
||||
layout = value
|
||||
layout.changed.connect(__notify_changed)
|
||||
|
||||
var __categories: Dictionary
|
||||
var __stages: Dictionary
|
||||
var __tasks: Dictionary
|
||||
|
||||
|
||||
## Generates a json representation of the board.
|
||||
func to_json() -> Dictionary:
|
||||
var dict := {}
|
||||
|
||||
var category_data := __propagate_uuid_dict(__categories)
|
||||
dict["categories"] = category_data
|
||||
|
||||
var stage_data := __propagate_uuid_dict(__stages)
|
||||
dict["stages"] = stage_data
|
||||
|
||||
var task_data := __propagate_uuid_dict(__tasks)
|
||||
dict["tasks"] = task_data
|
||||
|
||||
dict["layout"] = layout.to_json()
|
||||
|
||||
return dict
|
||||
|
||||
|
||||
## Save the board at `path`.
|
||||
func save(path: String) -> void:
|
||||
var file = FileAccess.open(path, FileAccess.WRITE)
|
||||
if not file:
|
||||
push_error("Error " + str(FileAccess.get_open_error()) + " while opening file for saving board data at " + path)
|
||||
file.close()
|
||||
return
|
||||
|
||||
var string := JSON.stringify(to_json(), "\t", false)
|
||||
file.store_string(string)
|
||||
file.close()
|
||||
|
||||
|
||||
## Initializes the board state from json data.
|
||||
func from_json(json: Dictionary) -> void:
|
||||
__instantiate_uuid_array(json.get("categories", null), __Category, __add_category)
|
||||
__instantiate_uuid_array(json.get("stages", null), __Stage, __add_stage)
|
||||
__instantiate_uuid_array(json.get("tasks", null), __Task, __add_task)
|
||||
|
||||
layout = __Layout.new([])
|
||||
if json.get("layout", null) is Dictionary:
|
||||
layout.from_json(json["layout"])
|
||||
else:
|
||||
push_warning("Loading incomplete board data which is missing layout data.")
|
||||
|
||||
|
||||
## Loads the data from `path` into the current instance.
|
||||
func load(path: String) -> void:
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
if not file:
|
||||
push_error("Error " + str(FileAccess.get_open_error()) + " while opening file for loading board data at " + path)
|
||||
file.close()
|
||||
return
|
||||
|
||||
var json = JSON.new()
|
||||
var err = json.parse(file.get_as_text())
|
||||
file.close()
|
||||
if err != OK:
|
||||
push_error("Error " + str(err) + " while parsing board at " + path + " to json. At line " + str(json.get_error_line()) + " the following problem occured:\n" + json.get_error_message())
|
||||
return
|
||||
|
||||
if json.data.has("columns"):
|
||||
__from_legacy_file(json.data)
|
||||
else:
|
||||
from_json(json.data)
|
||||
|
||||
|
||||
## Adds a category and returns the uuid which is associated with it.
|
||||
func add_category(category: __Category, silent: bool = false) -> String:
|
||||
var res := __add_category(category)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
return res
|
||||
|
||||
## Returns the category associated with the given uuid or `null` if there is none.
|
||||
func get_category(uuid: String) -> __Category:
|
||||
if not __categories.has(uuid) and uuid != "":
|
||||
push_warning('There is no category with the uuid "' + uuid + '".')
|
||||
return __categories.get(uuid, null)
|
||||
|
||||
## Returns the count of categories.
|
||||
func get_category_count() -> int:
|
||||
return len(__categories)
|
||||
|
||||
## Returns the uuid's of all categories.
|
||||
func get_categories() -> Array[String]:
|
||||
var temp: Array[String] = []
|
||||
temp.assign(__categories.keys())
|
||||
return temp
|
||||
|
||||
## Removes a category by uuid.
|
||||
func remove_category(uuid: String, silent: bool = false) -> void:
|
||||
if __categories.has(uuid):
|
||||
__categories[uuid].changed.disconnect(__notify_changed)
|
||||
__categories.erase(uuid)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
else:
|
||||
push_warning("Trying to remove uuid wich is not associated with a category.")
|
||||
|
||||
|
||||
## Adds a stage and returns the uuid which is associated with it.
|
||||
func add_stage(stage: __Stage, silent: bool = false) -> String:
|
||||
var res := __add_stage(stage)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
return res
|
||||
|
||||
## Returns the stage associated with the given uuid or `null` if there is none.
|
||||
func get_stage(uuid: String) -> __Stage:
|
||||
if not __stages.has(uuid) and uuid != "":
|
||||
push_warning('There is no stage with the uuid "' + uuid + '".')
|
||||
return __stages.get(uuid, null)
|
||||
|
||||
## Returns the count of stages.
|
||||
func get_stage_count() -> int:
|
||||
return len(__stages)
|
||||
|
||||
## Returns the uuid's of all stages.
|
||||
func get_stages() -> Array[String]:
|
||||
var temp: Array[String] = []
|
||||
temp.assign(__stages.keys())
|
||||
return temp
|
||||
|
||||
## Removes a stage by uuid.
|
||||
func remove_stage(uuid: String, silent: bool = false) -> void:
|
||||
if __stages.has(uuid):
|
||||
__stages[uuid].changed.disconnect(__notify_changed)
|
||||
__stages.erase(uuid)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
else:
|
||||
push_warning("Trying to remove uuid wich is not associated with a stage.")
|
||||
|
||||
|
||||
## Adds a task and returns the uuid which is associated with it.
|
||||
func add_task(task: __Task, silent: bool = false) -> String:
|
||||
var res := __add_task(task)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
return res
|
||||
|
||||
## Returns the task associated with the given uuid or `null` if there is none.
|
||||
func get_task(uuid: String) -> __Task:
|
||||
if not __tasks.has(uuid) and uuid != "":
|
||||
push_warning('There is no task with the uuid "' + uuid + '".')
|
||||
return __tasks.get(uuid, null)
|
||||
|
||||
## Returns the count of tasks.
|
||||
func get_task_count() -> int:
|
||||
return len(__tasks)
|
||||
|
||||
## Returns the uuid's of all tasks.
|
||||
func get_tasks() -> Array[String]:
|
||||
var temp: Array[String] = []
|
||||
temp.assign(__tasks.keys())
|
||||
return temp
|
||||
|
||||
## Removes a task by uuid.
|
||||
func remove_task(uuid: String, silent: bool = false) -> void:
|
||||
if __tasks.has(uuid):
|
||||
if __tasks[uuid].changed.is_connected(__notify_changed):
|
||||
__tasks[uuid].changed.disconnect(__notify_changed)
|
||||
__tasks.erase(uuid)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
else:
|
||||
push_warning("Trying to remove uuid wich is not associated with a task.")
|
||||
|
||||
|
||||
# Internal version of `add_category` which can be provided with an uuid suggestion.
|
||||
# The uuid that is passed can be altered by the board if it is already used by
|
||||
# an other category. Therefore always use the returned uuid.
|
||||
func __add_category(category: __Category, uuid: String = "") -> String:
|
||||
category.changed.connect(__notify_changed)
|
||||
|
||||
if __categories.has(uuid):
|
||||
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the category "' + category.title + '".')
|
||||
|
||||
if uuid == "":
|
||||
uuid = __UUID.v4()
|
||||
|
||||
while uuid in __categories.keys():
|
||||
uuid = __UUID.v4()
|
||||
|
||||
__categories[uuid] = category
|
||||
return uuid
|
||||
|
||||
|
||||
# Internal version of `add_stage` which can be provided with an uuid suggestion.
|
||||
func __add_stage(stage: __Stage, uuid: String = "") -> String:
|
||||
stage.changed.connect(__notify_changed)
|
||||
|
||||
if __stages.has(uuid):
|
||||
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the stage "' + stage.title + '".')
|
||||
|
||||
if uuid == "":
|
||||
uuid = __UUID.v4()
|
||||
|
||||
while uuid in __stages.keys():
|
||||
uuid = __UUID.v4()
|
||||
|
||||
__stages[uuid] = stage
|
||||
return uuid
|
||||
|
||||
|
||||
# Internal version of `add_task` which can be provided with an uuid suggestion.
|
||||
func __add_task(task: __Task, uuid: String = "") -> String:
|
||||
task.changed.connect(__notify_changed)
|
||||
|
||||
if __tasks.has(uuid):
|
||||
push_warning("The uuid " + uuid + ' is already used. A new one will be generated for the task "' + task.title + '".')
|
||||
|
||||
if uuid == "":
|
||||
uuid = __UUID.v4()
|
||||
|
||||
while uuid in __tasks.keys():
|
||||
uuid = __UUID.v4()
|
||||
|
||||
__tasks[uuid] = task
|
||||
return uuid
|
||||
|
||||
|
||||
# HACK: `array` should have the type `Array` but then `null` could not be passed.
|
||||
func __instantiate_uuid_array(array, type: Script, add_callback: Callable) -> void:
|
||||
if array == null:
|
||||
push_warning("Loading incomplete board data which is missing data for '" + type.resource_path + "'.")
|
||||
return
|
||||
|
||||
for data in array:
|
||||
var instance: __KanbanResource = type.new()
|
||||
instance.from_json(data)
|
||||
add_callback.call(instance, data.get("uuid", ""))
|
||||
|
||||
|
||||
# Converts a dictionary with (uuid, kanban_resource) pairs into a list
|
||||
# json representations with the uuid added.
|
||||
func __propagate_uuid_dict(dict: Dictionary) -> Array:
|
||||
var res := []
|
||||
for key in dict.keys():
|
||||
var json: Dictionary = {"uuid": key}
|
||||
json.merge(dict[key].to_json())
|
||||
res.append(json)
|
||||
return res
|
||||
|
||||
|
||||
# TODO: Remove this sometime in the future.
|
||||
## Loads a board from the old file format.
|
||||
func __from_legacy_file(data: Dictionary) -> void:
|
||||
var categories: Array[String] = []
|
||||
var tasks: Array[String] = []
|
||||
var stages: Array[String] = []
|
||||
|
||||
for c in data["categories"]:
|
||||
categories.append(
|
||||
__add_category(__Category.new(c["title"], c["color"])),
|
||||
)
|
||||
|
||||
for t in data["tasks"]:
|
||||
tasks.append(
|
||||
__add_task(
|
||||
__Task.new(t["title"], t["details"], categories[t["category"]]),
|
||||
),
|
||||
)
|
||||
|
||||
for s in data["stages"]:
|
||||
var contained_tasks: Array[String] = []
|
||||
for t in s["tasks"]:
|
||||
contained_tasks.append(tasks[t])
|
||||
stages.append(
|
||||
__add_stage(
|
||||
__Stage.new(s["title"], contained_tasks),
|
||||
),
|
||||
)
|
||||
|
||||
var columns: Array[PackedStringArray] = []
|
||||
for c in data["columns"]:
|
||||
var column = PackedStringArray([])
|
||||
for s in c["stages"]:
|
||||
column.append(stages[s])
|
||||
columns.append(column)
|
||||
layout = __Layout.new(columns)
|
||||
@@ -0,0 +1 @@
|
||||
uid://csd0qg7nc4p7m
|
||||
@@ -0,0 +1,43 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Data of a category.
|
||||
|
||||
|
||||
var title: String:
|
||||
set(value):
|
||||
title = value
|
||||
__notify_changed()
|
||||
|
||||
var color: Color:
|
||||
set(value):
|
||||
color = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func _init(p_title: String = "", p_color: Color = Color()) -> void:
|
||||
title = p_title
|
||||
color = p_color
|
||||
super._init()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
return {
|
||||
"title": title,
|
||||
"color": color.to_html(false),
|
||||
}
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
title = "Missing data."
|
||||
color = Color.CORNFLOWER_BLUE
|
||||
|
||||
if json.has("title"):
|
||||
title = json["title"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a title.")
|
||||
|
||||
if json.has("color"):
|
||||
color = Color.html(json["color"])
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a color.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://bymg657d5rk55
|
||||
@@ -0,0 +1,30 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
## Base class for kanban tasks data structures.
|
||||
|
||||
|
||||
## Emitted when the resource changed. The properties are updated before emitting.
|
||||
signal changed()
|
||||
|
||||
var __emit_changed := true
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## Serializes the object as json.
|
||||
func to_json() -> Dictionary:
|
||||
push_error("Method to_json not implemented.")
|
||||
return {}
|
||||
|
||||
|
||||
## Deserializes the object from json.
|
||||
func from_json(json: Dictionary) -> void:
|
||||
push_error("Method from_json not implemented.")
|
||||
|
||||
|
||||
func __notify_changed() -> void:
|
||||
if __emit_changed:
|
||||
changed.emit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dwbcvxus65wau
|
||||
@@ -0,0 +1,49 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Layout data.
|
||||
|
||||
|
||||
# Use `PackedStringArray` because nested typed collections are not supported.
|
||||
var columns: Array[PackedStringArray] = []:
|
||||
get:
|
||||
return columns.duplicate()
|
||||
set(value):
|
||||
columns = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func _init(p_columns: Array[PackedStringArray] = []) -> void:
|
||||
columns = p_columns
|
||||
super._init()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
var cols := []
|
||||
for c in columns:
|
||||
var col = []
|
||||
for uuid in c:
|
||||
col.append(uuid)
|
||||
cols.append(col)
|
||||
return {
|
||||
"columns": cols,
|
||||
}
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
if json.has("columns"):
|
||||
if json["columns"] is Array:
|
||||
var cols: Array[PackedStringArray] = []
|
||||
for c in json["columns"]:
|
||||
var arr := PackedStringArray()
|
||||
if c is Array:
|
||||
for id in c:
|
||||
arr.append(id)
|
||||
else:
|
||||
push_warning("Layout data is corrupted.")
|
||||
cols.append(arr)
|
||||
columns = cols
|
||||
else:
|
||||
push_warning("Layout data is corrupted.")
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a list of columns.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://rt5utu17k4q4
|
||||
@@ -0,0 +1,159 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Contains settings that are not bound to a board.
|
||||
|
||||
|
||||
const DEFAULT_EDITOR_DATA_PATH: String = "res://kanban_tasks_data.kanban"
|
||||
|
||||
enum DescriptionOnBoard {
|
||||
FULL,
|
||||
FIRST_LINE,
|
||||
UNTIL_FIRST_BLANK_LINE,
|
||||
}
|
||||
|
||||
enum StepsOnBoard {
|
||||
ONLY_OPEN,
|
||||
ALL_OPEN_FIRST,
|
||||
ALL_IN_ORDER
|
||||
}
|
||||
|
||||
## Whether the first line of the description is shown on the board.
|
||||
var show_description_preview: bool = true:
|
||||
set(value):
|
||||
show_description_preview = value
|
||||
__notify_changed()
|
||||
|
||||
var show_steps_preview: bool = true:
|
||||
set(value):
|
||||
show_steps_preview = value
|
||||
__notify_changed()
|
||||
|
||||
var show_category_on_board: bool = false:
|
||||
set(value):
|
||||
show_category_on_board = value
|
||||
__notify_changed()
|
||||
|
||||
var edit_step_details_exclusively: bool = false:
|
||||
set(value):
|
||||
edit_step_details_exclusively = value
|
||||
__notify_changed()
|
||||
|
||||
var max_displayed_lines_in_description: int = 0:
|
||||
set(value):
|
||||
max_displayed_lines_in_description = value
|
||||
__notify_changed()
|
||||
|
||||
var description_on_board := DescriptionOnBoard.FIRST_LINE:
|
||||
set(value):
|
||||
description_on_board = value
|
||||
__notify_changed()
|
||||
|
||||
var steps_on_board := StepsOnBoard.ONLY_OPEN:
|
||||
set(value):
|
||||
steps_on_board = value
|
||||
__notify_changed()
|
||||
|
||||
var max_steps_on_board: int = 2:
|
||||
set(value):
|
||||
max_steps_on_board = value
|
||||
__notify_changed()
|
||||
|
||||
var stages_width: int = 200:
|
||||
set(value):
|
||||
stages_width = value
|
||||
__notify_changed()
|
||||
|
||||
var editor_data_file_path: String = DEFAULT_EDITOR_DATA_PATH:
|
||||
set(value):
|
||||
editor_data_file_path = value
|
||||
__notify_changed()
|
||||
|
||||
var warn_about_empty_deletion: bool = false:
|
||||
set(value):
|
||||
warn_about_empty_deletion = value
|
||||
__notify_changed()
|
||||
|
||||
var recent_file_count: int = 5:
|
||||
set(value):
|
||||
recent_file_count = value
|
||||
recent_files.resize(value)
|
||||
__notify_changed()
|
||||
|
||||
var recent_files: PackedStringArray = []:
|
||||
get:
|
||||
return recent_files.duplicate()
|
||||
set(value):
|
||||
recent_files = value
|
||||
__notify_changed()
|
||||
|
||||
# Here such settings can come, which is own responsibiity of a user control.
|
||||
# When it just want to persist its own state, but the setting is not used by anything else.
|
||||
# In this case there is no need to mess up this class with bolerplate code
|
||||
# E.g. the splitter position in the details editor window
|
||||
# Set via set_internal_state to trigger notification
|
||||
# (As no clean-up, during develolpment some mess can remain in it.
|
||||
# Use clear or erase in your code in such cases, just don't forget there)
|
||||
var internal_states: Dictionary = { }
|
||||
|
||||
|
||||
func set_internal_state(property: String, value: Variant) -> void:
|
||||
internal_states[property] = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
var res := {
|
||||
"show_description_preview": show_description_preview,
|
||||
"warn_about_empty_deletion": warn_about_empty_deletion,
|
||||
"edit_step_details_exclusively": edit_step_details_exclusively,
|
||||
"max_displayed_lines_in_description": max_displayed_lines_in_description,
|
||||
"description_on_board": description_on_board,
|
||||
"show_steps_preview": show_steps_preview,
|
||||
"show_category_on_board": show_category_on_board,
|
||||
"steps_on_board": steps_on_board,
|
||||
"max_steps_on_board": max_steps_on_board,
|
||||
"stages_width": stages_width,
|
||||
}
|
||||
|
||||
if not Engine.is_editor_hint():
|
||||
res["recent_file_count"] = recent_file_count
|
||||
res["recent_files"] = recent_files
|
||||
else:
|
||||
res["editor_data_file_path"] = editor_data_file_path
|
||||
|
||||
res["internal_states"] = internal_states
|
||||
|
||||
return res
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
if json.has("show_description_preview"):
|
||||
show_description_preview = json["show_description_preview"]
|
||||
if json.has("warn_about_empty_deletion"):
|
||||
warn_about_empty_deletion = json["warn_about_empty_deletion"]
|
||||
if json.has("edit_step_details_exclusively"):
|
||||
edit_step_details_exclusively = json["edit_step_details_exclusively"]
|
||||
if json.has("max_displayed_lines_in_description"):
|
||||
max_displayed_lines_in_description = json["max_displayed_lines_in_description"]
|
||||
if json.has("description_on_board"):
|
||||
description_on_board = json["description_on_board"]
|
||||
if json.has("editor_data_file_path"):
|
||||
editor_data_file_path = json["editor_data_file_path"]
|
||||
if json.has("recent_file_count"):
|
||||
recent_file_count = json["recent_file_count"]
|
||||
if json.has("recent_files"):
|
||||
recent_files = PackedStringArray(json["recent_files"])
|
||||
if json.has("show_steps_preview"):
|
||||
show_steps_preview = json["show_steps_preview"]
|
||||
if json.has("steps_on_board"):
|
||||
steps_on_board = json["steps_on_board"]
|
||||
if json.has("max_steps_on_board"):
|
||||
max_steps_on_board = json["max_steps_on_board"]
|
||||
if json.has("stages_width"):
|
||||
stages_width = json["stages_width"]
|
||||
if json.has("show_category_on_board"):
|
||||
show_category_on_board = json["show_category_on_board"]
|
||||
if json.has("internal_states"):
|
||||
internal_states = json["internal_states"]
|
||||
__notify_changed()
|
||||
@@ -0,0 +1 @@
|
||||
uid://l6lfl3k3qwxw
|
||||
@@ -0,0 +1,48 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Data of a stage.
|
||||
|
||||
|
||||
var title: String:
|
||||
set(value):
|
||||
title = value
|
||||
__notify_changed()
|
||||
|
||||
var tasks: Array[String] = []:
|
||||
get:
|
||||
# Pass by value to avoid appending without emitting `changed`.
|
||||
return tasks.duplicate()
|
||||
set(value):
|
||||
tasks = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func _init(p_title: String = "", p_tasks: Array[String] = []) -> void:
|
||||
title = p_title
|
||||
tasks = p_tasks
|
||||
super._init()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
return {
|
||||
"title": title,
|
||||
"tasks": tasks,
|
||||
}
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
if json.has("title"):
|
||||
title = json["title"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a title.")
|
||||
|
||||
if json.has("tasks"):
|
||||
# HACK: Workaround for casting to typed array.
|
||||
var s: Array[String] = []
|
||||
for i in json["tasks"]:
|
||||
s.append(i)
|
||||
|
||||
tasks = s
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a list of tasks.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://ciuqm01ng1wfq
|
||||
@@ -0,0 +1,40 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Data of a step.
|
||||
|
||||
|
||||
var details: String:
|
||||
set(value):
|
||||
details = value
|
||||
__notify_changed()
|
||||
|
||||
var done: bool:
|
||||
set(value):
|
||||
done = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func _init(p_details: String = "", p_done: bool = false) -> void:
|
||||
details = p_details
|
||||
done = p_done
|
||||
super._init()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
return {
|
||||
"details": details,
|
||||
"done": done,
|
||||
}
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
if json.has("details"):
|
||||
details = json["details"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing details.")
|
||||
|
||||
if json.has("done"):
|
||||
done = json["done"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing 'done'.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://cvg6hllieqvxj
|
||||
@@ -0,0 +1,86 @@
|
||||
@tool
|
||||
extends "kanban_resource.gd"
|
||||
|
||||
## Data of a task.
|
||||
|
||||
|
||||
const __Step := preload("step.gd")
|
||||
|
||||
var title: String:
|
||||
set(value):
|
||||
title = value
|
||||
__notify_changed()
|
||||
|
||||
var description: String:
|
||||
set(value):
|
||||
description = value
|
||||
__notify_changed()
|
||||
|
||||
var category: String:
|
||||
set(value):
|
||||
category = value
|
||||
__notify_changed()
|
||||
|
||||
var steps: Array[__Step]:
|
||||
get:
|
||||
return steps.duplicate()
|
||||
set(value):
|
||||
steps = value
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func _init(p_title: String = "", p_description: String = "", p_category: String = "", p_steps: Array[__Step] = []) -> void:
|
||||
title = p_title
|
||||
description = p_description
|
||||
category = p_category
|
||||
steps = p_steps
|
||||
super._init()
|
||||
|
||||
|
||||
func add_step(step: __Step, silent: bool = false) -> void:
|
||||
var new_steps = steps
|
||||
new_steps.append(step)
|
||||
steps = new_steps
|
||||
step.changed.connect(__notify_changed)
|
||||
if not silent:
|
||||
__notify_changed()
|
||||
|
||||
|
||||
func to_json() -> Dictionary:
|
||||
var s: Array[Dictionary] = []
|
||||
for step in steps:
|
||||
s.append(step.to_json())
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"category": category,
|
||||
"steps": s,
|
||||
}
|
||||
|
||||
|
||||
func from_json(json: Dictionary) -> void:
|
||||
if json.has("title"):
|
||||
title = json["title"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a title.")
|
||||
|
||||
if json.has("description"):
|
||||
description = json["description"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a description.")
|
||||
|
||||
if json.has("category"):
|
||||
category = json["category"]
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing a category.")
|
||||
|
||||
if json.has("steps"):
|
||||
var s: Array[__Step] = []
|
||||
for step in json["steps"]:
|
||||
s.append(__Step.new())
|
||||
s[-1].from_json(step)
|
||||
s[-1].changed.connect(__notify_changed)
|
||||
steps = s
|
||||
else:
|
||||
push_warning("Loading incomplete json data which is missing steps.")
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1j5opcoegc6y
|
||||
@@ -0,0 +1,192 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
## A label with editable content.
|
||||
##
|
||||
## While not editing the text is displayed in a label. So it does not stick out
|
||||
## of some layout to much. Only while editing the label it is replaced with an
|
||||
## line edit.
|
||||
## Click onto the label to start editing it or start the editing mode via code
|
||||
## by using [member show_edit]. When editing press [kbd]Enter[/kbd] to finish
|
||||
## editing or press [kbd]Esc[/kbd] to discard your changes.
|
||||
|
||||
|
||||
## Emitted when the text changed.
|
||||
##
|
||||
## [b]Note:[/b] This is only emitted when you confirm your editing by pressing
|
||||
## [kbd]Enter[/kbd]. If you need access to all changes while editing use the
|
||||
## line edit directly. You can get it by calling [method get_edit].
|
||||
signal text_changed(new_text: String)
|
||||
|
||||
## The intentions with which the label can be edited.
|
||||
enum INTENTION {
|
||||
REPLACE, ## The text will be marked completly when editing.
|
||||
ADDITION, ## The cursor is placed at the end when editing.
|
||||
}
|
||||
|
||||
## The text to display and edit.
|
||||
@export var text: String = "":
|
||||
set(value):
|
||||
text = value
|
||||
__update_content()
|
||||
text_changed.emit(text)
|
||||
|
||||
## The default intention when editing the [member text].
|
||||
@export var default_intention := INTENTION.ADDITION
|
||||
|
||||
## Whether a double click is needed for editing. If [code]false[/code] a single
|
||||
## click is enough.
|
||||
@export var double_click: bool = true
|
||||
|
||||
# The line edit which is used to edit the text.
|
||||
var __edit: LineEdit
|
||||
|
||||
# The label which is used to display the text.
|
||||
var __label: Label
|
||||
|
||||
# The node which had focus before editing started. Used
|
||||
# to give focus back to it, when [kbd]Enter[/kbd] is used.
|
||||
var __old_focus: Control = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
# Setup the internal label.
|
||||
__label = Label.new()
|
||||
__label.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
__label.size_flags_vertical = SIZE_SHRINK_CENTER
|
||||
__label.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
__label.clip_text = true
|
||||
__label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
|
||||
__label.gui_input.connect(__on_label_gui_input)
|
||||
add_child(__label)
|
||||
|
||||
# Setup the internal line edit.
|
||||
__edit = LineEdit.new()
|
||||
__edit.visible = false
|
||||
__edit.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
__edit.size_flags_vertical = SIZE_FILL
|
||||
__edit.text_submitted.connect(__on_edit_text_submitted)
|
||||
__edit.gui_input.connect(__on_edit_gui_input)
|
||||
__edit.focus_exited.connect(__on_edit_focus_exited, CONNECT_DEFERRED)
|
||||
add_child(__edit)
|
||||
|
||||
__update_content()
|
||||
|
||||
# Wait for the label to get its true size.
|
||||
await get_tree().create_timer(0.0).timeout
|
||||
# Keep the same size when changing the edit mode.
|
||||
custom_minimum_size.y = max(__label.size.y, __edit.size.y) * 1.1
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
# End the editing when somewhere else was clicked.
|
||||
if (event is InputEventMouseButton) and event.pressed and __edit.visible:
|
||||
var local = __edit.make_input_local(event)
|
||||
if not Rect2(Vector2.ZERO, __edit.size).has_point(local.position):
|
||||
show_label()
|
||||
|
||||
|
||||
## Start editing the text and pass an optional intention.
|
||||
## This can be used to open the edit interface via code.
|
||||
func show_edit(intention: INTENTION = default_intention) -> void:
|
||||
if __edit.visible:
|
||||
return
|
||||
|
||||
# When this node can grab focus the focus should not be given back to the
|
||||
# old focus owner.
|
||||
__old_focus = get_viewport().gui_get_focus_owner() if focus_mode == FOCUS_NONE else null
|
||||
|
||||
__update_content()
|
||||
__label.visible = false
|
||||
__edit.visible = true
|
||||
|
||||
__edit.grab_focus()
|
||||
|
||||
match intention:
|
||||
INTENTION.ADDITION:
|
||||
__edit.caret_column = len(__edit.text)
|
||||
INTENTION.REPLACE:
|
||||
__edit.select_all()
|
||||
|
||||
|
||||
## Ends editing. If [code]apply_changes[/code] is [code]true[/code] the changed
|
||||
## text will be applied to the own [member text]. Otherwise the changes will
|
||||
## be discarded.
|
||||
func show_label(apply_changes: bool = true) -> void:
|
||||
if __label.visible:
|
||||
return
|
||||
|
||||
if apply_changes:
|
||||
text = __edit.text
|
||||
|
||||
if is_instance_valid(__old_focus):
|
||||
__old_focus.grab_focus()
|
||||
else:
|
||||
if focus_mode == FOCUS_NONE:
|
||||
__edit.release_focus()
|
||||
else:
|
||||
grab_focus()
|
||||
|
||||
__edit.visible = false
|
||||
__label.visible = true
|
||||
|
||||
|
||||
## Returns the [LineEdit] used to edit the text.
|
||||
##
|
||||
## [b]Warning:[/b] This is a required internal node, romoving and freeing it
|
||||
## may cause a crash. Feel free to edit its parameters to change, how the
|
||||
## [member text] is displayed.
|
||||
func get_edit() -> LineEdit:
|
||||
return __edit
|
||||
|
||||
|
||||
## Returns the [Label] used display the text.
|
||||
##
|
||||
## [b]Warning:[/b] This is a required internal node, romoving and freeing it
|
||||
## may cause a crash. Feel free to edit its parameters to change, how the
|
||||
## [member text] is displayed.
|
||||
func get_label() -> Label:
|
||||
return __label
|
||||
|
||||
|
||||
# Updates the diplayed text of [member __edit] and
|
||||
# [member __label] based on [member text].
|
||||
func __update_content() -> void:
|
||||
if __label:
|
||||
__label.text = text
|
||||
if __edit:
|
||||
__edit.text = text
|
||||
|
||||
|
||||
func __on_label_gui_input(event: InputEvent) -> void:
|
||||
# Edit when the label is clicked.
|
||||
if event is InputEventMouseButton:
|
||||
if event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if double_click == event.is_double_click():
|
||||
# Mark event as handled.
|
||||
__label.accept_event()
|
||||
show_edit()
|
||||
|
||||
|
||||
func __on_edit_gui_input(event: InputEvent) -> void:
|
||||
# Discard changes if ui_cancel action is pressed.
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
if event.is_action(&"ui_cancel"):
|
||||
show_label(false)
|
||||
|
||||
|
||||
func __on_edit_text_submitted(_new_text: String) -> void:
|
||||
# For some reason line edit does not accept the event on its own in GD4.
|
||||
__edit.accept_event()
|
||||
show_label()
|
||||
|
||||
|
||||
func __on_edit_focus_exited() -> void:
|
||||
if __edit.visible:
|
||||
__old_focus = null
|
||||
show_label()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cmnh7scftf1d
|
||||
@@ -0,0 +1,38 @@
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
|
||||
signal state_changed(expanded: bool)
|
||||
|
||||
@export var expanded: bool = true:
|
||||
set(value):
|
||||
if value != expanded:
|
||||
expanded = value
|
||||
__update_icon()
|
||||
state_changed.emit(expanded)
|
||||
|
||||
var __texture_rect := TextureRect.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
focus_mode = Control.FOCUS_NONE
|
||||
flat = true
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(center)
|
||||
center.add_child(__texture_rect)
|
||||
pressed.connect(__on_pressed)
|
||||
__update_icon()
|
||||
|
||||
|
||||
func _notification(what) -> void:
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
__texture_rect.texture = get_theme_icon(&"Collapse", &"EditorIcons")
|
||||
|
||||
|
||||
func __update_icon() -> void:
|
||||
__texture_rect.flip_v = expanded
|
||||
|
||||
|
||||
func __on_pressed() -> void:
|
||||
expanded = !expanded
|
||||
@@ -0,0 +1 @@
|
||||
uid://guc56bqr2khq
|
||||
@@ -0,0 +1,26 @@
|
||||
extends EditorExportPlugin
|
||||
|
||||
## Excludes Kanban Tasks from the exported project, since it's an editor-only
|
||||
## plugin.
|
||||
##
|
||||
## To bypass this for the standalone export configure "kanban_tasks_standalone"
|
||||
## as export feature.
|
||||
|
||||
func _get_name() -> String:
|
||||
# Plugins are processed in the order of their alphabetically sorted names.
|
||||
# We need this plugin to run before the GDScript export plugin runs.
|
||||
return "0_exclude_kanban_tasks"
|
||||
|
||||
var is_standalone_export: bool = false
|
||||
var base_path: String = ""
|
||||
|
||||
func _export_begin(features: PackedStringArray, is_debug: bool, path: String, flags: int) -> void:
|
||||
is_standalone_export = features.has("kanban_tasks_standalone")
|
||||
# Assumption: Located in base folder of plugin.
|
||||
base_path = (get_script() as Script).resource_path.get_base_dir()
|
||||
|
||||
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
|
||||
if is_standalone_export:
|
||||
return
|
||||
if path.begins_with(base_path):
|
||||
skip()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cf55kdqqoumwb
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><path d="m124.43 124.43c1.0492-1.0492.52463-3.6723-.52461-8.9185l-5.2462-26.231c-1.0492-5.2462-1.1213-5.449-4.1969-8.3938l-72.397-72.397c-4.6502-4.6502-12.138-4.6502-16.788-.000004l-16.788 16.788c-4.6502 4.6502-4.6502 12.138.000005 16.788l72.397 72.397c3.1477 3.1477 3.1477 3.1477 8.3939 4.1969l26.231 5.2462c5.2462 1.0492 7.8693 1.5738 8.9185.52461zm-36.199-27.805c-2.3179 2.3179-6.076 2.3179-8.3939-.000008l-33.576-33.575c-2.3179-2.3179-2.3179-6.076-.000007-8.3939 2.3179-2.3179 6.076-2.3179 8.3939.000009l33.576 33.575c2.3179 2.3179 2.3179 6.076-.000011 8.3939z" fill="#e0e0e0"/></svg>
|
||||
|
After Width: | Height: | Size: 831 B |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bc22jf62qsikb"
|
||||
path="res://.godot/imported/icon.svg-055e2d1e864a196c183290847b905009.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/kanban_tasks/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-055e2d1e864a196c183290847b905009.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=0.125
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Kanban Tasks - Todo Manager"
|
||||
description="Another kanban board plugin for the godot engine."
|
||||
author="HolonProduction"
|
||||
version="v2.2.0"
|
||||
script="plugin.gd"
|
||||
@@ -0,0 +1,450 @@
|
||||
@tool
|
||||
extends "standalone_plugin.gd"
|
||||
|
||||
|
||||
const __Singletons := preload("./plugin_singleton/singletons.gd")
|
||||
const __Shortcuts := preload("./view/shortcuts.gd")
|
||||
const __EditContext := preload("./view/edit_context.gd")
|
||||
const __Settings := preload("./data/settings.gd")
|
||||
const __BoardData := preload("./data/board.gd")
|
||||
const __LayoutData := preload("./data/layout.gd")
|
||||
const __TaskData := preload("./data/task.gd")
|
||||
const __CategoryData := preload("./data/category.gd")
|
||||
const __StageData := preload("./data/stage.gd")
|
||||
const __BoardView := preload("./view/board/board.tscn")
|
||||
const __BoardViewType := preload("./view/board/board.gd")
|
||||
const __StartView := preload("./view/start/start.tscn")
|
||||
const __StartViewType := preload("./view/start/start.gd")
|
||||
const __DocumentationView := preload("./view/documentation/documentation.tscn")
|
||||
|
||||
const SETTINGS_KEY: String = "kanban_tasks/general/settings"
|
||||
|
||||
enum {
|
||||
ACTION_SAVE,
|
||||
ACTION_SAVE_AS,
|
||||
ACTION_OPEN,
|
||||
ACTION_CREATE,
|
||||
ACTION_CLOSE,
|
||||
ACTION_DOCUMENTATION,
|
||||
ACTION_QUIT,
|
||||
}
|
||||
|
||||
var main_panel_frame: MarginContainer
|
||||
var start_view: __StartViewType
|
||||
var file_dialog_save: FileDialog
|
||||
var file_dialog_open: FileDialog
|
||||
var discard_changes_dialog: ConfirmationDialog
|
||||
var documentation_dialog: AcceptDialog
|
||||
|
||||
var file_menu: PopupMenu
|
||||
var help_menu: PopupMenu
|
||||
|
||||
var board_view: __BoardViewType
|
||||
var board_label: Label
|
||||
var board_path: String = "":
|
||||
set(value):
|
||||
board_path = value
|
||||
__update_board_label()
|
||||
__update_menus()
|
||||
var board_changed: bool = false:
|
||||
set(value):
|
||||
board_changed = value
|
||||
__update_board_label()
|
||||
|
||||
var export_plugin: Variant = null
|
||||
|
||||
func _enter_tree() -> void:
|
||||
board_label = Label.new()
|
||||
if not Engine.is_editor_hint():
|
||||
add_control_to_container(CONTAINER_TOOLBAR, board_label)
|
||||
|
||||
file_menu = PopupMenu.new()
|
||||
file_menu.name = "File"
|
||||
file_menu.add_item("Save board", ACTION_SAVE)
|
||||
file_menu.add_item("Save board as...", ACTION_SAVE_AS)
|
||||
file_menu.add_item("Close board", ACTION_CLOSE)
|
||||
file_menu.add_item("Open board...", ACTION_OPEN)
|
||||
file_menu.add_item("Create board", ACTION_CREATE)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_item("Quit", ACTION_QUIT)
|
||||
file_menu.id_pressed.connect(__action)
|
||||
add_menu(file_menu)
|
||||
|
||||
help_menu = PopupMenu.new()
|
||||
help_menu.name = "Help"
|
||||
help_menu.add_item("Documentation", ACTION_DOCUMENTATION)
|
||||
help_menu.id_pressed.connect(__action)
|
||||
add_menu(help_menu)
|
||||
|
||||
file_dialog_save = FileDialog.new()
|
||||
file_dialog_save.access = FileDialog.ACCESS_FILESYSTEM
|
||||
file_dialog_save.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||
file_dialog_save.add_filter("*.kanban, *.json", "Kanban Board")
|
||||
file_dialog_save.min_size = Vector2(800, 500)
|
||||
file_dialog_save.file_selected.connect(__save_board)
|
||||
get_editor_interface().get_base_control().add_child(file_dialog_save)
|
||||
|
||||
file_dialog_open = FileDialog.new()
|
||||
file_dialog_open.access = FileDialog.ACCESS_FILESYSTEM
|
||||
file_dialog_open.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog_open.add_filter("*.kanban, *.json", "Kanban Board")
|
||||
file_dialog_open.min_size = Vector2(800, 500)
|
||||
file_dialog_open.file_selected.connect(__open_board)
|
||||
get_editor_interface().get_base_control().add_child(file_dialog_open)
|
||||
|
||||
discard_changes_dialog = ConfirmationDialog.new()
|
||||
discard_changes_dialog.dialog_text = "All unsaved changes will be discarded."
|
||||
discard_changes_dialog.unresizable = true
|
||||
get_editor_interface().get_base_control().add_child(discard_changes_dialog)
|
||||
|
||||
documentation_dialog = __DocumentationView.instantiate()
|
||||
get_editor_interface().get_base_control().add_child(documentation_dialog)
|
||||
|
||||
main_panel_frame = MarginContainer.new()
|
||||
main_panel_frame.add_theme_constant_override(&"margin_top", 5)
|
||||
main_panel_frame.add_theme_constant_override(&"margin_left", 5)
|
||||
main_panel_frame.add_theme_constant_override(&"margin_bottom", 5)
|
||||
main_panel_frame.add_theme_constant_override(&"margin_right", 5)
|
||||
main_panel_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
get_editor_interface().get_editor_main_screen().add_child(main_panel_frame)
|
||||
|
||||
start_view = __StartView.instantiate()
|
||||
start_view.create_board.connect(__action.bind(ACTION_CREATE))
|
||||
start_view.open_board.connect(__on_start_view_open_board)
|
||||
main_panel_frame.add_child(start_view)
|
||||
|
||||
_make_visible(false)
|
||||
|
||||
await get_tree().create_timer(0.0).timeout
|
||||
|
||||
__load_settings()
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
var editor_data_file_path = ctx.settings.editor_data_file_path
|
||||
if FileAccess.file_exists(editor_data_file_path):
|
||||
__open_board(editor_data_file_path)
|
||||
elif FileAccess.file_exists("res://addons/kanban_tasks/data.json"):
|
||||
# TODO: Remove sometime in the future.
|
||||
# Migrate from old version.
|
||||
__open_board("res://addons/kanban_tasks/data.json")
|
||||
__save_board(editor_data_file_path)
|
||||
else:
|
||||
__create_board()
|
||||
__save_board(editor_data_file_path)
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
export_plugin = load((get_script() as Script).resource_path.get_base_dir().path_join("export_plugin.gd")).new()
|
||||
add_export_plugin(export_plugin)
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.save_board.connect(__editor_save_board)
|
||||
ctx.reload_board.connect(__editor_reload_board)
|
||||
ctx.create_board.connect(__editor_create_board)
|
||||
|
||||
__update_menus()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(export_plugin):
|
||||
remove_export_plugin(export_plugin)
|
||||
|
||||
if not Engine.is_editor_hint():
|
||||
remove_control_from_container(CONTAINER_TOOLBAR, board_label)
|
||||
board_label.queue_free()
|
||||
|
||||
remove_menu(file_menu)
|
||||
file_menu.queue_free()
|
||||
|
||||
remove_menu(help_menu)
|
||||
file_menu.queue_free()
|
||||
|
||||
file_dialog_save.queue_free()
|
||||
file_dialog_open.queue_free()
|
||||
discard_changes_dialog.queue_free()
|
||||
documentation_dialog.queue_free()
|
||||
|
||||
main_panel_frame.queue_free()
|
||||
start_view.queue_free()
|
||||
|
||||
if is_instance_valid(board_view):
|
||||
board_view.queue_free()
|
||||
|
||||
|
||||
func _shortcut_input(event: InputEvent) -> void:
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
if not Engine.is_editor_hint() and shortcuts.save.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
__action(ACTION_SAVE)
|
||||
|
||||
if not Engine.is_editor_hint() and shortcuts.save_as.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
__action(ACTION_SAVE_AS)
|
||||
|
||||
|
||||
func _has_main_screen() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func _make_visible(visible) -> void:
|
||||
if main_panel_frame:
|
||||
main_panel_frame.visible = visible
|
||||
|
||||
|
||||
func _get_plugin_name() -> String:
|
||||
return "Tasks"
|
||||
|
||||
|
||||
func _get_plugin_icon() -> Texture2D:
|
||||
return preload("./icon.svg")
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
match what:
|
||||
NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
if not Engine.is_editor_hint():
|
||||
if not board_changed:
|
||||
get_tree().quit()
|
||||
else:
|
||||
__request_discard_changes(get_tree().quit)
|
||||
|
||||
|
||||
func __update_menus() -> void:
|
||||
if not is_instance_valid(file_menu):
|
||||
return
|
||||
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
|
||||
file_menu.set_item_disabled(
|
||||
file_menu.get_item_index(ACTION_SAVE),
|
||||
not is_instance_valid(board_view),
|
||||
)
|
||||
file_menu.set_item_shortcut(
|
||||
file_menu.get_item_index(ACTION_SAVE),
|
||||
shortcuts.save,
|
||||
)
|
||||
file_menu.set_item_disabled(
|
||||
file_menu.get_item_index(ACTION_SAVE_AS),
|
||||
not is_instance_valid(board_view),
|
||||
)
|
||||
file_menu.set_item_shortcut(
|
||||
file_menu.get_item_index(ACTION_SAVE_AS),
|
||||
shortcuts.save_as,
|
||||
)
|
||||
file_menu.set_item_disabled(
|
||||
file_menu.get_item_index(ACTION_CLOSE),
|
||||
not is_instance_valid(board_view),
|
||||
)
|
||||
|
||||
|
||||
func __update_board_label() -> void:
|
||||
if not is_instance_valid(board_label):
|
||||
return
|
||||
if is_instance_valid(board_view):
|
||||
if board_path.is_empty():
|
||||
board_label.text = "unsaved"
|
||||
else:
|
||||
board_label.text = board_path
|
||||
if board_changed:
|
||||
board_label.text += "*"
|
||||
else:
|
||||
board_label.text = ""
|
||||
|
||||
|
||||
func __action(id: int) -> void:
|
||||
match id:
|
||||
ACTION_SAVE:
|
||||
__request_save()
|
||||
ACTION_SAVE_AS:
|
||||
__request_save(true)
|
||||
ACTION_CREATE:
|
||||
if not board_changed:
|
||||
__create_board()
|
||||
else:
|
||||
__request_discard_changes(__create_board)
|
||||
ACTION_OPEN:
|
||||
if not board_changed:
|
||||
file_dialog_open.popup_centered()
|
||||
else:
|
||||
__request_discard_changes(file_dialog_open.popup_centered)
|
||||
ACTION_CLOSE:
|
||||
if not board_changed:
|
||||
__close_board()
|
||||
else:
|
||||
__request_discard_changes(__close_board)
|
||||
ACTION_DOCUMENTATION:
|
||||
documentation_dialog.popup_centered()
|
||||
ACTION_QUIT:
|
||||
get_tree().get_root().propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
||||
|
||||
|
||||
func __editor_save_board() -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
__save_board(ctx.settings.editor_data_file_path)
|
||||
|
||||
|
||||
func __editor_reload_board(discard_changes: bool) -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
if not discard_changes:
|
||||
__action(ACTION_SAVE)
|
||||
__open_board(ctx.settings.editor_data_file_path)
|
||||
|
||||
|
||||
func __editor_create_board() -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
__action(ACTION_SAVE)
|
||||
__create_board()
|
||||
__save_board(ctx.settings.editor_data_file_path)
|
||||
|
||||
|
||||
func __request_discard_changes(callback: Callable) -> void:
|
||||
for connection in discard_changes_dialog.confirmed.get_connections():
|
||||
discard_changes_dialog.confirmed.disconnect(connection["callable"])
|
||||
discard_changes_dialog.confirmed.connect(callback)
|
||||
discard_changes_dialog.popup_centered()
|
||||
|
||||
|
||||
func __request_save(force_new_location: bool = false) -> void:
|
||||
if not is_instance_valid(board_view):
|
||||
return
|
||||
if not force_new_location and not board_path.is_empty():
|
||||
__save_board(board_path)
|
||||
else:
|
||||
file_dialog_save.popup_centered()
|
||||
|
||||
|
||||
func __create_board() -> void:
|
||||
var data := __BoardData.new()
|
||||
|
||||
data.layout = __LayoutData.new([
|
||||
PackedStringArray([data.add_stage(__StageData.new("Todo"))]),
|
||||
PackedStringArray([data.add_stage(__StageData.new("Doing"))]),
|
||||
PackedStringArray([data.add_stage(__StageData.new("Done"))]),
|
||||
])
|
||||
data.add_category(
|
||||
__CategoryData.new(
|
||||
"Task",
|
||||
get_editor_interface().get_base_control().
|
||||
get_theme_color(&"accent_color", &"Editor")
|
||||
)
|
||||
)
|
||||
|
||||
data.changed.connect(__on_board_changed)
|
||||
|
||||
__make_board_view_visible(data)
|
||||
|
||||
board_path = ""
|
||||
board_changed = false
|
||||
|
||||
|
||||
func __save_board(path: String) -> void:
|
||||
if is_instance_valid(board_view):
|
||||
__add_to_recent_files(path)
|
||||
board_path = path
|
||||
board_view.board_data.save(path)
|
||||
board_changed = false
|
||||
|
||||
|
||||
func __open_board(path: String) -> void:
|
||||
var data := __BoardData.new()
|
||||
data.load(path)
|
||||
data.changed.connect(__on_board_changed)
|
||||
|
||||
__make_board_view_visible(data)
|
||||
__add_to_recent_files(path)
|
||||
|
||||
board_path = path
|
||||
board_changed = false
|
||||
|
||||
|
||||
func __close_board() -> void:
|
||||
board_view.queue_free()
|
||||
board_view = null
|
||||
board_path = ""
|
||||
board_changed = false
|
||||
start_view.show()
|
||||
|
||||
|
||||
func __add_to_recent_files(path: String) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
var files = ctx.settings.recent_files
|
||||
|
||||
if path in files:
|
||||
files.remove_at(files.find(path))
|
||||
files.insert(0, path)
|
||||
else:
|
||||
files.insert(0, path)
|
||||
files.resize(ctx.settings.recent_file_count)
|
||||
|
||||
ctx.settings.recent_files = files
|
||||
|
||||
|
||||
func __on_board_changed() -> void:
|
||||
board_changed = true
|
||||
if Engine.is_editor_hint():
|
||||
__request_save()
|
||||
|
||||
|
||||
func __on_start_view_open_board(path: String) -> void:
|
||||
if path.is_empty():
|
||||
__action(ACTION_OPEN)
|
||||
else:
|
||||
__open_board(path)
|
||||
|
||||
|
||||
func __make_board_view_visible(data: __BoardData) -> void:
|
||||
if is_instance_valid(board_view):
|
||||
board_view.queue_free()
|
||||
board_view = __BoardView.instantiate()
|
||||
board_view.show_documentation.connect(__action.bind(ACTION_DOCUMENTATION))
|
||||
board_view.board_data = data
|
||||
|
||||
main_panel_frame.add_child(board_view)
|
||||
start_view.hide()
|
||||
|
||||
|
||||
func __save_settings() -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
var data := JSON.stringify(ctx.settings.to_json())
|
||||
if Engine.is_editor_hint():
|
||||
get_editor_interface().get_editor_settings().set_setting(
|
||||
SETTINGS_KEY,
|
||||
data,
|
||||
)
|
||||
else:
|
||||
ProjectSettings.set_setting(
|
||||
SETTINGS_KEY,
|
||||
data,
|
||||
)
|
||||
save_project_settings()
|
||||
|
||||
|
||||
func __load_settings() -> void:
|
||||
var data: String = "{}"
|
||||
if Engine.is_editor_hint():
|
||||
var editor_settings = get_editor_interface().get_editor_settings()
|
||||
if editor_settings.has_setting(SETTINGS_KEY):
|
||||
data = editor_settings.get_setting(SETTINGS_KEY)
|
||||
else:
|
||||
if ProjectSettings.has_setting(SETTINGS_KEY):
|
||||
data = ProjectSettings.get_setting(SETTINGS_KEY)
|
||||
|
||||
var json = JSON.new()
|
||||
var err = json.parse(data)
|
||||
if err != OK:
|
||||
push_error(
|
||||
"Error "
|
||||
+ str(err)
|
||||
+ " while parsing settings. At line "
|
||||
+ str(json.get_error_line())
|
||||
+ " the following problem occured:\n"
|
||||
+ json.get_error_message()
|
||||
)
|
||||
return
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.from_json(json.data)
|
||||
ctx.settings.changed.connect(__save_settings)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bxywkes3ex12p
|
||||
@@ -0,0 +1,23 @@
|
||||
extends Object
|
||||
|
||||
## Allows the registration of anonymous singletons into the scene tree.
|
||||
|
||||
|
||||
const HOLDER_NAME: String = "PluginSingletons"
|
||||
|
||||
|
||||
static func instance_of(p_script: Script, requester: Node) -> Variant:
|
||||
var holder: Node = requester.get_tree().get_root().get_node_or_null(HOLDER_NAME)
|
||||
|
||||
if not is_instance_valid(holder):
|
||||
holder = Node.new()
|
||||
holder.name = HOLDER_NAME
|
||||
requester.get_tree().get_root().add_child(holder)
|
||||
|
||||
for child in holder.get_children():
|
||||
if child.get_script() == p_script:
|
||||
return child
|
||||
|
||||
var instance: Node = p_script.new()
|
||||
holder.add_child(instance)
|
||||
return instance
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6nperxjqdq6m
|
||||
@@ -0,0 +1,37 @@
|
||||
#@standalone
|
||||
# The line above is needed for standalone detection. Do not modify it.
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
## The StandalonePlugin implementation for in editor use.
|
||||
##
|
||||
## This file violates DRY. It should not contain references to other files
|
||||
## of standalone plugin. This allows the independent use as editor plugin.
|
||||
|
||||
|
||||
## Additional containers for add_control_to_container
|
||||
# CONTAINER_TOOLBAR = 0,
|
||||
# CONTAINER_SPATIAL_EDITOR_MENU = 1,
|
||||
# CONTAINER_SPATIAL_EDITOR_SIDE_LEFT = 2,
|
||||
# CONTAINER_SPATIAL_EDITOR_SIDE_RIGHT = 3,
|
||||
# CONTAINER_SPATIAL_EDITOR_BOTTOM = 4,
|
||||
# CONTAINER_CANVAS_EDITOR_MENU = 5,
|
||||
# CONTAINER_CANVAS_EDITOR_SIDE_LEFT = 6,
|
||||
# CONTAINER_CANVAS_EDITOR_SIDE_RIGHT = 7,
|
||||
# CONTAINER_CANVAS_EDITOR_BOTTOM = 8,
|
||||
# CONTAINER_INSPECTOR_BOTTOM = 9,
|
||||
# CONTAINER_PROJECT_SETTING_TAB_LEFT = 10,
|
||||
# CONTAINER_PROJECT_SETTING_TAB_RIGHT = 11,
|
||||
const CONTAINER_LAUNCH_PAD := 12
|
||||
|
||||
|
||||
func save_project_settings() -> int:
|
||||
return ProjectSettings.save()
|
||||
|
||||
|
||||
func add_menu(menu: PopupMenu) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func remove_menu(menu: PopupMenu) -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3jwfydpmke0f
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Xavier Sellier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Note: The code might not be as pretty it could be, since it's written
|
||||
# in a way that maximizes performance. Methods are inlined and loops are avoided.
|
||||
extends Node
|
||||
|
||||
const MODULO_8_BIT = 256
|
||||
|
||||
static func getRandomInt():
|
||||
# Randomize every time to minimize the risk of collisions
|
||||
randomize()
|
||||
|
||||
return randi() % MODULO_8_BIT
|
||||
|
||||
static func uuidbin():
|
||||
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||
return [
|
||||
getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(),
|
||||
getRandomInt(), getRandomInt(), ((getRandomInt()) & 0x0f) | 0x40, getRandomInt(),
|
||||
((getRandomInt()) & 0x3f) | 0x80, getRandomInt(), getRandomInt(), getRandomInt(),
|
||||
getRandomInt(), getRandomInt(), getRandomInt(), getRandomInt(),
|
||||
]
|
||||
|
||||
static func v4():
|
||||
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||
var b = uuidbin()
|
||||
|
||||
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
|
||||
# low
|
||||
b[0], b[1], b[2], b[3],
|
||||
|
||||
# mid
|
||||
b[4], b[5],
|
||||
|
||||
# hi
|
||||
b[6], b[7],
|
||||
|
||||
# clock
|
||||
b[8], b[9],
|
||||
|
||||
# clock
|
||||
b[10], b[11], b[12], b[13], b[14], b[15]
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
uid://dsdb27xoyjldv
|
||||
@@ -0,0 +1,157 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
## The visual representation of a kanban board.
|
||||
|
||||
|
||||
const __Singletons := preload("../../plugin_singleton/singletons.gd")
|
||||
const __Shortcuts := preload("../shortcuts.gd")
|
||||
const __EditContext := preload("../edit_context.gd")
|
||||
const __BoardData := preload("../../data/board.gd")
|
||||
const __StageScript := preload("../stage/stage.gd")
|
||||
const __StageScene := preload("../stage/stage.tscn")
|
||||
const __Filter := preload("../filter.gd")
|
||||
const __SettingsScript := preload("../settings/settings.gd")
|
||||
|
||||
signal show_documentation()
|
||||
|
||||
var board_data: __BoardData
|
||||
|
||||
@onready var search_bar: LineEdit = %SearchBar
|
||||
@onready var button_advanced_search: Button = %AdvancedSearch
|
||||
@onready var button_show_categories: Button = %ShowCategories
|
||||
@onready var button_show_descriptions: Button = %ShowDescriptions
|
||||
@onready var button_show_steps: Button = %ShowSteps
|
||||
@onready var button_documentation: Button = %Documentation
|
||||
@onready var button_settings: Button = %Settings
|
||||
@onready var column_holder: HBoxContainer = %ColumnHolder
|
||||
@onready var settings: __SettingsScript = %SettingsView
|
||||
|
||||
|
||||
func _ready():
|
||||
update()
|
||||
board_data.layout.changed.connect(update)
|
||||
|
||||
settings.board_data = board_data
|
||||
|
||||
search_bar.text_changed.connect(__on_filter_changed)
|
||||
search_bar.text_submitted.connect(__on_search_bar_entered)
|
||||
button_advanced_search.toggled.connect(__on_filter_changed)
|
||||
|
||||
button_show_categories.toggled.connect(__on_show_categories_toggled)
|
||||
button_show_descriptions.toggled.connect(__on_show_descriptions_toggled)
|
||||
button_show_steps.toggled.connect(__on_show_steps_toggled)
|
||||
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
|
||||
await get_tree().create_timer(0.0).timeout
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
|
||||
ctx.settings.changed.connect(update)
|
||||
|
||||
ctx.filter_changed.connect(__on_filter_changed_external)
|
||||
|
||||
button_documentation.pressed.connect(func(): show_documentation.emit())
|
||||
button_documentation.visible = Engine.is_editor_hint()
|
||||
|
||||
button_settings.pressed.connect(settings.popup_centered_ratio_no_fullscreen)
|
||||
|
||||
|
||||
func _shortcut_input(event: InputEvent) -> void:
|
||||
if not __Shortcuts.should_handle_shortcut(self):
|
||||
return
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
if not event.is_echo() and event.is_pressed():
|
||||
if shortcuts.search.matches_event(event):
|
||||
search_bar.grab_focus()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif shortcuts.undo.matches_event(event):
|
||||
ctx.undo_redo.undo()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif shortcuts.redo.matches_event(event):
|
||||
ctx.undo_redo.redo()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
if is_instance_valid(search_bar):
|
||||
search_bar.right_icon = get_theme_icon(&"Search", &"EditorIcons")
|
||||
if is_instance_valid(button_settings):
|
||||
button_settings.icon = get_theme_icon(&"Tools", &"EditorIcons")
|
||||
if is_instance_valid(button_documentation):
|
||||
button_documentation.icon = get_theme_icon(&"Help", &"EditorIcons")
|
||||
if is_instance_valid(button_advanced_search):
|
||||
button_advanced_search.icon = get_theme_icon(&"Zoom", &"EditorIcons")
|
||||
if is_instance_valid(button_show_categories):
|
||||
button_show_categories.icon = get_theme_icon(&"Rectangle", &"EditorIcons")
|
||||
if is_instance_valid(button_show_descriptions):
|
||||
button_show_descriptions.icon = get_theme_icon(&"Script", &"EditorIcons")
|
||||
if is_instance_valid(button_show_steps):
|
||||
button_show_steps.icon = get_theme_icon(&"FileList", &"EditorIcons")
|
||||
if is_instance_valid(settings):
|
||||
settings.on_theme_changed()
|
||||
|
||||
|
||||
func update() -> void:
|
||||
for column in column_holder.get_children():
|
||||
column.queue_free()
|
||||
|
||||
for column_data in board_data.layout.columns:
|
||||
var column_scroll = ScrollContainer.new()
|
||||
column_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
column_scroll.set_v_size_flags(Control.SIZE_EXPAND_FILL)
|
||||
column_scroll.set_h_size_flags(Control.SIZE_EXPAND_FILL)
|
||||
var column = VBoxContainer.new()
|
||||
column.set_v_size_flags(Control.SIZE_EXPAND_FILL)
|
||||
column.set_h_size_flags(Control.SIZE_EXPAND_FILL)
|
||||
|
||||
column_scroll.add_child(column)
|
||||
column_holder.add_child(column_scroll)
|
||||
|
||||
for uuid in column_data:
|
||||
var stage := __StageScene.instantiate()
|
||||
stage.board_data = board_data
|
||||
stage.data_uuid = uuid
|
||||
column.add_child(stage)
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
button_show_categories.set_pressed_no_signal(ctx.settings.show_category_on_board)
|
||||
button_show_descriptions.set_pressed_no_signal(ctx.settings.show_description_preview)
|
||||
button_show_steps.set_pressed_no_signal(ctx.settings.show_steps_preview)
|
||||
|
||||
|
||||
# Do not use parameters the method is bound to diffrent signals.
|
||||
func __on_filter_changed(param1: Variant = null):
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
|
||||
if ctx.filter_changed.is_connected(__on_filter_changed_external):
|
||||
ctx.filter_changed.disconnect(__on_filter_changed_external)
|
||||
|
||||
ctx.filter = __Filter.new(search_bar.text, button_advanced_search.button_pressed)
|
||||
|
||||
ctx.filter_changed.connect(__on_filter_changed_external)
|
||||
|
||||
|
||||
func __on_search_bar_entered(filter: String):
|
||||
button_advanced_search.grab_focus()
|
||||
|
||||
|
||||
func __on_filter_changed_external():
|
||||
search_bar.text = ""
|
||||
|
||||
|
||||
func __on_show_categories_toggled(button_pressed: bool):
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.show_category_on_board = button_pressed
|
||||
|
||||
|
||||
func __on_show_descriptions_toggled(button_pressed: bool):
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.show_description_preview = button_pressed
|
||||
|
||||
|
||||
func __on_show_steps_toggled(button_pressed: bool):
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.show_steps_preview = button_pressed
|
||||
@@ -0,0 +1 @@
|
||||
uid://5hks8mhjyp62
|
||||
@@ -0,0 +1,89 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://c5dk4lnyiag3w"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5hks8mhjyp62" path="res://addons/kanban_tasks/view/board/board.gd" id="1_p7lf4"]
|
||||
[ext_resource type="PackedScene" uid="uid://dh1yunmhipirg" path="res://addons/kanban_tasks/view/settings/settings.tscn" id="2_by8mq"]
|
||||
|
||||
[node name="BoardView" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
mouse_filter = 0
|
||||
theme_override_constants/separation = 5
|
||||
script = ExtResource("1_p7lf4")
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="SearchBar" type="LineEdit" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Search"
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="AdvancedSearch" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Search in details."
|
||||
toggle_mode = true
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="Header"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Show categories"
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="ShowCategories" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
|
||||
[node name="ShowDescriptions" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Show descriptions."
|
||||
toggle_mode = true
|
||||
|
||||
[node name="ShowSteps" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Show steps."
|
||||
toggle_mode = true
|
||||
|
||||
[node name="VSeparator2" type="VSeparator" parent="Header"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="Documentation" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Open documentation."
|
||||
flat = true
|
||||
|
||||
[node name="Settings" type="Button" parent="Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Manage board settings."
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
mouse_filter = 0
|
||||
vertical_scroll_mode = 0
|
||||
|
||||
[node name="ColumnHolder" type="HBoxContainer" parent="ScrollContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 5
|
||||
alignment = 1
|
||||
|
||||
[node name="SettingsView" parent="." instance=ExtResource("2_by8mq")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
@@ -0,0 +1,44 @@
|
||||
@tool
|
||||
extends PopupMenu
|
||||
|
||||
|
||||
const __BoardData := preload("../../data/board.gd")
|
||||
|
||||
var board_data: __BoardData
|
||||
|
||||
signal uuid_selected(uuid)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
about_to_popup.connect(__update_items_from_board)
|
||||
id_pressed.connect(__on_id_pressed)
|
||||
|
||||
|
||||
func popup_at_local_position(source: CanvasItem, local_position: Vector2) -> void:
|
||||
popup_at_global_position(source, source.get_global_transform() * local_position)
|
||||
|
||||
|
||||
func popup_at_global_position(source: CanvasItem, global_position: Vector2) -> void:
|
||||
position = global_position
|
||||
if not source.get_window().gui_embed_subwindows:
|
||||
position += source.get_window().position
|
||||
popup()
|
||||
|
||||
|
||||
func popup_at_mouse_position(source: CanvasItem) -> void:
|
||||
popup_at_global_position(source, source.get_global_mouse_position())
|
||||
|
||||
|
||||
func __update_items_from_board() -> void:
|
||||
clear()
|
||||
size = Vector2i.ZERO
|
||||
for uuid in board_data.get_categories():
|
||||
var i = Image.create(16, 16, false, Image.FORMAT_RGB8)
|
||||
i.fill(board_data.get_category(uuid).color)
|
||||
var t = ImageTexture.create_from_image(i)
|
||||
add_icon_item(t, board_data.get_category(uuid).title)
|
||||
set_item_metadata(-1, uuid)
|
||||
|
||||
|
||||
func __on_id_pressed(id) -> void:
|
||||
uuid_selected.emit(get_item_metadata(id))
|
||||
@@ -0,0 +1 @@
|
||||
uid://pv2ghsb6w6fn
|
||||
@@ -0,0 +1,225 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
|
||||
const __BoardData := preload("../../data/board.gd")
|
||||
const __StepData := preload("../../data/step.gd")
|
||||
const __StepEntry := preload("../details/step_entry.gd")
|
||||
const __Singletons := preload("../../plugin_singleton/singletons.gd")
|
||||
const __EditContext := preload("../edit_context.gd")
|
||||
|
||||
var board_data: __BoardData
|
||||
var data_uuid: String
|
||||
|
||||
var __step_data: __StepData
|
||||
|
||||
@onready var category_select: OptionButton = %Category
|
||||
@onready var h_split_container: HSplitContainer = %HSplitContainer
|
||||
@onready var description_edit: TextEdit = %Description
|
||||
@onready var step_holder: VBoxContainer = %StepHolder
|
||||
@onready var steps_panel_container: PanelContainer = %PanelContainer
|
||||
@onready var create_step_edit: LineEdit = %CreateStepEdit
|
||||
@onready var step_details: VBoxContainer = %StepDetails
|
||||
@onready var close_step_details_button: Button = %CloseStepDetails
|
||||
@onready var step_edit: TextEdit = %StepEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
about_to_popup.connect(__on_about_to_popup)
|
||||
create_step_edit.text_submitted.connect(__create_step)
|
||||
close_step_details_button.pressed.connect(__close_step_details)
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
step_holder.entry_action_triggered.connect(__on_step_action_triggered)
|
||||
step_holder.entry_move_requesed.connect(__step_move_requesed)
|
||||
|
||||
visibility_changed.connect(__save_internal_state)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
if is_instance_valid(steps_panel_container):
|
||||
steps_panel_container.add_theme_stylebox_override(&"panel", get_theme_stylebox(&"panel", &"Panel"))
|
||||
if is_instance_valid(create_step_edit):
|
||||
create_step_edit.right_icon = get_theme_icon(&"Add", &"EditorIcons")
|
||||
if is_instance_valid(close_step_details_button):
|
||||
close_step_details_button.icon = get_theme_icon(&"Close", &"EditorIcons")
|
||||
|
||||
|
||||
func update() -> void:
|
||||
if description_edit.text_changed.is_connected(__on_description_changed):
|
||||
description_edit.text_changed.disconnect(__on_description_changed)
|
||||
if description_edit.text != board_data.get_task(data_uuid).description:
|
||||
description_edit.text = board_data.get_task(data_uuid).description
|
||||
description_edit.text_changed.connect(__on_description_changed)
|
||||
|
||||
title = "Task Details: " + board_data.get_task(data_uuid).title
|
||||
|
||||
if category_select.item_selected.is_connected(__on_category_selected):
|
||||
category_select.item_selected.disconnect(__on_category_selected)
|
||||
category_select.clear()
|
||||
for uuid in board_data.get_categories():
|
||||
var i = Image.create(16, 16, false, Image.FORMAT_RGB8)
|
||||
i.fill(board_data.get_category(uuid).color)
|
||||
var t = ImageTexture.create_from_image(i)
|
||||
category_select.add_icon_item(t, board_data.get_category(uuid).title)
|
||||
category_select.set_item_metadata(-1, uuid)
|
||||
if uuid == board_data.get_task(data_uuid).category:
|
||||
category_select.select(category_select.item_count - 1)
|
||||
|
||||
category_select.item_selected.connect(__on_category_selected)
|
||||
|
||||
step_holder.clear_steps()
|
||||
for step in board_data.get_task(data_uuid).steps:
|
||||
step_holder.add_step(step)
|
||||
for entry in step_holder.get_step_entries():
|
||||
entry.being_edited = (entry.step_data == __step_data)
|
||||
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
|
||||
step_details.visible = is_instance_valid(__step_data)
|
||||
description_edit.visible = not (ctx.settings.edit_step_details_exclusively and is_instance_valid(__step_data))
|
||||
if is_instance_valid(__step_data):
|
||||
if step_edit.text_changed.is_connected(__on_step_details_changed):
|
||||
step_edit.text_changed.disconnect(__on_step_details_changed)
|
||||
step_edit.text = __step_data.details
|
||||
step_edit.text_changed.connect(__on_step_details_changed)
|
||||
|
||||
|
||||
# Workaround for godotengine/godot#70451
|
||||
func popup_centered_ratio_no_fullscreen(ratio: float = 0.8) -> void:
|
||||
var viewport: Viewport = get_parent().get_viewport()
|
||||
popup(Rect2i(Vector2(viewport.position) + viewport.size / 2.0 - viewport.size * ratio / 2.0, viewport.size * ratio))
|
||||
|
||||
|
||||
func edit_step_details(step: __StepData) -> void:
|
||||
if is_instance_valid(__step_data):
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data = step
|
||||
__step_data.changed.connect(update)
|
||||
update()
|
||||
step_edit.set_caret_line(step_edit.get_line_count())
|
||||
step_edit.set_caret_column(len(step_edit.get_line(step_edit.get_line_count() - 1)))
|
||||
step_edit.grab_focus.call_deferred()
|
||||
|
||||
|
||||
func move_step_up(step: __StepData) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps and steps[0] != step:
|
||||
var index = steps.find(step)
|
||||
steps.erase(step)
|
||||
steps.insert(index - 1, step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func move_step_down(step: __StepData) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps and steps[-1] != step:
|
||||
var index = steps.find(step)
|
||||
steps.erase(step)
|
||||
steps.insert(index + 1, step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func delete_step(step: __StepData) -> void:
|
||||
close_step_details(step)
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
if step in steps:
|
||||
steps.erase(step)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func close_step_details(step: __StepData) -> void:
|
||||
if __step_data == step:
|
||||
__close_step_details()
|
||||
|
||||
|
||||
func __on_step_action_triggered(entry: __StepEntry, action: __StepEntry.Actions) -> void:
|
||||
match action:
|
||||
__StepEntry.Actions.EDIT_HARD:
|
||||
edit_step_details(entry.step_data)
|
||||
__StepEntry.Actions.EDIT_SOFT:
|
||||
if is_instance_valid(__step_data):
|
||||
edit_step_details(entry.step_data)
|
||||
__StepEntry.Actions.CLOSE:
|
||||
close_step_details(entry.step_data)
|
||||
__StepEntry.Actions.DELETE:
|
||||
delete_step(entry.step_data)
|
||||
__StepEntry.Actions.MOVE_UP:
|
||||
move_step_up(entry.step_data)
|
||||
__StepEntry.Actions.MOVE_DOWN:
|
||||
move_step_down(entry.step_data)
|
||||
|
||||
|
||||
func __step_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool) -> void:
|
||||
var steps = board_data.get_task(data_uuid).steps
|
||||
var moved_idx = steps.find(moved_entry.step_data)
|
||||
var target_idx = steps.find(target_entry.step_data)
|
||||
if moved_idx < 0 or target_idx < 0 or moved_idx == target_idx:
|
||||
return
|
||||
steps.erase(moved_entry.step_data)
|
||||
if moved_idx < target_idx:
|
||||
target_idx -= 1
|
||||
if move_after_target:
|
||||
steps.insert(target_idx + 1, moved_entry.step_data)
|
||||
else:
|
||||
steps.insert(target_idx, moved_entry.step_data)
|
||||
board_data.get_task(data_uuid).steps = steps
|
||||
update()
|
||||
|
||||
|
||||
func __load_internal_state() -> void:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
if ctx.settings.internal_states.has("details_editor_step_holder_width"):
|
||||
h_split_container.split_offset = ctx.settings.internal_states["details_editor_step_holder_width"]
|
||||
|
||||
|
||||
func __save_internal_state() -> void:
|
||||
if not visible:
|
||||
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
|
||||
ctx.settings.set_internal_state("details_editor_step_holder_width", h_split_container.split_offset)
|
||||
|
||||
|
||||
func __close_step_details() -> void:
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data = null
|
||||
update()
|
||||
|
||||
|
||||
func __on_step_details_changed() -> void:
|
||||
if __step_data.changed.is_connected(update):
|
||||
__step_data.changed.disconnect(update)
|
||||
__step_data.details = step_edit.text
|
||||
__step_data.changed.connect(update)
|
||||
|
||||
|
||||
func __on_about_to_popup() -> void:
|
||||
if is_instance_valid(__step_data):
|
||||
__close_step_details()
|
||||
update()
|
||||
__load_internal_state()
|
||||
if board_data.get_task(data_uuid).description.is_empty():
|
||||
description_edit.grab_focus.call_deferred()
|
||||
|
||||
|
||||
func __on_description_changed() -> void:
|
||||
board_data.get_task(data_uuid).description = description_edit.text
|
||||
|
||||
|
||||
func __on_category_selected(index: int) -> void:
|
||||
board_data.get_task(data_uuid).category = category_select.get_item_metadata(index)
|
||||
|
||||
|
||||
func __create_step(text: String) -> void:
|
||||
if text.is_empty():
|
||||
return
|
||||
var task = board_data.get_task(data_uuid)
|
||||
var data = __StepData.new(text)
|
||||
task.add_step(data)
|
||||
create_step_edit.text = ""
|
||||
update()
|
||||
for step in step_holder.get_step_entries():
|
||||
if step.step_data == data:
|
||||
step.grab_focus.call_deferred()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfuka8bfmcwog
|
||||
@@ -0,0 +1,100 @@
|
||||
[gd_scene format=3 uid="uid://bwi22eyrmeeet"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cfuka8bfmcwog" path="res://addons/kanban_tasks/view/details/details.gd" id="1_gh7s6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dwjg5vyxx4g48" path="res://addons/kanban_tasks/view/details/step_holder.tscn" id="2_0ptaf"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g2k57"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.1, 0.1, 0.1, 0.6)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 5
|
||||
|
||||
[node name="Details" type="AcceptDialog" unique_id=537054767]
|
||||
oversampling_override = 1.0
|
||||
title = "Task Details"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(916, 557)
|
||||
ok_button_text = "Close"
|
||||
script = ExtResource("1_gh7s6")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1730906135]
|
||||
custom_minimum_size = Vector2(900, 500)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Category" type="OptionButton" parent="VBoxContainer" unique_id=879562296]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer" unique_id=1738893748]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer/HSplitContainer" unique_id=1951989716]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Description" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=2088816932]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
wrap_mode = 1
|
||||
|
||||
[node name="StepDetails" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer" unique_id=1125161346]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2007120957]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=609616893]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Step Details:"
|
||||
|
||||
[node name="CloseStepDetails" type="Button" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails/HBoxContainer" unique_id=1389055250]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Close"
|
||||
flat = true
|
||||
|
||||
[node name="StepEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VSplitContainer/StepDetails" unique_id=2005028969]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
wrap_mode = 1
|
||||
|
||||
[node name="StepList" type="VBoxContainer" parent="VBoxContainer/HSplitContainer" unique_id=1447925866]
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CreateStepEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/StepList" unique_id=479211446]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Create Step"
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HSplitContainer/StepList" unique_id=1566602760]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_g2k57")
|
||||
|
||||
[node name="StepHolder" parent="VBoxContainer/HSplitContainer/StepList/PanelContainer" unique_id=128405745 instance=ExtResource("2_0ptaf")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
steps_focus_mode = 2
|
||||
@@ -0,0 +1,135 @@
|
||||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
## Visual representation of a step.
|
||||
|
||||
|
||||
signal action_triggered(entry: __StepEntry, action: Actions)
|
||||
|
||||
const __EditLabel := preload("../../edit_label/edit_label.gd")
|
||||
const __StepData := preload("../../data/step.gd")
|
||||
const __Singletons := preload("../../plugin_singleton/singletons.gd")
|
||||
const __Shortcuts := preload("../shortcuts.gd")
|
||||
const __StepEntry := preload("step_entry.gd")
|
||||
|
||||
enum Actions {
|
||||
DELETE,
|
||||
MOVE_UP,
|
||||
MOVE_DOWN,
|
||||
EDIT_HARD, ## Forces the step details to open.
|
||||
EDIT_SOFT, ## Only switches to this step if the details are opened.
|
||||
CLOSE,
|
||||
}
|
||||
|
||||
@export var context_menu_enabled: bool = true
|
||||
|
||||
var done: CheckBox
|
||||
var title_label: Label
|
||||
var focus_box: StyleBoxFlat
|
||||
var context_menu: PopupMenu
|
||||
|
||||
var step_data: __StepData
|
||||
|
||||
var being_edited := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
|
||||
context_menu = PopupMenu.new()
|
||||
context_menu.id_pressed.connect(__action)
|
||||
add_child(context_menu)
|
||||
|
||||
done = CheckBox.new()
|
||||
done.focus_mode = Control.FOCUS_NONE
|
||||
done.toggled.connect(__set_done)
|
||||
add_child(done)
|
||||
|
||||
title_label = Label.new()
|
||||
title_label.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
title_label.text = step_data.details
|
||||
title_label.max_lines_visible = 1
|
||||
title_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
add_child(title_label)
|
||||
|
||||
focus_box = StyleBoxFlat.new()
|
||||
focus_box.bg_color = Color(1, 1, 1, 0.1)
|
||||
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
|
||||
step_data.changed.connect(update)
|
||||
update()
|
||||
|
||||
|
||||
func _shortcut_input(event: InputEvent) -> void:
|
||||
if not __Shortcuts.should_handle_shortcut(self):
|
||||
return
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
if not event.is_echo() and event.is_pressed():
|
||||
if shortcuts.rename.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
__action(Actions.EDIT_HARD)
|
||||
elif shortcuts.confirm.matches_event(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
done.button_pressed = not done.button_pressed
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
accept_event()
|
||||
if context_menu_enabled:
|
||||
__update_context_menu()
|
||||
context_menu.position = get_global_mouse_position()
|
||||
if not get_window().gui_embed_subwindows:
|
||||
context_menu.position += get_window().position
|
||||
context_menu.popup()
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() and event.is_double_click():
|
||||
__action(Actions.EDIT_HARD)
|
||||
|
||||
|
||||
func _notification(what) -> void:
|
||||
match(what):
|
||||
NOTIFICATION_DRAW:
|
||||
if has_focus() or being_edited:
|
||||
focus_box.draw(get_canvas_item(), Rect2(Vector2.ZERO, get_rect().size))
|
||||
NOTIFICATION_FOCUS_ENTER:
|
||||
__action(Actions.EDIT_SOFT)
|
||||
|
||||
|
||||
func update() -> void:
|
||||
tooltip_text = step_data.details
|
||||
done.set_pressed_no_signal(step_data.done)
|
||||
|
||||
title_label.text = step_data.details
|
||||
|
||||
|
||||
func __action(what: Actions) -> void:
|
||||
action_triggered.emit(self, what)
|
||||
|
||||
|
||||
func __update_context_menu() -> void:
|
||||
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
|
||||
|
||||
context_menu.clear()
|
||||
context_menu.size = Vector2.ZERO
|
||||
|
||||
if being_edited:
|
||||
context_menu.add_icon_item(get_theme_icon(&"Close", &"EditorIcons"), "Close", Actions.CLOSE)
|
||||
else:
|
||||
context_menu.add_icon_item(get_theme_icon(&"Rename", &"EditorIcons"), "Edit", Actions.EDIT_HARD)
|
||||
context_menu.set_item_shortcut(context_menu.get_item_index(Actions.EDIT_HARD), shortcuts.rename)
|
||||
|
||||
context_menu.add_icon_item(get_theme_icon(&"MoveUp", &"EditorIcons"), "Move Up", Actions.MOVE_UP)
|
||||
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_UP), get_index() == 0)
|
||||
context_menu.add_icon_item(get_theme_icon(&"MoveDown", &"EditorIcons"), "Move Down", Actions.MOVE_DOWN)
|
||||
context_menu.set_item_disabled(context_menu.get_item_index(Actions.MOVE_DOWN), get_index() == get_parent().get_child_count() - 1)
|
||||
|
||||
context_menu.add_separator()
|
||||
|
||||
context_menu.add_icon_item(get_theme_icon(&"Remove", &"EditorIcons"), "Delete", Actions.DELETE)
|
||||
|
||||
|
||||
func __set_done(done: bool) -> void:
|
||||
__action(Actions.CLOSE)
|
||||
step_data.done = done
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdyn8h04v5qqh
|
||||
@@ -0,0 +1,190 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal entry_action_triggered(entry: __StepEntry, action: __StepEntry.Actions)
|
||||
signal entry_move_requesed(moved_entry: __StepEntry, target_entry: __StepEntry, move_after_target: bool)
|
||||
|
||||
const __StepData := preload("../../data/step.gd")
|
||||
const __StepEntry := preload("../details/step_entry.gd")
|
||||
|
||||
@export var scrollable: bool = true:
|
||||
set(value):
|
||||
if value != scrollable:
|
||||
scrollable = value
|
||||
__update_children_settings()
|
||||
|
||||
@export var steps_can_be_removed: bool = true:
|
||||
set(value):
|
||||
if value != steps_can_be_removed:
|
||||
steps_can_be_removed = value
|
||||
__update_children_settings()
|
||||
|
||||
@export var steps_can_be_reordered: bool = true:
|
||||
set(value):
|
||||
if value != steps_can_be_reordered:
|
||||
steps_can_be_reordered = value
|
||||
__update_children_settings()
|
||||
|
||||
@export var steps_have_context_menu: bool = true:
|
||||
set(value):
|
||||
if value != steps_have_context_menu:
|
||||
steps_have_context_menu = value
|
||||
__update_children_settings()
|
||||
|
||||
@export var steps_focus_mode := FocusMode.FOCUS_NONE:
|
||||
set(value):
|
||||
if value != steps_focus_mode:
|
||||
steps_focus_mode = value
|
||||
__update_children_settings()
|
||||
|
||||
var __mouse_entered_step_list: bool = false
|
||||
var __move_target_entry: __StepEntry = null
|
||||
var __move_after_target: bool = false
|
||||
|
||||
@onready var __scroll_container: ScrollContainer = %ScrollContainer
|
||||
@onready var __remove_separator: HSeparator = %RemoveSeparator
|
||||
@onready var __step_list: VBoxContainer = %StepList
|
||||
@onready var __remove_area: Button = %RemoveArea
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
__step_list.draw.connect(__on_step_list_draw)
|
||||
__step_list.mouse_exited.connect(__on_step_list_mouse_exited)
|
||||
__step_list.mouse_entered.connect(__on_step_list_mouse_entered)
|
||||
notification(NOTIFICATION_THEME_CHANGED)
|
||||
__update_children_settings()
|
||||
|
||||
|
||||
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
|
||||
if not steps_can_be_removed and not steps_can_be_reordered:
|
||||
return false
|
||||
if data is __StepEntry:
|
||||
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
||||
return true
|
||||
__update_move_target(at_position)
|
||||
return (__move_target_entry != null)
|
||||
return false
|
||||
|
||||
|
||||
func _get_drag_data(at_position: Vector2) -> Variant:
|
||||
if not steps_can_be_removed and not steps_can_be_reordered:
|
||||
return null
|
||||
for entry in get_step_entries():
|
||||
if entry.get_global_rect().has_point(get_global_transform() * at_position):
|
||||
var preview := Label.new()
|
||||
preview.text = entry.step_data.details
|
||||
set_drag_preview(preview)
|
||||
return entry
|
||||
return null
|
||||
|
||||
|
||||
func _drop_data(at_position: Vector2, data: Variant) -> void:
|
||||
if __move_target_entry != null:
|
||||
entry_move_requesed.emit(data, __move_target_entry, __move_after_target)
|
||||
__move_target_entry = null
|
||||
if data is __StepEntry:
|
||||
if __remove_area.get_global_rect().has_point(get_global_transform() * at_position):
|
||||
data.__action(__StepEntry.Actions.DELETE)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_THEME_CHANGED and not is_part_of_edited_scene():
|
||||
if is_instance_valid(__remove_area):
|
||||
__remove_area.icon = get_theme_icon(&"Remove", &"EditorIcons")
|
||||
|
||||
|
||||
func add_step(step: __StepData) -> void:
|
||||
var entry = __StepEntry.new()
|
||||
entry.step_data = step
|
||||
entry.show_behind_parent = true
|
||||
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
__step_list.add_child(entry)
|
||||
entry.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
entry.action_triggered.connect(__on_entry_action_triggered)
|
||||
entry.context_menu_enabled = steps_have_context_menu
|
||||
entry.focus_mode = steps_focus_mode
|
||||
|
||||
|
||||
func clear_steps() -> void:
|
||||
for step in get_step_entries():
|
||||
__step_list.remove_child(step)
|
||||
step.queue_free()
|
||||
|
||||
|
||||
func get_step_entries() -> Array[__StepEntry]:
|
||||
var step_entries: Array[__StepEntry] = []
|
||||
if is_instance_valid(__step_list):
|
||||
for child in __step_list.get_children():
|
||||
if child is __StepEntry:
|
||||
step_entries.append(child)
|
||||
return step_entries
|
||||
|
||||
|
||||
func __update_children_settings() -> void:
|
||||
if is_instance_valid(__scroll_container):
|
||||
__scroll_container.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO if scrollable else ScrollContainer.SCROLL_MODE_DISABLED
|
||||
if is_instance_valid(__remove_separator):
|
||||
__remove_separator.visible = steps_can_be_removed
|
||||
if is_instance_valid(__remove_area):
|
||||
__remove_area.visible = steps_can_be_removed
|
||||
for entry in get_step_entries():
|
||||
entry.context_menu_enabled = steps_have_context_menu
|
||||
entry.focus_mode = steps_focus_mode
|
||||
|
||||
|
||||
func __update_move_target(at_position: Vector2) -> void:
|
||||
var at_global_position := get_global_transform() * at_position
|
||||
# This __mouse_entered_step_list is needed here, as this seemed to be the only reliable solution, as:
|
||||
# 1) something is NOK with transforming at_position to global and compare with step_list.global_rect
|
||||
# 2) cannot decide what is the visible rect of the step_list
|
||||
# 3) _can_drop_data was called even after mouse is outside the list (to the bottom direction)
|
||||
if __mouse_entered_step_list:
|
||||
var closes_entry: __StepEntry = null
|
||||
var smallest_distance: float
|
||||
var position_is_after_closes_entry: bool
|
||||
for e in get_step_entries():
|
||||
var entry_global_rect = e.get_global_rect()
|
||||
var distance := abs(at_global_position.y - entry_global_rect.position.y)
|
||||
if closes_entry == null or distance < smallest_distance:
|
||||
closes_entry = e
|
||||
smallest_distance = distance
|
||||
position_is_after_closes_entry = false
|
||||
distance = abs(at_global_position.y - entry_global_rect.end.y)
|
||||
if closes_entry == null or distance < smallest_distance:
|
||||
closes_entry = e
|
||||
smallest_distance = distance
|
||||
position_is_after_closes_entry = true
|
||||
__move_target_entry = closes_entry
|
||||
__move_after_target = position_is_after_closes_entry
|
||||
else:
|
||||
__move_target_entry = null
|
||||
__step_list.queue_redraw()
|
||||
|
||||
|
||||
func __on_step_list_mouse_entered() -> void:
|
||||
__mouse_entered_step_list = true
|
||||
|
||||
|
||||
func __on_step_list_mouse_exited() -> void:
|
||||
__mouse_entered_step_list = false
|
||||
__update_move_target(get_local_mouse_position())
|
||||
|
||||
|
||||
func __on_step_list_draw() -> void:
|
||||
if __move_target_entry != null:
|
||||
var target_rect := __step_list.get_global_transform().inverse() * __move_target_entry.get_global_rect()
|
||||
var separation = __step_list.get_theme_constant(&"separation")
|
||||
var preview_rect := Rect2(
|
||||
Vector2(0, target_rect.end.y if __move_after_target else target_rect.position.y - separation),
|
||||
Vector2(target_rect.size.x, separation)
|
||||
)
|
||||
if preview_rect.position.y < 0:
|
||||
preview_rect.position.y = 0
|
||||
if preview_rect.end.y > __step_list.size.y:
|
||||
preview_rect.position.y -= (preview_rect.end.y - __step_list.size.y)
|
||||
__step_list.draw_rect(preview_rect, get_theme_color(&"step_move_review_color"))
|
||||
|
||||
|
||||
func __on_entry_action_triggered(entry, action) -> void:
|
||||
entry_action_triggered.emit(entry, action)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dw1afc1ntflyw
|
||||
@@ -0,0 +1,45 @@
|
||||
[gd_scene format=3 uid="uid://dwjg5vyxx4g48"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dw1afc1ntflyw" path="res://addons/kanban_tasks/view/details/step_holder.gd" id="1_exd17"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_1hs0w"]
|
||||
StepHolder/base_type = &"VBoxContainer"
|
||||
StepHolder/colors/step_move_review_color = Color(0.439216, 0.729412, 0.980392, 0.501961)
|
||||
|
||||
[node name="StepHolder" type="VBoxContainer" unique_id=1736962155]
|
||||
offset_right = 326.0
|
||||
offset_bottom = 500.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme = SubResource("Theme_1hs0w")
|
||||
theme_type_variation = &"StepHolder"
|
||||
script = ExtResource("1_exd17")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=1852819234]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
metadata/_edit_use_anchors_ = true
|
||||
|
||||
[node name="StepList" type="VBoxContainer" parent="ScrollContainer" unique_id=643748775]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="RemoveSeparator" type="HSeparator" parent="." unique_id=106663436]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="RemoveArea" type="Button" parent="." unique_id=1184154065]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
focus_mode = 0
|
||||
mouse_filter = 2
|
||||
button_mask = 0
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"><path d="m1.3229 1.3229h10.583v23.812h-10.583z" fill="#575b64" stroke="#575b64" stroke-width="1.0583"/><path d="m14.552 1.3229h10.583v23.812h-10.583z" fill="#626771" stroke="#626771" stroke-width="1.0583"/><path d="m2.3476 10.695h15.217v3.9785h-15.217z" transform="matrix(.99153-.12989.11858.99294 0 0)" fill="#179ceb" stroke="#179ceb" stroke-width="1.0584"/></g><path d="m16.933 10.848c-.9234.09187-1.7773.46826-2.9104 1.1617 1.1037-.18609 2.0649-.1914 3.4396.16123l-.52917-1.3229" fill="#a4c1d3" fill-opacity=".60851"/><path d="m16.931 10.715c-.016153.000299-.032539.003438-.048059.009819-.04982.02053-.082375.069113-.082165.12299v1.0588c.000615.08981.088821.15306.17415.12506l.22738-.075448.050642.15193a.1825.1825 0 00.23048.11524.1825.1825 0 00.11524-.23048l-.050126-.15141.22118-.073381c.09245-.03185.11961-.14979.050643-.21911l-.79272-.79478c-.02598-.026228-.061098-.03993-.096635-.039274z" fill="#d8d8d8" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://3edv1ymvukp0"
|
||||
path="res://.godot/imported/1.svg-15461a63252c61d47c72a31629894a26.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/kanban_tasks/view/documentation/1.svg"
|
||||
dest_files=["res://.godot/imported/1.svg-15461a63252c61d47c72a31629894a26.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g fill="#575b64" stroke="#575b64" stroke-linejoin="round"><path d="m1.3229 1.3229h10.583v23.812h-10.583z" stroke-width="1.0583"/><path d="m14.552 14.552h10.583v10.583h-10.583z" stroke-width="1.058"/><path d="m14.552 1.3229h10.583v10.583h-10.583z" stroke-width="1.0581"/></g><path d="m1.852 2.9104h9.5251v1.3229h-9.5251z" fill="#179ceb" stroke="#179ceb" stroke-width="1.0581" stroke-linejoin="round"/><path d="m2.1167 3.6049h7.9375" fill="none" stroke="#106da5" stroke-width=".79375" stroke-linecap="round"/><path d="m8.4073 3.5781-.23386-.80682.60804.65481.40926-.42095.10524.63142.42095.22217-.52619.070158-.22217.38587-.26894-.39756-.57296.15201z" fill="#afb7c9" fill-opacity=".84681"/><path d="m8.2585 3.454-.23386-.80682.60804.65481.40926-.42095.10524.63142.42095.22217-.52619.070158-.22217.38587-.26894-.39756-.57296.15201z" fill="#afb7c9" fill-opacity=".65957"/><path d="m8.8615 3.5711c-.016153.000299-.032539.0034-.048059.0098-.04982.02053-.082375.06911-.082165.12299v1.0588c.000615.08981.088821.15306.17415.12506l.22738-.07545.050642.15193a.1825.1825 0 00.23048.11524.1825.1825 0 00.11524-.23048l-.050126-.15141.22118-.07338c.09245-.03185.11961-.14979.050643-.21911l-.79272-.79478c-.02598-.02623-.061098-.03993-.096635-.03927z" fill="#d8d8d8"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bbo1gfac2wymg"
|
||||
path="res://.godot/imported/2.svg-053c5d997067871191f4000b81461e5d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/kanban_tasks/view/documentation/2.svg"
|
||||
dest_files=["res://.godot/imported/2.svg-053c5d997067871191f4000b81461e5d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user