item bundling, viewmodel for icon things

This commit is contained in:
veclavtalica 2025-02-13 09:58:21 +03:00
parent 4c56d6badc
commit d2bbeb45dc
9 changed files with 51 additions and 43 deletions

View File

@ -1,13 +1,12 @@
[gd_resource type="Resource" script_class="InventoryItem" load_steps=3 format=3 uid="uid://nrpcuqveh7io"] [gd_resource type="Resource" script_class="InventoryItem" load_steps=3 format=3 uid="uid://nrpcuqveh7io"]
[ext_resource type="Texture2D" uid="uid://cb6qv3c0iojfl" path="res://icon.svg" id="1_u477x"] [ext_resource type="Texture2D" uid="uid://dw3x3h3f34sy3" path="res://assets/coin_flower.png" id="1_vrj0d"]
[ext_resource type="Script" path="res://src/lib/inventory_item.gd" id="3_fe16f"] [ext_resource type="Script" path="res://src/lib/inventory_item.gd" id="3_fe16f"]
[resource] [resource]
script = ExtResource("3_fe16f") script = ExtResource("3_fe16f")
icon = ExtResource("1_u477x") icon = ExtResource("1_vrj0d")
name = "Coin Flower" name = "Coin Flower"
id = &"coin_flower" id = &"coin_flower"
stackable = false stackable = false
stack_limit = 8 stack_limit = 8
count = 0

View File

@ -25,7 +25,7 @@ var _projectile_speed := 12.0
var _interaction_selection: Node3D var _interaction_selection: Node3D
var controls_disabled := false var controls_disabled := false
var held_thing: InventoryItem var held_thing := { "item_id": &"empty_hand", "count": 0 }
# What the others see. # What the others see.
@ -59,7 +59,7 @@ func _physics_process(delta: float) -> void:
## Process interactivity selection. ## Process interactivity selection.
if id == multiplayer.get_unique_id(): if id == multiplayer.get_unique_id():
var collider: Object = null var collider: Object = null
if held_thing == null and _line_of_sight.is_colliding(): if empty_handed() and _line_of_sight.is_colliding():
collider = _line_of_sight.get_collider(0) collider = _line_of_sight.get_collider(0)
if collider != _interaction_selection: if collider != _interaction_selection:
if _interaction_selection != null: if _interaction_selection != null:
@ -103,17 +103,22 @@ func _physics_process(delta: float) -> void:
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func hold_thing(p_item_instance_id: int) -> void: func hold_thing(p_bundle: Dictionary) -> void:
var item = instance_from_id(p_item_instance_id) as InventoryItem var item := GameState.fetch().INVENTORY_ITEM_DB[p_bundle["item_id"]] as InventoryItem
assert(item != null) held_thing = p_bundle
held_thing = item
var base_node := _camera_pivot.get_node("HeldViewmodel") var base_node := _camera_pivot.get_node("HeldViewmodel")
for child in base_node.get_children(): for child in base_node.get_children():
child.queue_free() child.queue_free()
if item.model != null: if item.model != null:
var model = item.model.instantiate() var model = item.model.instantiate()
base_node.add_child(model) base_node.add_child(model)
if id == multiplayer.get_unique_id(): else:
# Create a icon sprite based one instead.
var model := preload("res://src/quad_viewmodel.tscn").instantiate()
model.reflect_bundle(p_bundle)
base_node.add_child(model)
if item.model != null and id == multiplayer.get_unique_id():
# Disable depth test and increase render priority. # Disable depth test and increase render priority.
# TODO: in more complex scenarios model scene might want to have its own callback for this. # TODO: in more complex scenarios model scene might want to have its own callback for this.
for model in base_node.get_children(): for model in base_node.get_children():
@ -127,11 +132,15 @@ func hold_thing(p_item_instance_id: int) -> void:
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func throw_thing() -> void: func throw_thing() -> void:
held_thing = null # TODO: represent 'empty hand' as a unique item ? held_thing = { "item_id": &"empty_hand", "count": 0 }
for child in _camera_pivot.get_node("HeldViewmodel").get_children(): for child in _camera_pivot.get_node("HeldViewmodel").get_children():
child.queue_free() child.queue_free()
func empty_handed() -> bool:
return held_thing["item_id"] == &"empty_hand"
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if controls_disabled or id != multiplayer.get_unique_id(): if controls_disabled or id != multiplayer.get_unique_id():
return return
@ -142,12 +151,12 @@ func _unhandled_input(event: InputEvent) -> void:
_camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2) _camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2)
return return
if event.is_action_pressed("pick") and held_thing == null: if event.is_action_pressed("pick") and empty_handed():
if _interaction_selection != null: if _interaction_selection != null:
hold_thing.rpc(_interaction_selection.owner.item_component.get_instance_id())
_interaction_selection.owner.get_picked_up.rpc() _interaction_selection.owner.get_picked_up.rpc()
hold_thing.rpc(_interaction_selection.owner.item_bundle)
if event.is_action_pressed("fire") and held_thing != null: if event.is_action_pressed("fire") and not empty_handed():
var new_projectile: Node3D = _projectile_scene.instantiate() var new_projectile: Node3D = _projectile_scene.instantiate()
_projectile_holder.add_child(new_projectile, true) _projectile_holder.add_child(new_projectile, true)
_set_projectile_authority.rpc(new_projectile.get_path(), id) _set_projectile_authority.rpc(new_projectile.get_path(), id)

View File

@ -5,7 +5,7 @@ extends Node3D
@export var _mesh: MeshInstance3D @export var _mesh: MeshInstance3D
@export var _area: Area3D @export var _area: Area3D
@export var item_component: InventoryItem var item_bundle := { "item_id": &"coin_flower", "count": 1 }
var needs_water := true var needs_water := true
var stage: int = 1 var stage: int = 1
@ -47,6 +47,7 @@ func water(sender_id: int) -> void:
@rpc("any_peer", "call_local", "reliable") @rpc("any_peer", "call_local", "reliable")
func get_picked_up() -> void: func get_picked_up() -> void:
assert(stage == final_stage)
stage = 1 stage = 1
_mesh.material_override.set("shader_parameter/albedo_texture", load("res://assets/sprout%s.png" % stage)) _mesh.material_override.set("shader_parameter/albedo_texture", load("res://assets/sprout%s.png" % stage))
_area.collision_layer ^= 1 << 4 _area.collision_layer ^= 1 << 4

View File

@ -1,7 +1,6 @@
[gd_scene load_steps=10 format=3 uid="uid://bysgtksvovyur"] [gd_scene load_steps=9 format=3 uid="uid://bysgtksvovyur"]
[ext_resource type="Script" path="res://src/ingame/sprout.gd" id="1_snma1"] [ext_resource type="Script" path="res://src/ingame/sprout.gd" id="1_snma1"]
[ext_resource type="Resource" uid="uid://nrpcuqveh7io" path="res://data/coin_flower.tres" id="2_amjnn"]
[ext_resource type="Texture2D" uid="uid://d35y5ckne72qe" path="res://assets/sprout1.png" id="2_ipgad"] [ext_resource type="Texture2D" uid="uid://d35y5ckne72qe" path="res://assets/sprout1.png" id="2_ipgad"]
[ext_resource type="Shader" path="res://assets/shaders/interactivity_outline2.gdshader" id="2_oa2it"] [ext_resource type="Shader" path="res://assets/shaders/interactivity_outline2.gdshader" id="2_oa2it"]
[ext_resource type="Texture2D" uid="uid://cwbl0r1e26eja" path="res://assets/drop.png" id="3_kghdv"] [ext_resource type="Texture2D" uid="uid://cwbl0r1e26eja" path="res://assets/drop.png" id="3_kghdv"]
@ -36,7 +35,6 @@ _need_water_drop = NodePath("NeedWaterDrop")
_production_timer = NodePath("ProductionTimer") _production_timer = NodePath("ProductionTimer")
_mesh = NodePath("Mesh") _mesh = NodePath("Mesh")
_area = NodePath("Area3D") _area = NodePath("Area3D")
item_component = ExtResource("2_amjnn")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_rs2qp") replication_config = SubResource("SceneReplicationConfig_rs2qp")

View File

@ -8,8 +8,8 @@ extends Node3D
@export var body: RigidBody3D @export var body: RigidBody3D
## no longer exists and shouldn't be considered, but not ready to be freed yet ## no longer exists and shouldn't be considered, but not ready to be freed yet
@export var is_dead := false @export var is_dead := false
## by convention is looked up in owner from areas
@export var item_component: InventoryItem const item_bundle := { "item_id": &"water_bomb", "count": 1 }
var _in_splash_range := {} var _in_splash_range := {}

View File

@ -1,8 +1,7 @@
[gd_scene load_steps=12 format=3 uid="uid://tdsbo3e5ic86"] [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/water_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="AudioStream" uid="uid://dtjpv2b74g24m" path="res://assets/sfx/splash-small.wav" id="2_0wk8g"]
[ext_resource type="Resource" uid="uid://cmeif37pci2ek" path="res://data/water_bomb.tres" id="2_b3357"]
[ext_resource type="PackedScene" uid="uid://ba2mut58elwrh" path="res://assets/water-bomb.glb" id="2_v2imr"] [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"] [ext_resource type="AudioStream" uid="uid://blgrl2wl05feq" path="res://assets/sfx/splash-small-quiet.wav" id="3_hgy7l"]
@ -58,7 +57,6 @@ _model = NodePath("Model")
_splash_particles = NodePath("SplashParticles") _splash_particles = NodePath("SplashParticles")
_picking_area = NodePath("PickingArea") _picking_area = NodePath("PickingArea")
body = NodePath("RigidBody3D") body = NodePath("RigidBody3D")
item_component = ExtResource("2_b3357")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_0ebrr") replication_config = SubResource("SceneReplicationConfig_0ebrr")

View File

@ -1,17 +1,18 @@
class_name GameState class_name GameState extends Resource
extends Resource
# TODO: build dynamically?
const INVENTORY_ITEM_DB = { const INVENTORY_ITEM_DB = {
&"meat": preload("res://data/meat.tres"), &"meat": preload("res://data/meat.tres"),
&"water_bomb": preload("res://data/water_bomb.tres"), &"water_bomb": preload("res://data/water_bomb.tres"),
&"coin_flower": preload("res://data/coin_flower.tres"), &"coin_flower": preload("res://data/coin_flower.tres"),
} }
# keys are multiplayer ID ints, values are PlayerData ## keys are multiplayer ID ints, values are PlayerData
@export var player_data := {} @export var player_data := {}
# keys are InventoryItem resource IDs (from db), values are InventoryItems ## keys are InventoryItem resource IDs (from db),
## values are { "item": InventoryItem, "count": int }
@export var inventory := {} @export var inventory := {}
@export var coins: int @export var coins: int # TODO: might it make sense to have even them as items?
static var _instance := GameState.new() static var _instance := GameState.new()

View File

@ -8,5 +8,3 @@ extends Resource
@export var stackable := false @export var stackable := false
@export var stack_limit := 8 @export var stack_limit := 8
# TODO: should it be here? or context dependent, for different inventories
@export var count := 0

View File

@ -185,32 +185,36 @@ func set_coins(value: int) -> void:
GameState.fetch().coins = value GameState.fetch().coins = value
## Make sure it exists and all.
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],
"count": 0,
}
@rpc("authority", "call_local", "reliable") @rpc("authority", "call_local", "reliable")
func add_inventory_item(item_id: StringName, amount: int) -> void: func add_inventory_item(item_id: StringName, amount: int) -> void:
if not GameState.fetch().inventory.has(item_id): prepate_inventory_idem(item_id)
GameState.fetch().inventory[item_id] = GameState.INVENTORY_ITEM_DB[item_id].duplicate() GameState.fetch().inventory[item_id]["count"] += amount
GameState.fetch().inventory[item_id].count += amount
@rpc("authority", "call_local", "reliable") @rpc("authority", "call_local", "reliable")
func remove_inventory_item(item_id: StringName, amount: int) -> void: func remove_inventory_item(item_id: StringName, amount: int) -> void:
assert(GameState.fetch().inventory.has(item_id)) assert(GameState.fetch().inventory.has(item_id))
GameState.fetch().inventory[item_id].count -= amount GameState.fetch().inventory[item_id]["count"] -= amount
assert(GameState.fetch().inventory[item_id].count >= 0) assert(GameState.fetch().inventory[item_id]["count"] >= 0)
if GameState.fetch().inventory[item_id].count == 0: if GameState.fetch().inventory[item_id]["count"] == 0:
GameState.fetch().inventory.erase(item_id) GameState.fetch().inventory.erase(item_id)
@rpc("authority", "call_local", "reliable") @rpc("authority", "call_local", "reliable")
func set_inventory_item_count(item_id: StringName, value: int) -> void: func set_inventory_item_count(item_id: StringName, value: int) -> void:
assert(value >= 0) prepate_inventory_idem(item_id)
if not GameState.fetch().inventory.has(item_id): GameState.fetch().inventory[item_id]["count"] = value
GameState.fetch().inventory[item_id] = GameState.INVENTORY_ITEM_DB[item_id].duplicate()
GameState.fetch().inventory[item_id].count = value if GameState.fetch().inventory[item_id]["count"] == 0:
if GameState.fetch().inventory[item_id].count == 0:
GameState.fetch().inventory.erase(item_id) GameState.fetch().inventory.erase(item_id)