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"]
[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"]
[resource]
script = ExtResource("3_fe16f")
icon = ExtResource("1_u477x")
icon = ExtResource("1_vrj0d")
name = "Coin Flower"
id = &"coin_flower"
stackable = false
stack_limit = 8
count = 0

View File

@ -25,7 +25,7 @@ var _projectile_speed := 12.0
var _interaction_selection: Node3D
var controls_disabled := false
var held_thing: InventoryItem
var held_thing := { "item_id": &"empty_hand", "count": 0 }
# What the others see.
@ -59,7 +59,7 @@ func _physics_process(delta: float) -> void:
## Process interactivity selection.
if id == multiplayer.get_unique_id():
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)
if collider != _interaction_selection:
if _interaction_selection != null:
@ -103,17 +103,22 @@ func _physics_process(delta: float) -> void:
@rpc("any_peer", "call_local", "reliable")
func hold_thing(p_item_instance_id: int) -> void:
var item = instance_from_id(p_item_instance_id) as InventoryItem
assert(item != null)
held_thing = item
func hold_thing(p_bundle: Dictionary) -> void:
var item := GameState.fetch().INVENTORY_ITEM_DB[p_bundle["item_id"]] as InventoryItem
held_thing = p_bundle
var base_node := _camera_pivot.get_node("HeldViewmodel")
for child in base_node.get_children():
child.queue_free()
if item.model != null:
var model = item.model.instantiate()
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.
# TODO: in more complex scenarios model scene might want to have its own callback for this.
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")
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():
child.queue_free()
func empty_handed() -> bool:
return held_thing["item_id"] == &"empty_hand"
func _unhandled_input(event: InputEvent) -> void:
if controls_disabled or id != multiplayer.get_unique_id():
return
@ -142,12 +151,12 @@ func _unhandled_input(event: InputEvent) -> void:
_camera_pivot.rotation.x = clamp(_camera_pivot.rotation.x, -1.2, 1.2)
return
if event.is_action_pressed("pick") and held_thing == null:
if event.is_action_pressed("pick") and empty_handed():
if _interaction_selection != null:
hold_thing.rpc(_interaction_selection.owner.item_component.get_instance_id())
_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()
_projectile_holder.add_child(new_projectile, true)
_set_projectile_authority.rpc(new_projectile.get_path(), id)

View File

@ -5,7 +5,7 @@ extends Node3D
@export var _mesh: MeshInstance3D
@export var _area: Area3D
@export var item_component: InventoryItem
var item_bundle := { "item_id": &"coin_flower", "count": 1 }
var needs_water := true
var stage: int = 1
@ -47,6 +47,7 @@ func water(sender_id: int) -> void:
@rpc("any_peer", "call_local", "reliable")
func get_picked_up() -> void:
assert(stage == final_stage)
stage = 1
_mesh.material_override.set("shader_parameter/albedo_texture", load("res://assets/sprout%s.png" % stage))
_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="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="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"]
@ -36,7 +35,6 @@ _need_water_drop = NodePath("NeedWaterDrop")
_production_timer = NodePath("ProductionTimer")
_mesh = NodePath("Mesh")
_area = NodePath("Area3D")
item_component = ExtResource("2_amjnn")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_rs2qp")

View File

@ -8,8 +8,8 @@ extends Node3D
@export var body: RigidBody3D
## no longer exists and shouldn't be considered, but not ready to be freed yet
@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 := {}

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="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="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")
_picking_area = NodePath("PickingArea")
body = NodePath("RigidBody3D")
item_component = ExtResource("2_b3357")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_0ebrr")

View File

@ -1,17 +1,18 @@
class_name GameState
extends Resource
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"),
}
# keys are multiplayer ID ints, values are PlayerData
## keys are multiplayer ID ints, values are PlayerData
@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 coins: int
@export var coins: int # TODO: might it make sense to have even them as items?
static var _instance := GameState.new()

View File

@ -8,5 +8,3 @@ extends Resource
@export var stackable := false
@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
## 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")
func add_inventory_item(item_id: StringName, amount: int) -> void:
if not GameState.fetch().inventory.has(item_id):
GameState.fetch().inventory[item_id] = GameState.INVENTORY_ITEM_DB[item_id].duplicate()
GameState.fetch().inventory[item_id].count += amount
prepate_inventory_idem(item_id)
GameState.fetch().inventory[item_id]["count"] += amount
@rpc("authority", "call_local", "reliable")
func remove_inventory_item(item_id: StringName, amount: int) -> void:
assert(GameState.fetch().inventory.has(item_id))
GameState.fetch().inventory[item_id].count -= amount
assert(GameState.fetch().inventory[item_id].count >= 0)
GameState.fetch().inventory[item_id]["count"] -= amount
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)
@rpc("authority", "call_local", "reliable")
func set_inventory_item_count(item_id: StringName, value: int) -> void:
assert(value >= 0)
if not GameState.fetch().inventory.has(item_id):
GameState.fetch().inventory[item_id] = GameState.INVENTORY_ITEM_DB[item_id].duplicate()
prepate_inventory_idem(item_id)
GameState.fetch().inventory[item_id]["count"] = value
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)