From 1132015349bd422d2fa366d2d91e3937307ec2dd Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Fri, 14 Feb 2025 11:43:27 +0300 Subject: [PATCH] selling. really fucked up code. --- data/coin.tres | 2 +- data/water_bomb.tres | 4 +- default_bus_layout.tres | 5 +- src/ingame/bomb.gd | 139 +++++++++++++++++++++++++++++++++++++ src/ingame/ingame.tscn | 2 +- src/ingame/item_bomb.tscn | 98 ++++++++++++++++++++++++++ src/ingame/player.gd | 9 +-- src/ingame/player.tscn | 9 +-- src/ingame/water_bomb.gd | 108 ---------------------------- src/ingame/water_bomb.tscn | 5 +- src/lib/game_state.gd | 10 +-- src/lib/inventory_item.gd | 1 + src/main/main.gd | 9 +-- 13 files changed, 270 insertions(+), 131 deletions(-) create mode 100644 src/ingame/bomb.gd create mode 100644 src/ingame/item_bomb.tscn delete mode 100644 src/ingame/water_bomb.gd diff --git a/data/coin.tres b/data/coin.tres index bddf69e..d4476c9 100644 --- a/data/coin.tres +++ b/data/coin.tres @@ -10,4 +10,4 @@ name = "Coin" id = &"coin" stackable = true stack_limit = 100 -sells_for = 0 +sells_for = 1 diff --git a/data/water_bomb.tres b/data/water_bomb.tres index 8d9396e..1fd9549 100644 --- a/data/water_bomb.tres +++ b/data/water_bomb.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="InventoryItem" load_steps=4 format=3 uid="uid://cmeif37pci2ek"] +[gd_resource type="Resource" script_class="InventoryItem" load_steps=5 format=3 uid="uid://cmeif37pci2ek"] [ext_resource type="Texture2D" uid="uid://cb6qv3c0iojfl" path="res://icon.svg" id="1_g8c3q"] +[ext_resource type="PackedScene" uid="uid://tdsbo3e5ic86" path="res://src/ingame/water_bomb.tscn" id="1_t62t2"] [ext_resource type="PackedScene" uid="uid://ba2mut58elwrh" path="res://assets/water-bomb.glb" id="2_5cxkh"] [ext_resource type="Script" path="res://src/lib/inventory_item.gd" id="2_pe2p8"] @@ -8,6 +9,7 @@ script = ExtResource("2_pe2p8") icon = ExtResource("1_g8c3q") model = ExtResource("2_5cxkh") +bomb = ExtResource("1_t62t2") name = "Water Bomb" id = &"water_bomb" stackable = false diff --git a/default_bus_layout.tres b/default_bus_layout.tres index 7b11231..d9e8fb3 100644 --- a/default_bus_layout.tres +++ b/default_bus_layout.tres @@ -1,15 +1,16 @@ [gd_resource type="AudioBusLayout" format=3 uid="uid://dcxpomrfumov"] [resource] +bus/0/mute = true bus/1/name = &"Music" bus/1/solo = false bus/1/mute = false bus/1/bypass_fx = false -bus/1/volume_db = 0.0 +bus/1/volume_db = -4.25003 bus/1/send = &"Master" bus/2/name = &"SoundEffects" bus/2/solo = false bus/2/mute = false bus/2/bypass_fx = false -bus/2/volume_db = -4.00569 +bus/2/volume_db = -10.0692 bus/2/send = &"Master" diff --git a/src/ingame/bomb.gd b/src/ingame/bomb.gd new file mode 100644 index 0000000..a491c24 --- /dev/null +++ b/src/ingame/bomb.gd @@ -0,0 +1,139 @@ +class_name Bomb extends Node3D + +@export var _splash_small_sound: AudioStreamPlayer3D +@export var _splash_small_quiet_sound: AudioStreamPlayer3D +@export var _model: Node3D +@export var _splash_particles: GPUParticles3D +@export var _picking_area: Area3D +@export var body: RigidBody3D +@export var billboard := false + +## Initials to construct from +@export var item_id := &"ID" +@export var item_count := 0 +## Will be constructed as { item_id, item_count }, but could be edited after +## Use init_with_bundle() if it's dynamically assigned +@export var item_bundle: Dictionary + +## Effect of splhashing. +@export_enum("none", "water") var splash_function := "none" + +## no longer exists and shouldn't be considered, but not ready to be freed yet +var is_dead := false + +## something to ignore +var sender_id: int +var sender_body: PhysicsBody3D + +var _in_splash_range := {} + + +func _ready() -> void: + body.process_mode = Node.PROCESS_MODE_DISABLED + + +func _enter_tree() -> void: + if item_bundle.is_empty(): + item_bundle = { + "item_id": item_id, + "count": item_count + } + + +@rpc("authority", "call_local", "reliable") +func set_item_bundle(p_item_bundle: Dictionary) -> void: + item_bundle = p_item_bundle + if _model.has_method("reflect_bundle"): + _model.reflect_bundle(item_bundle) + + +func _process(delta: float) -> void: + # spin around, no need to replicate this + if not billboard: + _model.basis = _model.basis.rotated(Vector3(1, 0, 0), -((TAU*2) * delta)) + _model.basis = _model.basis.orthonormalized() + + if not is_multiplayer_authority(): + return + + # TODO: interpolate for non-authority clients + # or, do we *really* care about synchronicity? could simulate it independently. + global_position = body.global_position + + +@rpc("authority", "call_local", "reliable") +func _disable_body() -> void: + # not using synchronizer for this because it ends up enabling processing + # on other clients + (func() -> void: body.process_mode = Node.PROCESS_MODE_DISABLED).call_deferred() + _picking_area.collision_layer = 0 + + +func _on_body_entered(p_body: Node3D) -> void: + if p_body == sender_body: + return + + if p_body.is_in_group("voids"): + queue_free() + return + + if p_body.is_in_group("sell_boxes"): + var item := GameState.fetch().INVENTORY_ITEM_DB[item_bundle["item_id"]] as InventoryItem + if item.sells_for != 0: + get_node("/root/Main").add_inventory_item.rpc(&"coin", item.sells_for * item_bundle["count"]) + queue_free() + return + + if splash_function == "water": + for area: Area3D in _in_splash_range: + area.get_parent_node_3d().water.rpc_id(1, sender_id) + + if _splash_small_sound != null: + _splash_small_sound.play() + if _splash_small_quiet_sound: + _splash_small_quiet_sound.play() + if _splash_particles: + _splash_particles.emitting = true + + is_dead = true + _model.hide() + _disable_body.rpc() + if _splash_small_sound != null: + await _splash_small_sound.finished + if _splash_small_quiet_sound != null and _splash_small_quiet_sound.playing: + await _splash_small_quiet_sound.finished + queue_free() + + +func _on_splash_area_entered(area: Area3D) -> void: + _in_splash_range[area] = true + + +func _on_splash_area_exited(area: Area3D) -> void: + _in_splash_range.erase(area) + + +@rpc("authority", "call_local", "reliable") +func set_global_pos(new: Vector3) -> void: + global_position = new + body.global_position = new + body.reset_physics_interpolation() + + +@rpc("any_peer", "call_local", "reliable") +func get_picked_up() -> void: + if is_multiplayer_authority(): + queue_free() + + +func mark_interactive() -> void: + # TODO: whatever + if _model.scene_file_path == "res://assets/water-bomb.glb": + for submodel in _model.get_children(): + (submodel as MeshInstance3D).mesh.surface_get_material(0).next_pass.set("shader_parameter/color", Color.WHITE) + + +func mark_non_interactive() -> void: + if _model.scene_file_path == "res://assets/water-bomb.glb": + for submodel in _model.get_children(): + (submodel as MeshInstance3D).mesh.surface_get_material(0).next_pass.set("shader_parameter/color", Color(1, 1, 1, 0)) diff --git a/src/ingame/ingame.tscn b/src/ingame/ingame.tscn index f9bd912..896b782 100644 --- a/src/ingame/ingame.tscn +++ b/src/ingame/ingame.tscn @@ -315,7 +315,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.15702, 1.04855, 5.88574) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.43229, 0.627689, 7.20138) [node name="SellBox" type="StaticBody3D" parent="." groups=["sell_boxes"]] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0) collision_layer = 0 collision_mask = 4 diff --git a/src/ingame/item_bomb.tscn b/src/ingame/item_bomb.tscn new file mode 100644 index 0000000..4fb7b9c --- /dev/null +++ b/src/ingame/item_bomb.tscn @@ -0,0 +1,98 @@ +[gd_scene load_steps=9 format=3 uid="uid://bpvbnlhpth4w7"] + +[ext_resource type="Script" path="res://src/ingame/bomb.gd" id="1_27lur"] +[ext_resource type="PackedScene" uid="uid://dxb5f3il2h1ur" path="res://src/ingame/quad_viewmodel.tscn" id="2_fy6j6"] + +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_0ebrr"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 1 +properties/1/path = NodePath(".:is_dead") +properties/1/spawn = true +properties/1/replication_mode = 2 +properties/2/path = NodePath(".:rotation") +properties/2/spawn = true +properties/2/replication_mode = 1 +properties/3/path = NodePath(".:item_bundle") +properties/3/spawn = true +properties/3/replication_mode = 2 +properties/4/path = NodePath("Model:visible") +properties/4/spawn = true +properties/4/replication_mode = 2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_v7dnr"] +albedo_color = Color(0, 1, 1, 1) +roughness = 0.2 + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_rjbgm"] +direction = Vector3(0, 1, 0) +initial_velocity_min = 5.0 +initial_velocity_max = 5.0 +gravity = Vector3(0, -8, 0) +collision_mode = 2 + +[sub_resource type="SphereMesh" id="SphereMesh_bhdh4"] +radius = 0.1 +height = 0.2 + +[sub_resource type="SphereShape3D" id="SphereShape3D_6c830"] +radius = 0.2 + +[sub_resource type="SphereShape3D" id="SphereShape3D_y6453"] +radius = 1.5 + +[node name="ItemBomb" type="Node3D" node_paths=PackedStringArray("_model", "_picking_area", "body")] +script = ExtResource("1_27lur") +_model = NodePath("Model") +_picking_area = NodePath("PickingArea") +body = NodePath("RigidBody3D") +billboard = true + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_0ebrr") + +[node name="Model" parent="." instance=ExtResource("2_fy6j6")] + +[node name="SplashParticles" type="GPUParticles3D" parent="."] +visible = false +material_override = SubResource("StandardMaterial3D_v7dnr") +emitting = false +amount = 32 +lifetime = 2.0 +one_shot = true +explosiveness = 1.0 +randomness = 1.0 +process_material = SubResource("ParticleProcessMaterial_rjbgm") +draw_pass_1 = SubResource("SphereMesh_bhdh4") + +[node name="RigidBody3D" type="RigidBody3D" parent="."] +top_level = true +collision_layer = 4 +collision_mask = 3 +contact_monitor = true +max_contacts_reported = 1 +linear_damp_mode = 1 +linear_damp = 0.3 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"] +shape = SubResource("SphereShape3D_6c830") + +[node name="SplashArea" type="Area3D" parent="RigidBody3D"] +collision_layer = 0 +collision_mask = 8 +monitorable = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D/SplashArea"] +shape = SubResource("SphereShape3D_y6453") + +[node name="PickingArea" type="Area3D" parent="."] +collision_layer = 16 +collision_mask = 0 +monitoring = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="PickingArea"] +shape = SubResource("SphereShape3D_6c830") + +[connection signal="body_entered" from="RigidBody3D" to="." method="_on_body_entered"] +[connection signal="area_entered" from="RigidBody3D/SplashArea" to="." method="_on_splash_area_entered"] +[connection signal="area_exited" from="RigidBody3D/SplashArea" to="." method="_on_splash_area_exited"] diff --git a/src/ingame/player.gd b/src/ingame/player.gd index ed8aeb8..c3e2093 100644 --- a/src/ingame/player.gd +++ b/src/ingame/player.gd @@ -3,8 +3,6 @@ class_name Player extends CharacterBody3D const SPEED = 5.0 const JUMP_VELOCITY = 4.5 -@export var _projectile_scene: PackedScene - @export var _projectile_holder: Node @export var _projectile_point: Marker3D @export var _camera_pivot: Node3D @@ -24,7 +22,7 @@ var _projectile_speed := 12.0 var _interaction_selection: Node3D var controls_disabled := false -var held_thing := { "item_id": &"empty_hand", "count": 0 } +@export var held_thing := { "item_id": &"empty_hand", "count": 0 } # What the others see. @@ -133,6 +131,7 @@ func hold_thing(p_bundle: Dictionary) -> void: mesh.surface_set_material(0, material) submodel.mesh = mesh + @rpc("any_peer", "call_local", "reliable") func throw_thing() -> void: held_thing = { "item_id": &"empty_hand", "count": 0 } @@ -165,9 +164,11 @@ func _unhandled_input(event: InputEvent) -> void: hold_thing.rpc(bundle) if event.is_action_pressed("fire") and not empty_handed(): - var new_projectile: Node3D = _projectile_scene.instantiate() + var item := GameState.fetch().INVENTORY_ITEM_DB[held_thing["item_id"]] as InventoryItem + var new_projectile: Node3D = item.bomb.instantiate() _projectile_holder.add_child(new_projectile, true) _set_projectile_authority.rpc(new_projectile.get_path(), id) + new_projectile.set_item_bundle.rpc(held_thing) new_projectile.set_global_pos.rpc(_projectile_point.global_position) new_projectile.rotation = rotation new_projectile.sender_body = self diff --git a/src/ingame/player.tscn b/src/ingame/player.tscn index 9592bd1..0b3b800 100644 --- a/src/ingame/player.tscn +++ b/src/ingame/player.tscn @@ -1,8 +1,7 @@ -[gd_scene load_steps=9 format=3 uid="uid://cs8c570bxh6u"] +[gd_scene load_steps=8 format=3 uid="uid://cs8c570bxh6u"] [ext_resource type="Script" path="res://src/ingame/player.gd" id="1_r8lgj"] [ext_resource type="Script" path="res://src/lib/player_character.gd" id="1_sba4x"] -[ext_resource type="PackedScene" uid="uid://tdsbo3e5ic86" path="res://src/ingame/water_bomb.tscn" id="2_naek4"] [ext_resource type="AudioStream" uid="uid://3dlhs18w1fa2" path="res://assets/sfx/boom.wav" id="3_u2hxa"] [sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_2xotl"] @@ -24,6 +23,9 @@ properties/4/replication_mode = 2 properties/5/path = NodePath("ShotSound:playing") properties/5/spawn = true properties/5/replication_mode = 2 +properties/6/path = NodePath(".:held_thing") +properties/6/spawn = true +properties/6/replication_mode = 2 [sub_resource type="CapsuleMesh" id="CapsuleMesh_hi8jw"] @@ -34,7 +36,6 @@ properties/5/replication_mode = 2 [node name="Player" type="CharacterBody3D" node_paths=PackedStringArray("_projectile_holder", "_projectile_point", "_camera_pivot", "_camera", "_line_of_sight", "_shot_sound")] collision_layer = 2 script = ExtResource("1_r8lgj") -_projectile_scene = ExtResource("2_naek4") _projectile_holder = NodePath("ProjectileHolder") _projectile_point = NodePath("ProjectilePoint") _camera_pivot = NodePath("CameraPivot") @@ -49,7 +50,7 @@ script = ExtResource("1_sba4x") replication_config = SubResource("SceneReplicationConfig_2xotl") [node name="ProjectileSpawner" type="MultiplayerSpawner" parent="."] -_spawnable_scenes = PackedStringArray("res://src/ingame/water_bomb.tscn") +_spawnable_scenes = PackedStringArray("res://src/ingame/water_bomb.tscn", "res://src/ingame/item_bomb.tscn") spawn_path = NodePath("../ProjectileHolder") [node name="ProjectileHolder" type="Node" parent="."] diff --git a/src/ingame/water_bomb.gd b/src/ingame/water_bomb.gd deleted file mode 100644 index b4d3705..0000000 --- a/src/ingame/water_bomb.gd +++ /dev/null @@ -1,108 +0,0 @@ -extends Node3D - -@export var _splash_small_sound: AudioStreamPlayer3D -@export var _splash_small_quiet_sound: AudioStreamPlayer3D -@export var _model: Node3D -@export var _splash_particles: GPUParticles3D -@export var _picking_area: Area3D -@export var body: RigidBody3D -## no longer exists and shouldn't be considered, but not ready to be freed yet -@export var is_dead := false - -const item_bundle := { "item_id": &"water_bomb", "count": 1 } - -var _in_splash_range := {} - -## something to ignore -var sender_id: int -var sender_body: PhysicsBody3D - -func _ready() -> void: - body.process_mode = Node.PROCESS_MODE_DISABLED - - -func _process(delta: float) -> void: - # spin around, no need to replicate this - _model.basis = _model.basis.rotated(Vector3(1, 0, 0), -((TAU*2) * delta)) - _model.basis = _model.basis.orthonormalized() - - if not is_multiplayer_authority(): - return - - global_position = body.global_position - - -@rpc("authority", "call_local", "reliable") -func _disable_body() -> void: - # not using synchronizer for this because it ends up enabling processing - # on other clients - (func() -> void: body.process_mode = Node.PROCESS_MODE_DISABLED).call_deferred() - _picking_area.collision_layer = 0 - - -@rpc("authority", "call_local", "reliable") -func get_sold() -> void: - var item := GameState.fetch().INVENTORY_ITEM_DB[item_bundle["item_id"]] - get_node("/root/Main").add_inventory_item(&"coin", item.sells_for * item_bundle["count"]) - - -func _on_body_entered(p_body: Node3D) -> void: - if p_body == sender_body: - return - - if p_body.is_in_group("voids"): - queue_free() - return - - if p_body.is_in_group("sell_boxes"): - var item := GameState.fetch().INVENTORY_ITEM_DB[item_bundle["item_id"]] - if item.sells_for != 0: - queue_free() - get_sold.rpc() - return - - for area: Area3D in _in_splash_range: - area.get_parent_node_3d().water.rpc_id(1, sender_id) - - _splash_small_sound.play() - _splash_small_quiet_sound.play() - _splash_particles.emitting = true - - is_dead = true - _model.hide() - _disable_body.rpc() - await _splash_small_sound.finished - if _splash_small_quiet_sound.playing: - await _splash_small_quiet_sound.finished - queue_free() - - -func _on_splash_area_entered(area: Area3D) -> void: - _in_splash_range[area] = true - - -func _on_splash_area_exited(area: Area3D) -> void: - _in_splash_range.erase(area) - - -@rpc("authority", "call_local", "reliable") -func set_global_pos(new: Vector3) -> void: - global_position = new - body.global_position = new - body.reset_physics_interpolation() - - -@rpc("any_peer", "call_local", "reliable") -func get_picked_up() -> void: - if is_multiplayer_authority(): - queue_free() - - -func mark_interactive() -> void: - for submodel in _model.get_children(): - (submodel as MeshInstance3D).mesh.surface_get_material(0).next_pass.set("shader_parameter/color", Color.WHITE) - - -func mark_non_interactive() -> void: - for submodel in _model.get_children(): - (submodel as MeshInstance3D).mesh.surface_get_material(0).next_pass.set("shader_parameter/color", Color(1, 1, 1, 0)) diff --git a/src/ingame/water_bomb.tscn b/src/ingame/water_bomb.tscn index 130ea8c..1e2560f 100644 --- a/src/ingame/water_bomb.tscn +++ b/src/ingame/water_bomb.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=11 format=3 uid="uid://tdsbo3e5ic86"] -[ext_resource type="Script" path="res://src/ingame/water_bomb.gd" id="1_lk5fq"] +[ext_resource type="Script" path="res://src/ingame/bomb.gd" id="1_lk5fq"] [ext_resource type="AudioStream" uid="uid://dtjpv2b74g24m" path="res://assets/sfx/splash-small.wav" id="2_0wk8g"] [ext_resource type="PackedScene" uid="uid://ba2mut58elwrh" path="res://assets/water-bomb.glb" id="2_v2imr"] [ext_resource type="AudioStream" uid="uid://blgrl2wl05feq" path="res://assets/sfx/splash-small-quiet.wav" id="3_hgy7l"] @@ -57,6 +57,9 @@ _model = NodePath("Model") _splash_particles = NodePath("SplashParticles") _picking_area = NodePath("PickingArea") body = NodePath("RigidBody3D") +item_id = &"water_bomb" +item_count = 1 +splash_function = "water" [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] replication_config = SubResource("SceneReplicationConfig_0ebrr") diff --git a/src/lib/game_state.gd b/src/lib/game_state.gd index a96bbe6..b72e625 100644 --- a/src/lib/game_state.gd +++ b/src/lib/game_state.gd @@ -1,11 +1,11 @@ class_name GameState extends Resource # TODO: build dynamically? -const INVENTORY_ITEM_DB = { - &"meat": preload("res://data/meat.tres"), - &"water_bomb": preload("res://data/water_bomb.tres"), - &"coin_flower": preload("res://data/coin_flower.tres"), - &"coin": preload("res://data/coin.tres"), +var INVENTORY_ITEM_DB := { + &"meat": load("res://data/meat.tres"), + &"water_bomb": load("res://data/water_bomb.tres"), + &"coin_flower": load("res://data/coin_flower.tres"), + &"coin": load("res://data/coin.tres"), } ## keys are multiplayer ID ints, values are PlayerData diff --git a/src/lib/inventory_item.gd b/src/lib/inventory_item.gd index d7b1652..21c2647 100644 --- a/src/lib/inventory_item.gd +++ b/src/lib/inventory_item.gd @@ -2,6 +2,7 @@ class_name InventoryItem extends Resource @export var icon: Texture2D = preload("res://icon.svg") @export var model: PackedScene +@export var bomb: PackedScene = preload("res://src/ingame/item_bomb.tscn") @export var name := "NAME" @export var id := &"ID" diff --git a/src/main/main.gd b/src/main/main.gd index c6a6ca4..15cc54d 100644 --- a/src/main/main.gd +++ b/src/main/main.gd @@ -182,18 +182,19 @@ func _on_chat_message_submitted(text := "") -> void: func prepate_inventory_idem(item_id: StringName) -> void: if not GameState.fetch().inventory.has(item_id): GameState.fetch().inventory[item_id] = { - "item": GameState.INVENTORY_ITEM_DB[item_id], + "item": GameState.fetch().INVENTORY_ITEM_DB[item_id], "count": 0, } -@rpc("authority", "call_local", "reliable") +# TODO: made any_peer to have access from projectiles +@rpc("any_peer", "call_local", "reliable") func add_inventory_item(item_id: StringName, amount: int) -> void: prepate_inventory_idem(item_id) GameState.fetch().inventory[item_id]["count"] += amount -@rpc("authority", "call_local", "reliable") +@rpc("any_peer", "call_local", "reliable") func remove_inventory_item(item_id: StringName, amount: int) -> void: assert(GameState.fetch().inventory.has(item_id)) @@ -204,7 +205,7 @@ func remove_inventory_item(item_id: StringName, amount: int) -> void: GameState.fetch().inventory.erase(item_id) -@rpc("authority", "call_local", "reliable") +@rpc("any_peer", "call_local", "reliable") func set_inventory_item_count(item_id: StringName, value: int) -> void: prepate_inventory_idem(item_id) GameState.fetch().inventory[item_id]["count"] = value