diff --git a/src/scenes/attack_arrow.tscn b/src/scenes/attack_arrow.tscn new file mode 100644 index 0000000..f6420c1 --- /dev/null +++ b/src/scenes/attack_arrow.tscn @@ -0,0 +1,76 @@ +[gd_scene format=3 uid="uid://cvjj4wo2agd2k"] + +[ext_resource type="Texture2D" uid="uid://ba772auc1t65n" path="res://assets/gfx/arrow.png" id="1_bey2v"] +[ext_resource type="Script" uid="uid://dqbctups3eri6" path="res://scripts/attack_arrow.gd" id="1_if6eb"] +[ext_resource type="AudioStream" uid="uid://hmci4kgvbqib" path="res://assets/audio/sfx/weapons/bow/arrow_fire_swosh.wav" id="3_o8cb2"] +[ext_resource type="AudioStream" uid="uid://b140nlsak4ub7" path="res://assets/audio/sfx/weapons/bow/arrow-hit-brick-wall-01.mp3" id="4_8l43l"] +[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="4_ol4b0"] + +[sub_resource type="Gradient" id="Gradient_yp18a"] +colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 0) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_gpny7"] +gradient = SubResource("Gradient_yp18a") +fill = 1 +fill_from = Vector2(0.504587, 0.504587) +fill_to = Vector2(0.848624, 0.784404) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_b6ybh"] +size = Vector2(2, 6) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_wuwd8"] +size = Vector2(2, 8) + +[node name="Arrow" type="CharacterBody2D" unique_id=1519950765] +z_index = 10 +y_sort_enabled = true +collision_layer = 0 +collision_mask = 0 +motion_mode = 1 +script = ExtResource("1_if6eb") + +[node name="Shadow" type="Sprite2D" parent="." unique_id=1423726915] +z_index = 1 +z_as_relative = false +position = Vector2(-2.98023e-08, 0) +scale = Vector2(0.09375, 0.0820313) +texture = SubResource("GradientTexture2D_gpny7") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1110211076] +texture = ExtResource("1_bey2v") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1017444254] +shape = SubResource("RectangleShape2D_b6ybh") + +[node name="ArrowArea" type="Area2D" parent="." unique_id=1075536929] +collision_layer = 0 +collision_mask = 75 +priority = 1 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="ArrowArea" unique_id=926133347] +physics_interpolation_mode = 1 +position = Vector2(0, 1) +shape = SubResource("RectangleShape2D_wuwd8") +debug_color = Color(0.7, 0, 0.195726, 0.42) + +[node name="SfxArrowFire" type="AudioStreamPlayer2D" parent="." unique_id=1413495156] +stream = ExtResource("3_o8cb2") +pitch_scale = 1.61 +max_polyphony = 4 + +[node name="SfxImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=1132726967] +stream = ExtResource("4_8l43l") +volume_db = -4.0 +pitch_scale = 1.29 +attenuation = 3.4822 +max_polyphony = 4 +panning_strength = 1.3 + +[node name="SfxImpactSound" type="AudioStreamPlayer2D" parent="." unique_id=463883211] +stream = ExtResource("4_ol4b0") +volume_db = -4.685 +pitch_scale = 1.47 +max_polyphony = 4 + +[connection signal="area_entered" from="ArrowArea" to="." method="_on_arrow_area_area_entered"] +[connection signal="body_entered" from="ArrowArea" to="." method="_on_arrow_area_body_entered"] diff --git a/src/scenes/attack_axe_swing.tscn b/src/scenes/attack_axe_swing.tscn new file mode 100644 index 0000000..bc2cb48 --- /dev/null +++ b/src/scenes/attack_axe_swing.tscn @@ -0,0 +1,86 @@ +[gd_scene format=3 uid="uid://tcobiw1iirdw"] + +[ext_resource type="Script" uid="uid://bqrtsr3mjvv3j" path="res://scripts/attack_axe_swing.gd" id="1_xo3v0"] +[ext_resource type="Texture2D" uid="uid://bwxpic53sluul" path="res://assets/gfx/sword_slash.png" id="2_lwt2c"] +[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_v2p0x"] +[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_ul7bj"] +[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="5_whqew"] + +[sub_resource type="Animation" id="Animation_6bxep"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} + +[sub_resource type="Animation" id="Animation_p46b1"] +resource_name = "slash_anim" +length = 0.8 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.533333, 0.733333), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"] +_data = { +&"RESET": SubResource("Animation_6bxep"), +&"slash_anim": SubResource("Animation_p46b1") +} + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"] +size = Vector2(12, 12) + +[node name="AxeSwing" type="Node2D" unique_id=1568208090] +z_index = 10 +y_sort_enabled = true +script = ExtResource("1_xo3v0") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=461038063] +texture = ExtResource("2_lwt2c") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=691292922] +libraries/ = SubResource("AnimationLibrary_hj6i2") + +[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="." unique_id=515041905] +stream = ExtResource("3_v2p0x") +pitch_scale = 0.74 +autoplay = true + +[node name="DamageArea" type="Area2D" parent="." unique_id=985585639] +collision_layer = 0 +collision_mask = 75 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea" unique_id=805714782] +shape = SubResource("RectangleShape2D_3jdng") +debug_color = Color(0.7, 0, 0.18232, 0.42) + +[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=1016195072] +stream = ExtResource("4_ul7bj") +volume_db = -4.0 +pitch_scale = 1.3 +max_polyphony = 4 + +[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="." unique_id=1548143473] +stream = ExtResource("5_whqew") +volume_db = -5.622 +pitch_scale = 1.43 +max_polyphony = 4 + +[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"] +[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"] diff --git a/src/scenes/attack_spear_thrust.tscn b/src/scenes/attack_spear_thrust.tscn new file mode 100644 index 0000000..c928081 --- /dev/null +++ b/src/scenes/attack_spear_thrust.tscn @@ -0,0 +1,86 @@ +[gd_scene format=3 uid="uid://b3my31y2ljai1"] + +[ext_resource type="Script" uid="uid://ddprn0wrasavr" path="res://scripts/attack_spear_thrust.gd" id="1_psi1x"] +[ext_resource type="Texture2D" uid="uid://bwxpic53sluul" path="res://assets/gfx/sword_slash.png" id="2_rh1o6"] +[ext_resource type="AudioStream" uid="uid://4vulahdsj4i2" path="res://assets/audio/sfx/swoosh/throw_01.wav.mp3" id="3_j7ui3"] +[ext_resource type="AudioStream" uid="uid://uerx5rib87a6" path="res://assets/audio/sfx/weapons/bone_hit_wall_01.wav.mp3" id="4_cijfq"] +[ext_resource type="AudioStream" uid="uid://dc7nt8gnjt5u5" path="res://assets/audio/sfx/weapons/melee_attack_12.wav.mp3" id="5_h4gub"] + +[sub_resource type="Animation" id="Animation_6bxep"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} + +[sub_resource type="Animation" id="Animation_p46b1"] +resource_name = "slash_anim" +length = 0.8 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Sprite2D:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.533333, 0.733333), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_hj6i2"] +_data = { +&"RESET": SubResource("Animation_6bxep"), +&"slash_anim": SubResource("Animation_p46b1") +} + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_3jdng"] +size = Vector2(12, 12) + +[node name="SpearThrust" type="Node2D" unique_id=785354531] +z_index = 10 +y_sort_enabled = true +script = ExtResource("1_psi1x") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1051719548] +texture = ExtResource("2_rh1o6") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=907474922] +libraries/ = SubResource("AnimationLibrary_hj6i2") + +[node name="AttackSwosh" type="AudioStreamPlayer2D" parent="." unique_id=1397921535] +stream = ExtResource("3_j7ui3") +pitch_scale = 1.4 +autoplay = true + +[node name="DamageArea" type="Area2D" parent="." unique_id=1687133888] +collision_layer = 0 +collision_mask = 75 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="DamageArea" unique_id=376828642] +shape = SubResource("RectangleShape2D_3jdng") +debug_color = Color(0.7, 0, 0.18232, 0.42) + +[node name="MeleeImpactWall" type="AudioStreamPlayer2D" parent="." unique_id=651866255] +stream = ExtResource("4_cijfq") +volume_db = -4.0 +pitch_scale = 1.3 +max_polyphony = 4 + +[node name="MeleeImpact" type="AudioStreamPlayer2D" parent="." unique_id=35588340] +stream = ExtResource("5_h4gub") +volume_db = -5.622 +pitch_scale = 1.43 +max_polyphony = 4 + +[connection signal="area_entered" from="DamageArea" to="." method="_on_damage_area_area_entered"] +[connection signal="body_entered" from="DamageArea" to="." method="_on_damage_area_body_entered"] diff --git a/src/scripts/attack_arrow.gd b/src/scripts/attack_arrow.gd new file mode 100644 index 0000000..56e2848 --- /dev/null +++ b/src/scripts/attack_arrow.gd @@ -0,0 +1,169 @@ +extends CharacterBody2D + +var speed = 300 +var direction = Vector2.ZERO +var stick_duration = 3.0 # How long the arrow stays stuck to walls +var is_stuck = false +var stick_timer = 0.0 + +var initiated_by: Node2D = null + +@onready var arrow_area = $ArrowArea # Assuming you have an Area2D node named ArrowArea +@onready var shadow = $Shadow # Assuming you have a Shadow node under the CharacterBody2D + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + arrow_area.set_deferred("monitoring", true) + #arrow_area.body_entered.connect(_on_body_entered) + $SfxArrowFire.play() + call_deferred("_initialize_arrow") + +func _initialize_arrow() -> void: + var angle = direction.angle() + self.rotation = angle - PI / 2 # Adjust for sprite orientation + # Set initial rotation based on direction + velocity = direction * speed # Set initial velocity to move the arrow + + # Calculate the offset for the shadow position, which should be below the arrow + var shadow_offset = Vector2(0, 4) # Adjust the 16 to how far you want the shadow from the arrow (this is just an example) + + # Apply the rotation of the arrow to the shadow offset + shadow.position += shadow_offset.rotated(-self.rotation) + if abs(direction.x) == 1: + shadow.scale.x = 0.26 + shadow.scale.y = 0.062 + + elif abs(direction.x) > 0: + shadow.scale.x = 0.18 + shadow.scale.y = 0.08 + else: + shadow.scale.x = 0.1 + shadow.scale.y = 0.1 + + # Calculate the shadow's scale based on the velocity or direction of the arrow + #var velocity_magnitude = velocity.length() + + # Scale more in the horizontal direction if moving diagonally or horizontally + #var scale_factor = 0.28 + abs(velocity.x) / velocity_magnitude # Adjust the factor to your preference + + # Apply the scaling to the shadow + shadow.rotation = -(angle - PI / 2) + +func shoot(shoot_direction: Vector2, start_pos: Vector2) -> void: + direction = shoot_direction.normalized() + global_position = start_pos + #position = start_pos + +# Called every frame. 'delta' is the e lapsed time since the previous frame. +func _process(delta: float) -> void: + if is_stuck: + # Handle fade out here if it's stuck + stick_timer += delta + if stick_timer >= stick_duration: + # Start fading out after it sticks + modulate.a = max(0, 1 - (stick_timer - stick_duration) / 1.0) # Fade out over 1 second + if stick_timer >= stick_duration + 1.0: # Extra second for fade out + queue_free() # Remove the arrow after fade out + move_and_slide() + +func _physics_process(_delta: float) -> void: + # If the arrow is stuck, stop it from moving + if is_stuck: + velocity = Vector2.ZERO # Stop movement + # Optional: disable further physics interaction by setting linear_velocity + # move_and_slide(Vector2.ZERO) # You can also use this to stop the character + +func play_impact(): + $SfxImpactSound.play() + +# Called when the arrow hits a wall or another object +func _on_body_entered(body: Node) -> void: + if not is_stuck: + if body == initiated_by: + return + if body is CharacterBody2D and body.stats.is_invulnerable == false and body.stats.hp > 0: # hit an enemy + #if body is CharacterBody2D and body.collision_layer & (1 << 8) and body.taking_damage_timer <= 0 and body.stats.hp > 0: # Check if body is enemy (layer 9) + + # Stop the arrow + velocity = Vector2.ZERO + is_stuck = true + stick_timer = 0.0 + arrow_area.set_deferred("monitoring", false) + # Calculate the collision point - move arrow slightly back from its direction + var collision_normal = -direction # Opposite of arrow's direction + var offset_distance = 8 # Adjust this value based on your collision shape sizes + var stick_position = global_position + (collision_normal * offset_distance) + + # Make arrow a child of the enemy to stick to it + var global_rot = global_rotation + get_parent().call_deferred("remove_child", self) + body.call_deferred("add_child", self) + self.set_deferred("global_position", stick_position) + self.set_deferred("global_rotation", global_rot) + #global_rotation = global_rot + body.call_deferred("take_damage", self, initiated_by) + self.call_deferred("play_impact") # need to play the sound on the next frame, because else it cuts it. + + else: + $SfxImpactWall.play() + # Stop the arrow + velocity = Vector2.ZERO + is_stuck = true + stick_timer = 0.0 + arrow_area.set_deferred("monitoring", false) + # You can optionally stick the arrow at the collision point if you want: + # position = body.position # Uncomment this if you want to "stick" it at the collision point + # Additional logic for handling interaction with walls or other objects + + +func _on_arrow_area_area_entered(area: Area2D) -> void: + if not is_stuck: + if area.get_parent() == initiated_by: + return + if area.get_parent() is CharacterBody2D and area.get_parent().stats.is_invulnerable == false and area.get_parent().stats.hp > 0: # hit an enemy + #if body is CharacterBody2D and body.collision_layer & (1 << 8) and body.taking_damage_timer <= 0 and body.stats.hp > 0: # Check if body is enemy (layer 9) + + # Stop the arrow + velocity = Vector2.ZERO + is_stuck = true + stick_timer = 0.0 + arrow_area.set_deferred("monitoring", false) + # Calculate the collision point - move arrow slightly back from its direction + var collision_normal = -direction # Opposite of arrow's direction + var offset_distance = 8 # Adjust this value based on your collision shape sizes + var stick_position = global_position + (collision_normal * offset_distance) + + # Make arrow a child of the enemy to stick to it + var global_rot = global_rotation + get_parent().call_deferred("remove_child", self) + area.get_parent().call_deferred("add_child", self) + self.set_deferred("global_position", stick_position) + self.set_deferred("global_rotation", global_rot) + #global_rotation = global_rot + area.get_parent().call_deferred("take_damage", self, initiated_by) + self.call_deferred("play_impact") # need to play the sound on the next frame, because else it cuts it. + + else: + $SfxImpactWall.play() + # Stop the arrow + velocity = Vector2.ZERO + is_stuck = true + stick_timer = 0.0 + arrow_area.set_deferred("monitoring", false) + # You can optionally stick the arrow at the collision point if you want: + # position = body.position # Uncomment this if you want to "stick" it at the collision point + # Additional logic for handling interaction with walls or other objects + pass # Replace with function body. + + +func _on_arrow_area_body_entered(body: Node2D) -> void: + if not is_stuck: + if body == initiated_by: + return + $SfxImpactWall.play() + # Stop the arrow + velocity = Vector2.ZERO + is_stuck = true + stick_timer = 0.0 + arrow_area.set_deferred("monitoring", false) + pass # Replace with function body. diff --git a/src/scripts/attack_arrow.gd.uid b/src/scripts/attack_arrow.gd.uid new file mode 100644 index 0000000..38767de --- /dev/null +++ b/src/scripts/attack_arrow.gd.uid @@ -0,0 +1 @@ +uid://dqbctups3eri6 diff --git a/src/scripts/attack_axe_swing.gd b/src/scripts/attack_axe_swing.gd new file mode 100644 index 0000000..f8c4caf --- /dev/null +++ b/src/scripts/attack_axe_swing.gd @@ -0,0 +1,64 @@ +extends Node2D + +var direction := Vector2.ZERO # Default direction +var fade_delay := 0.14 # When to start fading (mid-move) +var move_duration := 0.2 # Slash exists for 0.3 seconds +var fade_duration := 0.06 # Time to fade out +var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite +var slash_amount = 8 +var initiated_by: Node2D = null + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + call_deferred("_initialize_swing") + pass # Replace with function body. + +func _initialize_swing(): + var tween = create_tween() + var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction + tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration + tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down + tween.tween_property(self, "global_position", move_target, move_duration) + ' + # Create stretch tween (grow and shrink slightly) + var stretch_tween = create_tween() + stretch_tween.set_trans(Tween.TRANS_CUBIC) + stretch_tween.set_ease(Tween.EASE_OUT) + stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal + stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2) + ' + + # Wait until mid-move to start fade + await get_tree().create_timer(fade_delay).timeout + + # Start fade-out effect + var fade_tween = create_tween() + fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent + await fade_tween.finished + queue_free() + pass + +func _on_damage_area_body_entered(body: Node2D) -> void: + if body.get_parent() == initiated_by or body == initiated_by: + return + if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy + $MeleeImpact.play() + body.take_damage(self, initiated_by) + pass + else: + $MeleeImpactWall.play() + pass + pass # Replace with function body. + + +func _on_damage_area_area_entered(body: Area2D) -> void: + if body.get_parent() == initiated_by: + return + if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy + $MeleeImpact.play() + body.get_parent().take_damage(self, initiated_by) + pass + else: + $MeleeImpactWall.play() + pass + pass # Replace with function body. diff --git a/src/scripts/attack_axe_swing.gd.uid b/src/scripts/attack_axe_swing.gd.uid new file mode 100644 index 0000000..5f72380 --- /dev/null +++ b/src/scripts/attack_axe_swing.gd.uid @@ -0,0 +1 @@ +uid://bqrtsr3mjvv3j diff --git a/src/scripts/attack_spear_thrust.gd b/src/scripts/attack_spear_thrust.gd new file mode 100644 index 0000000..0f90fda --- /dev/null +++ b/src/scripts/attack_spear_thrust.gd @@ -0,0 +1,64 @@ +extends Node2D + +var direction := Vector2.ZERO # Default direction +var fade_delay := 0.14 # When to start fading (mid-move) +var move_duration := 0.2 # Slash exists for 0.3 seconds +var fade_duration := 0.06 # Time to fade out +var stretch_amount := Vector2(1, 1.4) # How much to stretch the sprite +var slash_amount = 8 +var initiated_by: Node2D = null + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + call_deferred("_initialize_thrust") + pass # Replace with function body. + +func _initialize_thrust(): + var tween = create_tween() + var move_target = global_position + (direction.normalized() * slash_amount) # Moves in given direction + tween.set_trans(Tween.TRANS_CUBIC) # Smooth acceleration & deceleration + tween.set_ease(Tween.EASE_OUT) # Fast start, then slows down + tween.tween_property(self, "global_position", move_target, move_duration) + ' + # Create stretch tween (grow and shrink slightly) + var stretch_tween = create_tween() + stretch_tween.set_trans(Tween.TRANS_CUBIC) + stretch_tween.set_ease(Tween.EASE_OUT) + stretch_tween.tween_property($Sprite2D, "scale", Vector2.ONE, move_duration / 2) # start normal + stretch_tween.tween_property($Sprite2D, "scale", stretch_amount, move_duration / 2) + ' + + # Wait until mid-move to start fade + await get_tree().create_timer(fade_delay).timeout + + # Start fade-out effect + var fade_tween = create_tween() + fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, fade_duration) # Fade to transparent + await fade_tween.finished + queue_free() + pass + +func _on_damage_area_body_entered(body: Node2D) -> void: + if body.get_parent() == initiated_by or body == initiated_by: + return + if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy + $MeleeImpact.play() + body.take_damage(self, initiated_by) + pass + else: + $MeleeImpactWall.play() + pass + pass # Replace with function body. + + +func _on_damage_area_area_entered(body: Area2D) -> void: + if body.get_parent() == initiated_by: + return + if body.get_parent() is CharacterBody2D and body.get_parent().stats.is_invulnerable == false and body.get_parent().stats.hp > 0: # hit an enemy + $MeleeImpact.play() + body.get_parent().take_damage(self, initiated_by) + pass + else: + $MeleeImpactWall.play() + pass + pass # Replace with function body. diff --git a/src/scripts/attack_spear_thrust.gd.uid b/src/scripts/attack_spear_thrust.gd.uid new file mode 100644 index 0000000..c75d3cb --- /dev/null +++ b/src/scripts/attack_spear_thrust.gd.uid @@ -0,0 +1 @@ +uid://ddprn0wrasavr diff --git a/src/scripts/dungeon_generator.gd b/src/scripts/dungeon_generator.gd index 1ba3f81..55cd6e8 100644 --- a/src/scripts/dungeon_generator.gd +++ b/src/scripts/dungeon_generator.gd @@ -1266,7 +1266,7 @@ func _place_enemies_in_room(room: Dictionary, grid: Array, map_size: Vector2i, r # Randomize stats (similar to player but weaker) # Base stats vary by enemy type - var max_health = rng.randf_range(30.0, 60.0) + var max_health = rng.randf_range(18.0, 35.0) # Reduced from 30.0-60.0 for better balance var move_speed: float var damage = rng.randf_range(5.0, 15.0) diff --git a/src/scripts/enemy_base.gd b/src/scripts/enemy_base.gd index 0331791..1c99189 100644 --- a/src/scripts/enemy_base.gd +++ b/src/scripts/enemy_base.gd @@ -4,10 +4,12 @@ extends CharacterBody2D @export var max_health: float = 50.0 @export var move_speed: float = 80.0 -@export var damage: float = 10.0 +@export var damage: float = 10.0 # Legacy - use character_stats.damage instead @export var attack_cooldown: float = 1.0 +@export var exp_reward: float = 10.0 # EXP granted when this enemy is defeated var current_health: float = 50.0 +var character_stats: CharacterStats # RPG stats system (same as players) var is_dead: bool = false var target_player: Node = null var attack_timer: float = 0.0 @@ -35,6 +37,9 @@ var anim_speed: float = 0.15 # Seconds per frame @onready var collision_shape = get_node_or_null("CollisionShape2D") func _ready(): + # Initialize CharacterStats for RPG system + _initialize_character_stats() + current_health = max_health add_to_group("enemy") @@ -51,6 +56,27 @@ func _ready(): # Walls are on layer 7 (bit 6 = 64), not layer 4! collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64) +# Initialize CharacterStats for this enemy +# Override in subclasses to set specific baseStats +func _initialize_character_stats(): + character_stats = CharacterStats.new() + character_stats.character_type = "enemy" + character_stats.character_name = name + # Default stats - override in subclasses + character_stats.baseStats.str = 10 + character_stats.baseStats.dex = 10 + character_stats.baseStats.int = 10 + character_stats.baseStats.end = 10 + character_stats.baseStats.wis = 10 + character_stats.baseStats.cha = 10 + character_stats.baseStats.lck = 10 + # Initialize hp and mp + character_stats.hp = character_stats.maxhp + character_stats.mp = character_stats.maxmp + # Sync max_health and current_health from character_stats (for backwards compatibility) + max_health = character_stats.maxhp + current_health = character_stats.hp + func _physics_process(delta): if is_dead: # Even when dead, allow knockback to continue briefly @@ -259,7 +285,7 @@ func _find_nearest_player_to_position(pos: Vector2, max_range: float = 100.0) -> return nearest -func take_damage(amount: float, from_position: Vector2): +func take_damage(amount: float, from_position: Vector2, is_critical: bool = false): # Only process damage on server/authority if not is_multiplayer_authority(): return @@ -273,8 +299,41 @@ func take_damage(amount: float, from_position: Vector2): if nearest_player: killer_player = nearest_player # Update killer to the most recent attacker - current_health -= amount - print(name, " took ", amount, " damage! Health: ", current_health) + # Check for dodge chance (based on DEX) - same as players + var _was_dodged = false + if character_stats: + var dodge_roll = randf() + var dodge_chance = character_stats.dodge_chance + if dodge_roll < dodge_chance: + _was_dodged = true + print(name, " DODGED the attack! (DEX: ", character_stats.baseStats.dex + character_stats.get_pass("dex"), ", dodge chance: ", dodge_chance * 100.0, "%)") + # Show "DODGED" text + _show_damage_number(0.0, from_position, false, false, true) # is_dodged = true + # Sync dodge visual to clients + if multiplayer.has_multiplayer_peer() and is_inside_tree(): + var enemy_name = name + var enemy_index = get_meta("enemy_index") if has_meta("enemy_index") else -1 + var game_world = get_tree().get_first_node_in_group("game_world") + if game_world and game_world.has_method("_sync_enemy_damage_visual"): + game_world._sync_enemy_damage_visual.rpc(enemy_name, enemy_index) + return # No damage taken, exit early + + # If not dodged, apply damage with DEF reduction + var actual_damage = amount + if character_stats: + # Calculate damage after DEF reduction (critical hits pierce 80% of DEF) + actual_damage = character_stats.calculate_damage(amount, false, is_critical) # false = not magical, is_critical = crit pierce + character_stats.modify_health(-actual_damage) + current_health = character_stats.hp + if character_stats.hp <= 0: + character_stats.no_health.emit() + var effective_def = character_stats.defense * (0.2 if is_critical else 1.0) + print(name, " took ", actual_damage, " damage (", amount, " base - ", effective_def, " DEF = ", actual_damage, ")! Health: ", current_health, "/", character_stats.maxhp) + else: + # Fallback for legacy (shouldn't happen if _initialize_character_stats is called) + current_health -= amount + actual_damage = amount + print(name, " took ", amount, " damage! Health: ", current_health, " (critical: ", is_critical, ")") # Calculate knockback direction (away from attacker) var knockback_direction = (global_position - from_position).normalized() @@ -287,10 +346,9 @@ func take_damage(amount: float, from_position: Vector2): # Flash red (even if dying, show the hit) _flash_damage() - # Show damage number (red, using dmg_numbers.png font) above enemy - # Only show if damage > 0 - if amount > 0: - _show_damage_number(amount, from_position) + # Show damage number (red/orange, using dmg_numbers.png font) above enemy + # Always show damage number, even if 0 + _show_damage_number(actual_damage, from_position, is_critical) # Sync damage visual to clients # Use game_world to route damage visual sync instead of direct RPC to avoid node path issues @@ -321,16 +379,14 @@ func take_damage(amount: float, from_position: Vector2): call_deferred("_notify_doors_enemy_died") @rpc("any_peer", "reliable") -func rpc_take_damage(amount: float, from_position: Vector2): +func rpc_take_damage(amount: float, from_position: Vector2, is_critical: bool = false): # RPC version - only process on server/authority if is_multiplayer_authority(): - take_damage(amount, from_position) + take_damage(amount, from_position, is_critical) -func _show_damage_number(amount: float, from_position: Vector2): - # Show damage number (red, using dmg_numbers.png font) above enemy - # Only show if damage > 0 - if amount <= 0: - return +func _show_damage_number(amount: float, from_position: Vector2, is_critical: bool = false, is_miss: bool = false, is_dodged: bool = false): + # Show damage number (red/orange for crits, cyan for dodge, gray for miss, using dmg_numbers.png font) above enemy + # Show even if amount is 0 for MISS/DODGED var damage_number_scene = preload("res://scenes/damage_number.tscn") if not damage_number_scene: @@ -340,9 +396,16 @@ func _show_damage_number(amount: float, from_position: Vector2): if not damage_label: return - # Set damage text and red color - damage_label.label = str(int(amount)) - damage_label.color = Color.RED + # Set text and color based on type + if is_dodged: + damage_label.label = "DODGED" + damage_label.color = Color.CYAN + elif is_miss: + damage_label.label = "MISS" + damage_label.color = Color.GRAY + else: + damage_label.label = str(int(amount)) + damage_label.color = Color.ORANGE if is_critical else Color.RED # Calculate direction from attacker (slight upward variation) var direction_from_attacker = (global_position - from_position).normalized() @@ -418,11 +481,16 @@ func _die(): is_dead = true print(name, " died!") - # Credit kill to the player who dealt the fatal damage + # Credit kill and grant EXP to the player who dealt the fatal damage if killer_player and is_instance_valid(killer_player) and killer_player.character_stats: killer_player.character_stats.kills += 1 print(name, " kill credited to ", killer_player.name, " (total kills: ", killer_player.character_stats.kills, ")") + # Grant EXP to the killer + if exp_reward > 0: + killer_player.character_stats.add_xp(exp_reward) + print(name, " granted ", exp_reward, " EXP to ", killer_player.name) + # Sync kill update to client if this player belongs to a client # Only sync if we're on the server and the killer is a client's player if multiplayer.has_multiplayer_peer() and is_multiplayer_authority(): @@ -475,16 +543,21 @@ func _spawn_loot(): var loot_chance = randf() print(name, " loot chance roll: ", loot_chance, " (need > 0.3)") if loot_chance > 0.3: - # Random loot type + # Decide what to drop: 30% coin, 30% food, 40% item + var drop_roll = randf() var loot_type = 0 + var drop_item = false - # 50% chance for coin - if randf() < 0.5: + if drop_roll < 0.3: + # 30% chance for coin loot_type = 0 # COIN - # 50% chance for food item - else: + elif drop_roll < 0.6: + # 30% chance for food item var food_types = [1, 2, 3] # APPLE, BANANA, CHERRY loot_type = food_types[randi() % food_types.size()] + else: + # 40% chance for Item instance + drop_item = true # Generate random velocity values (same on all clients) var random_angle = randf() * PI * 2 @@ -500,10 +573,20 @@ func _spawn_loot(): if game_world and game_world.has_method("_find_nearby_safe_spawn_position"): safe_spawn_pos = game_world._find_nearby_safe_spawn_position(global_position, 64.0) - # Spawn on server - var loot = loot_scene.instantiate() var entities_node = get_parent() - if entities_node: + if not entities_node: + print(name, " ERROR: entities_node is null! Cannot spawn loot!") + return + + if drop_item: + # Spawn Item instance as loot + var item = ItemDatabase.get_random_enemy_drop() + if item: + ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world) + print(name, " ✓ dropped item: ", item.item_name, " at ", safe_spawn_pos) + else: + # Spawn regular loot (coin or food) + var loot = loot_scene.instantiate() entities_node.add_child(loot) loot.global_position = safe_spawn_pos loot.loot_type = loot_type @@ -519,8 +602,7 @@ func _spawn_loot(): # Reuse game_world variable from above if game_world: # Generate unique loot ID - if not "loot_id_counter" in game_world: - game_world.loot_id_counter = 0 + # loot_id_counter is declared as a variable in game_world.gd, so it always exists var loot_id = game_world.loot_id_counter game_world.loot_id_counter += 1 # Store loot ID on server loot instance @@ -530,8 +612,6 @@ func _spawn_loot(): print(name, " ✓ synced loot spawn to clients") else: print(name, " ERROR: game_world not found for loot sync!") - else: - print(name, " ERROR: entities_node is null! Cannot spawn loot!") else: print(name, " loot chance failed (", loot_chance, " <= 0.3), no loot dropped") diff --git a/src/scripts/enemy_bat.gd b/src/scripts/enemy_bat.gd index e047b05..3508d70 100644 --- a/src/scripts/enemy_bat.gd +++ b/src/scripts/enemy_bat.gd @@ -15,16 +15,34 @@ var fly_height: float = 8.0 # Z position when flying func _ready(): super._ready() - max_health = 30.0 + max_health = 18.0 # Reduced from 30.0 for better balance current_health = max_health move_speed = 40.0 # Reasonable speed for bats damage = 5.0 + exp_reward = 6.0 # Bats give low-moderate EXP state_timer = idle_duration # CRITICAL: Ensure collision mask is set correctly (walls are on layer 7 = bit 6 = 64) collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64) +# Override to set weak stats for bats +func _initialize_character_stats(): + super._initialize_character_stats() + # Bats are weak enemies - very low END for low DEF + character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2) + character_stats.baseStats.str = 5 # Low STR = low damage + character_stats.baseStats.dex = 10 # Higher DEX (bats are agile) + character_stats.baseStats.int = 5 + character_stats.baseStats.wis = 5 + character_stats.baseStats.lck = 5 + # Re-initialize hp and mp with new stats + character_stats.hp = character_stats.maxhp + character_stats.mp = character_stats.maxmp + # Sync max_health from character_stats + max_health = character_stats.maxhp + current_health = max_health + func _physics_process(delta): # Always update animation (even when dead, and on clients) _update_animation(delta) diff --git a/src/scripts/enemy_humanoid.gd b/src/scripts/enemy_humanoid.gd index a9ad044..aac1aa1 100644 --- a/src/scripts/enemy_humanoid.gd +++ b/src/scripts/enemy_humanoid.gd @@ -740,40 +740,47 @@ func _setup_stats(): # Set stats based on type match humanoid_type: HumanoidType.CYCLOPS: - max_health = 100.0 + max_health = 55.0 # Reduced from 100.0 for better balance move_speed = 40.0 damage = 15.0 dex = 8 # Slow, strong + exp_reward = 25.0 # Strong enemies give more EXP HumanoidType.DEMON: - max_health = 80.0 + max_health = 45.0 # Reduced from 80.0 for better balance move_speed = 45.0 damage = 12.0 dex = 12 # Medium speed + exp_reward = 20.0 HumanoidType.HUMANOID: - max_health = 60.0 + max_health = 35.0 # Reduced from 60.0 for better balance move_speed = 50.0 damage = 10.0 dex = 15 # Fast, agile + exp_reward = 15.0 HumanoidType.NIGHTELF: - max_health = 70.0 + max_health = 40.0 # Reduced from 70.0 for better balance move_speed = 55.0 damage = 11.0 dex = 18 # Very fast + exp_reward = 18.0 HumanoidType.GOBLIN: - max_health = 40.0 + max_health = 25.0 # Reduced from 40.0 for better balance move_speed = 60.0 damage = 8.0 dex = 20 # Very fast, weak + exp_reward = 10.0 # Weak enemies give less EXP HumanoidType.ORC: - max_health = 90.0 + max_health = 50.0 # Reduced from 90.0 for better balance move_speed = 42.0 damage = 14.0 dex = 7 # Slow, very strong + exp_reward = 22.0 HumanoidType.SKELETON: - max_health = 50.0 + max_health = 30.0 # Reduced from 50.0 for better balance move_speed = 48.0 damage = 9.0 dex = 14 # Medium-fast + exp_reward = 12.0 current_health = max_health diff --git a/src/scripts/enemy_rat.gd b/src/scripts/enemy_rat.gd index fafcec0..4f981af 100644 --- a/src/scripts/enemy_rat.gd +++ b/src/scripts/enemy_rat.gd @@ -13,16 +13,34 @@ var detection_range: float = 150.0 func _ready(): super._ready() - max_health = 25.0 + max_health = 15.0 # Reduced from 25.0 for better balance current_health = max_health move_speed = 40.0 # Much slower damage = 8.0 + exp_reward = 5.0 # Rats give low EXP state_timer = idle_duration # CRITICAL: Ensure collision mask is set correctly (walls are on layer 7 = bit 6 = 64) collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64) +# Override to set weak stats for rats +func _initialize_character_stats(): + super._initialize_character_stats() + # Rats are weak enemies - very low END for low DEF + character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2) + character_stats.baseStats.str = 7 # Low STR = low damage + character_stats.baseStats.dex = 8 # Slightly higher DEX (rats are fast) + character_stats.baseStats.int = 5 + character_stats.baseStats.wis = 5 + character_stats.baseStats.lck = 5 + # Re-initialize hp and mp with new stats + character_stats.hp = character_stats.maxhp + character_stats.mp = character_stats.maxmp + # Sync max_health from character_stats + max_health = character_stats.maxhp + current_health = max_health + func _ai_behavior(delta): # Update state timer state_timer -= delta diff --git a/src/scripts/enemy_slime.gd b/src/scripts/enemy_slime.gd index 6eb7ea1..7b2fe5f 100644 --- a/src/scripts/enemy_slime.gd +++ b/src/scripts/enemy_slime.gd @@ -55,10 +55,11 @@ var time_since_last_frame = 0.0 func _ready(): super._ready() - max_health = 20.0 + max_health = 12.0 # Reduced from 20.0 for better balance current_health = max_health move_speed = 20.0 # Very slow (reduced from 35) damage = 6.0 + exp_reward = 8.0 # Slimes give moderate EXP state_timer = idle_duration @@ -70,6 +71,23 @@ func _ready(): if collision_shape and collision_shape.shape: collision_shape.shape.radius = 6.0 # 12x12 effective size +# Override to set weak stats for slimes +func _initialize_character_stats(): + super._initialize_character_stats() + # Slimes are weak enemies - very low END for low DEF + character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2) + character_stats.baseStats.str = 6 # Low STR = low damage + character_stats.baseStats.dex = 5 # Low DEX + character_stats.baseStats.int = 5 + character_stats.baseStats.wis = 5 + character_stats.baseStats.lck = 5 + # Re-initialize hp and mp with new stats + character_stats.hp = character_stats.maxhp + character_stats.mp = character_stats.maxmp + # Sync max_health from character_stats + max_health = character_stats.maxhp + current_health = max_health + func _physics_process(delta): # Always update animation (even when dead, and on clients) _update_animation(delta) diff --git a/src/scripts/game_world.gd b/src/scripts/game_world.gd index 361833b..0360c37 100644 --- a/src/scripts/game_world.gd +++ b/src/scripts/game_world.gd @@ -39,6 +39,9 @@ func _ready(): # Create chat UI _create_chat_ui() + # Create inventory UI + _create_inventory_ui() + # Generate dungeon on host if multiplayer.is_server() or not multiplayer.has_multiplayer_peer(): print("GameWorld: _ready() - Will generate dungeon (is_server: ", multiplayer.is_server(), ", has_peer: ", multiplayer.has_multiplayer_peer(), ")") @@ -275,6 +278,37 @@ func _sync_loot_spawn(spawn_position: Vector2, loot_type: int, initial_velocity: loot.is_airborne = true print("Client spawned loot: ", loot_type, " at ", spawn_position, " authority: ", loot.get_multiplayer_authority()) +@rpc("authority", "reliable") +func _sync_item_loot_spawn(spawn_position: Vector2, item_data: Dictionary, initial_velocity: Vector2, initial_velocity_z: float, loot_id: int = -1): + # Clients spawn item loot when server tells them to + if not multiplayer.is_server(): + var loot_scene = preload("res://scenes/loot.tscn") + if not loot_scene: + return + + var loot = loot_scene.instantiate() + var entities_node = get_node_or_null("Entities") + if entities_node: + # Create Item instance from data + var item = Item.new(item_data) + + # Set multiplayer authority to server (peer 1) so RPCs work + if multiplayer.has_multiplayer_peer(): + loot.set_multiplayer_authority(1) + # Store unique loot ID for identification + if loot_id >= 0: + loot.set_meta("loot_id", loot_id) + entities_node.add_child(loot) + loot.global_position = spawn_position + loot.loot_type = loot.LootType.ITEM + loot.item = item # Set the item instance + # Set initial velocity before _ready() processes + loot.velocity = initial_velocity + loot.velocity_z = initial_velocity_z + loot.velocity_set_by_spawner = true + loot.is_airborne = true + print("Client spawned item loot: ", item.item_name, " at ", spawn_position, " authority: ", loot.get_multiplayer_authority()) + @rpc("authority", "unreliable") func _sync_enemy_position(enemy_name: String, enemy_index: int, pos: Vector2, vel: Vector2, z_pos: float, dir: int, frame: int, anim: String, frame_num: int, state_value: int): # Clients receive enemy position updates from server @@ -2056,6 +2090,19 @@ func _create_chat_ui(): else: push_error("GameWorld: Failed to instantiate chat_ui.tscn!") +func _create_inventory_ui(): + # Create inventory UI programmatically (using CanvasLayer) + var inventory_ui_script = load("res://scripts/inventory_ui.gd") + if not inventory_ui_script: + push_error("GameWorld: Could not load inventory_ui.gd script!") + return + + var inventory_ui = CanvasLayer.new() + inventory_ui.set_script(inventory_ui_script) + inventory_ui.name = "InventoryUI" + add_child(inventory_ui) + print("GameWorld: Inventory UI created and added to scene tree") + func _send_player_join_message(peer_id: int, player_info: Dictionary): # Send a chat message when a player joins # Only send from server to avoid duplicate messages diff --git a/src/scripts/inspiration_scripts/character_stats.gd b/src/scripts/inspiration_scripts/character_stats.gd index 32523bb..2918701 100644 --- a/src/scripts/inspiration_scripts/character_stats.gd +++ b/src/scripts/inspiration_scripts/character_stats.gd @@ -4,6 +4,7 @@ extends Resource signal health_changed(new_health: float, max_health: float) signal mana_changed(new_mana: float, max_mana: float) signal level_changed(new_level: int) +signal level_up_stats(stats_increased: Array) # Emitted when leveling up, contains array of stat names that were increased signal xp_changed(new_xp: float, xp_to_next: float) signal no_health @@ -152,11 +153,13 @@ var maxmp: float: var damage: float: get: - return (baseStats.str + get_pass("str")) * 0.2 + get_pass("dmg") + # Increased damage scaling: 0.25 per STR point (was 0.2) - makes players stronger + return (baseStats.str + get_pass("str")) * 0.25 + get_pass("dmg") var defense: float: get: - return ((baseStats.end + get_pass("end")) * 0.3) + get_pass("def") + # Reduced DEF scaling: 0.2 per END point (was 0.3) to make it less overpowered for low-level enemies + return ((baseStats.end + get_pass("end")) * 0.2) + get_pass("def") var spell_amp: float: get: @@ -178,9 +181,23 @@ var crit_chance: float: get: return (baseStats.lck + get_pass("lck")) * 1.2 +var dodge_chance: float: + get: + # Dodge chance based on DEX (very low % per point, as per user request) + # Each point of DEX gives 0.5% dodge chance (so 20 DEX = 10% dodge) + return (baseStats.dex + get_pass("dex")) * 0.005 + +var hit_chance: float: + get: + # Hit chance based on DEX (higher DEX = better accuracy) + # Base hit chance is 95%, each DEX point adds 0.3% (so ~17 DEX = 100% base hit chance) + # Formula: 95% base + (DEX * 0.3%) + return 0.95 + ((baseStats.dex + get_pass("dex")) * 0.003) + var xp_to_next_level: float: get: - return pow(level * 10, 1.5) + # Scale EXP requirements more aggressively - gets harder to level as you go + return pow(level * 15, 1.6) func _init() -> void: hp = maxhp @@ -193,25 +210,41 @@ func add_xp(amount: float) -> void: while xp >= xp_to_next_level: level_up() -# instead of automatically update all stats, maybe let the player choose which stats to level up. +# Level up - randomly increases 2-3 stats instead of all func level_up() -> void: level += 1 xp -= xp_to_next_level - # Increase stats - baseStats.str += 1 - baseStats.dex += 1 - baseStats.int += 1 - baseStats.end += 1 - baseStats.wis += 1 - baseStats.cha += 1 - baseStats.lck += 1 + # Randomly select 2-3 stats to increase (random 2 or 3) + var num_stats_to_increase = randi_range(2, 3) + + # All available stats (excluding cha for now as per user request) + var available_stats = ["str", "dex", "int", "end", "wis", "lck"] + + # Shuffle and pick random stats + var stats_to_increase = [] + var shuffled_stats = available_stats.duplicate() + shuffled_stats.shuffle() + + for i in range(min(num_stats_to_increase, shuffled_stats.size())): + stats_to_increase.append(shuffled_stats[i]) + + # Increase selected stats + for stat_name in stats_to_increase: + baseStats[stat_name] += 1 + + # Store which stats were increased (for display) + var stats_increased = stats_to_increase # Restore health and mana on level up hp = maxhp mp = maxmp + # Emit level_changed signal level_changed.emit(level) + # Emit level_up_stats signal with which stats were increased + level_up_stats.emit(stats_increased) + health_changed.emit(hp, maxhp) mana_changed.emit(mp, maxmp) @@ -225,12 +258,25 @@ func modify_mana(amount: float) -> void: mana_changed.emit(mp, maxmp) character_changed.emit(self) -func calculate_damage(base_damage: float, is_magical: bool = false) -> float: - if is_magical: - return base_damage * (1 - (resistances.magic / 100.0)) - return base_damage * (1 - (defense / 100.0)) +func calculate_damage(base_damage: float, is_magical: bool = false, is_critical: bool = false) -> float: + # Apply defense reduction (DEF reduces damage by a flat amount, not percentage) + # Defense formula: flat reduction based on END and equipment DEF + # Critical hits pierce 80% of DEF (only 20% of DEF applies to crits) + var final_damage = base_damage + if not is_magical: + # Physical damage: reduce by defense value (flat reduction) + var effective_defense = defense + if is_critical: + # Critical hits pierce 80% of DEF (only 20% applies) + effective_defense = defense * 0.2 + final_damage = max(0.0, base_damage - effective_defense) + else: + # Magical damage: reduce by magic resistance percentage + final_damage = base_damage * (1 - (resistances.magic / 100.0)) + return final_damage func take_damage(amount: float, is_magical: bool = false) -> float: + # Calculate damage after DEF reduction var actual_damage = calculate_damage(amount, is_magical) modify_health(-actual_damage) if hp <= 0: @@ -419,6 +465,28 @@ func drop_equipment(iItem:Item): func add_item(iItem:Item): self.inventory.push_back(iItem) + + # Auto-equip if slot is empty (only for equippable items) + if iItem.item_type == Item.ItemType.Equippable and iItem.equipment_type != Item.EquipmentType.NONE: + var slot_key = "" + match iItem.equipment_type: + Item.EquipmentType.MAINHAND: + slot_key = "mainhand" + Item.EquipmentType.OFFHAND: + slot_key = "offhand" + Item.EquipmentType.HEADGEAR: + slot_key = "headgear" + Item.EquipmentType.ARMOUR: + slot_key = "armour" + Item.EquipmentType.BOOTS: + slot_key = "boots" + Item.EquipmentType.ACCESSORY: + slot_key = "accessory" + + # Auto-equip if slot is empty + if slot_key != "" and equipment[slot_key] == null: + equip_item(iItem) + emit_signal("character_changed", self) pass diff --git a/src/scripts/inspiration_scripts/inventory.gd b/src/scripts/inspiration_scripts/inventory.gd new file mode 100644 index 0000000..30c4f19 --- /dev/null +++ b/src/scripts/inspiration_scripts/inventory.gd @@ -0,0 +1,491 @@ +extends CanvasLayer + +var is_showing_inventory = true +var finished_tween = true + +var selectedItem:Item = null +var selectedEquipment:Item = null +var lastPressedItem:Item = null +var lastPressedType = "Item" + +var timerSelect:float = 0.0 + +func bindInventory(): + if GameManager.character_data.is_connected("character_changed", _charChanged): + GameManager.character_data.disconnect("character_changed", _charChanged) + GameManager.character_data.connect("character_changed", _charChanged) + pass + +func _ready() -> void: + + is_showing_inventory = false + var move_range = -119 + $ControlContainer/ControlStats.position.x += move_range + move_range = 80 + $ControlContainer/ControlInventory.position.x += move_range + move_range = 33 + $ControlContainer/ControlInfo.position.y += move_range + + $ControlContainer.mouse_filter = Control.MOUSE_FILTER_IGNORE + + + # Set up multiplayer peer + ' + GameManager.host(21212, false) + + GameManager.character_data.connect("character_changed", _charChanged) + + #$Player.position = Vector2(30, 30) + + var item = Item.new() + item.item_type = Item.ItemType.Equippable + item.equipment_type = Item.EquipmentType.ARMOUR + item.item_name = "Leather Armour" + item.description = "A nice leather armour" + item.spriteFrame = 12 + item.modifiers["def"] = 2 + item.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Tunic Body/BrownTunic.png" + GameManager.character_data.add_item(item) + + + var item2 = Item.new() + item2.item_type = Item.ItemType.Equippable + item2.equipment_type = Item.EquipmentType.HEADGEAR + item2.item_name = "Leather helm" + item2.description = "A nice leather helm" + item2.spriteFrame = 31 + item2.modifiers["def"] = 1 + item2.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Mage/MageHatRed.png" + item2.colorReplacements = [ + { "original": Color(255/255.0, 39/255.0, 44/255.0), "replace": Color(255/255.0,106/255.0,39/255.0)}, + { "original": Color(182/255.0, 0, 0), "replace": Color(182/255.0,106/255.0,0)}, + { "original": Color(118/255.0, 1/255.0, 0), "replace": Color(118/255.0,66/255.0,0)}, + { "original": Color(72/255.0, 0, 12/255.0), "replace": Color(72/255.0,34/255.0,0)} + ] + GameManager.character_data.add_item(item2) + + var item3 = Item.new() + item3.item_type = Item.ItemType.Equippable + item3.equipment_type = Item.EquipmentType.MAINHAND + item3.weapon_type = Item.WeaponType.SWORD + item3.item_name = "Dagger" + item3.description = "A sharp dagger" + item3.spriteFrame = 5*20 + 10 + item3.modifiers["dmg"] = 2 + GameManager.character_data.add_item(item3) + + var item4 = Item.new() + item4.item_type = Item.ItemType.Equippable + item4.equipment_type = Item.EquipmentType.MAINHAND + item4.weapon_type = Item.WeaponType.AXE + item4.item_name = "Hand Axe" + item4.description = "A sharp hand axe" + item4.spriteFrame = 5*20 + 11 + item4.modifiers["dmg"] = 4 + GameManager.character_data.add_item(item4) + + var item5 = Item.new() + item5.item_type = Item.ItemType.Equippable + item5.equipment_type = Item.EquipmentType.OFFHAND + item5.weapon_type = Item.WeaponType.AMMUNITION + item5.quantity = 13 + item5.can_have_multiple_of = true + item5.item_name = "Iron Arrow" + item5.description = "A sharp arrow made of iron and feathers from pelican birds" + item5.spriteFrame = 7*20 + 11 + item5.modifiers["dmg"] = 3 + GameManager.character_data.add_item(item5) + + var item6 = Item.new() + item6.item_type = Item.ItemType.Equippable + item6.equipment_type = Item.EquipmentType.MAINHAND + item6.weapon_type = Item.WeaponType.BOW + item6.item_name = "Wooden Bow" + item6.description = "A wooden bow made of elfish lembas trees" + item6.spriteFrame = 6*20 + 16 + item6.modifiers["dmg"] = 3 + GameManager.character_data.add_item(item6) + + var item7 = Item.new() + item7.item_type = Item.ItemType.Equippable + item7.equipment_type = Item.EquipmentType.BOOTS + item7.weapon_type = Item.WeaponType.NONE + item7.item_name = "Sandals" + item7.description = "A pair of shitty sandals" + item7.equipmentPath = "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesBrown.png" + item7.spriteFrame = 2*20 + 10 + item7.modifiers["def"] = 1 + GameManager.character_data.add_item(item7) + ' + + # clear default stuff + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = "" + + pass + +func _charChanged(iChar:CharacterStats): + # update all stats: + $ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer/LabelBaseStatsValue.text = str(iChar.level) + "\r\n" + \ + "\r\n" + \ + str(floori(iChar.hp)) + "/" + str(floori(iChar.maxhp)) + "\r\n" + \ + str(floori(iChar.mp)) + "/" + str(floori(iChar.maxmp)) + "\r\n" + \ + "\r\n" + \ + str(iChar.baseStats.str) + "\r\n" + \ + str(iChar.baseStats.dex) + "\r\n" + \ + str(iChar.baseStats.end) + "\r\n" + \ + str(iChar.baseStats.int) + "\r\n" + \ + str(iChar.baseStats.wis) + "\r\n" + \ + str(iChar.baseStats.lck) + + $ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer/LabelDerivedStatsValue.text = str(floori(iChar.xp)) + "/" + str(floori(iChar.xp_to_next_level)) + "\r\n" + \ + str(iChar.coin) + "\r\n" + \ + "\r\n" + \ + "\r\n" + \ + "\r\n" + \ + str(iChar.damage) + "\r\n" + \ + str(iChar.defense) + "\r\n" + \ + str(iChar.move_speed) + "\r\n" + \ + str(iChar.attack_speed) + "\r\n" + \ + str(iChar.sight) + "\r\n" + \ + str(iChar.spell_amp) + "\r\n" + \ + str(iChar.crit_chance) + "%" + + # read inventory and populate inventory + var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory + for child in vboxInvent.find_child("HBoxControl1").get_children(): + child.get_parent().remove_child(child) + child.propagate_call("queue_free", []) + for child in vboxInvent.find_child("HBoxControl2").get_children(): + child.get_parent().remove_child(child) + child.propagate_call("queue_free", []) + for child in vboxInvent.find_child("HBoxControl3").get_children(): + child.get_parent().remove_child(child) + child.propagate_call("queue_free", []) + + var selected_tex = preload("res://assets/gfx/ui/inventory_slot_kenny_white.png") + var styleBoxHover:StyleBox = StyleBoxTexture.new() + var styleBoxFocused:StyleBox = StyleBoxTexture.new() + var styleBoxPressed:StyleBox = StyleBoxTexture.new() + var styleBoxEmpty:StyleBox = StyleBoxEmpty.new() + styleBoxHover.texture = selected_tex + styleBoxFocused.texture = selected_tex + styleBoxPressed.texture = selected_tex + + var quantityFont:Font = load("res://assets/fonts/dmg_numbers.png") + for item:Item in iChar.inventory: + var btn:Button = Button.new() + btn.add_theme_stylebox_override("normal", styleBoxEmpty) + btn.add_theme_stylebox_override("hover", styleBoxHover) + btn.add_theme_stylebox_override("focus", styleBoxFocused) + btn.add_theme_stylebox_override("pressed", styleBoxPressed) + btn.custom_minimum_size = Vector2(24, 24) + btn.size = Vector2(24, 24) + + vboxInvent.find_child("HBoxControl1").add_child(btn) + var spr:Sprite2D = Sprite2D.new() + spr.texture = load(item.spritePath) + spr.hframes = item.spriteFrames.x + spr.vframes = item.spriteFrames.y + spr.frame = item.spriteFrame + spr.centered = false + spr.position = Vector2(4,4) + btn.add_child(spr) + + if item.can_have_multiple_of: + var lblQuantity:Label = Label.new() + lblQuantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + lblQuantity.size = Vector2(24, 24) + lblQuantity.custom_minimum_size = Vector2(0,0) + lblQuantity.position = Vector2(10, 2) + lblQuantity.text = str(item.quantity) + lblQuantity.add_theme_font_override("font", quantityFont) + lblQuantity.add_theme_font_size_override("font", 8) + lblQuantity.scale = Vector2(0.5, 0.5) + btn.add_child(lblQuantity) + pass + + btn.connect("pressed", _selectItem.bind(item, true)) + btn.connect("focus_entered", _selectItem.bind(item, false)) + + pass + # clear eq container... + var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer + for child in eqContainer.get_children(): + child.get_parent().remove_child(child) + child.propagate_call("queue_free", []) + '"mainhand": null, + "offhand": null, + "headgear": null, + "armour": null, + "boots": null, + "accessory": null' + addEq(iChar, "mainhand", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + addEq(iChar, "offhand", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + addEq(iChar, "headgear", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + addEq(iChar, "armour", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + addEq(iChar, "boots", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + addEq(iChar, "accessory", eqContainer, styleBoxEmpty, styleBoxHover, styleBoxFocused, styleBoxPressed, quantityFont) + + pass + +func addEq(iChar:CharacterStats, iPiece:String, eqContainer:Control, styleBoxEmpty: StyleBox, styleBoxHover: StyleBox, styleBoxFocused: StyleBox, styleBoxPressed: StyleBox, quantityFont: Font): + if iChar.equipment[iPiece] != null: + var btn:Button = Button.new() + btn.add_theme_stylebox_override("normal", styleBoxEmpty) + btn.add_theme_stylebox_override("hover", styleBoxHover) + btn.add_theme_stylebox_override("focus", styleBoxFocused) + btn.add_theme_stylebox_override("pressed", styleBoxPressed) + btn.custom_minimum_size = Vector2(24, 24) + btn.position = Vector2(1,9) + match iPiece: + "armour": + btn.position = Vector2(28, 9) + pass + "offhand": + btn.position = Vector2(55, 9) + pass + "headgear": + btn.position = Vector2(1, 36) + pass + "boots": + btn.position = Vector2(28, 36) + pass + "accessory": + btn.position = Vector2(55, 36) + pass + pass + + eqContainer.add_child(btn) + var spr:Sprite2D = Sprite2D.new() + spr.texture = load(iChar.equipment[iPiece].spritePath) + spr.hframes = iChar.equipment[iPiece].spriteFrames.x + spr.vframes = iChar.equipment[iPiece].spriteFrames.y + spr.frame = iChar.equipment[iPiece].spriteFrame + spr.centered = false + spr.position = Vector2(4,4) + btn.add_child(spr) + btn.connect("pressed", _selectEquipment.bind(iChar.equipment[iPiece], true)) + btn.connect("focus_entered", _selectEquipment.bind(iChar.equipment[iPiece], false)) + + if iChar.equipment[iPiece].can_have_multiple_of: + var lblQuantity:Label = Label.new() + lblQuantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + lblQuantity.size = Vector2(24, 24) + lblQuantity.custom_minimum_size = Vector2(0,0) + lblQuantity.position = Vector2(10, 2) + lblQuantity.text = str(iChar.equipment[iPiece].quantity) + lblQuantity.add_theme_font_override("font", quantityFont) + lblQuantity.add_theme_font_size_override("font", 8) + lblQuantity.scale = Vector2(0.5, 0.5) + btn.add_child(lblQuantity) + pass + pass + pass + + # draw equipment buttons (for unequipping) +func _selectEquipment(item:Item, isPress: bool): + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = true + if !is_showing_inventory: + return + if lastPressedItem == item and lastPressedType == "Equipment" and timerSelect > 0: + $SfxUnequip.play() + GameManager.character_data.unequip_item(selectedEquipment) + selectedItem = selectedEquipment + lastPressedItem = null + selectedEquipment = null + lastPressedType = "Item" + var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory + for child in vboxInvent.find_child("HBoxControl1").get_children(): + child.grab_focus() + break + return + lastPressedType = "Equipment" + selectedEquipment = item + # update description in bottom + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = load(item.spritePath) + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.hframes = item.spriteFrames.x + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.vframes = item.spriteFrames.y + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.frame = item.spriteFrame + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = item.description + if isPress: + lastPressedItem = item + timerSelect = 0.4 + # unequip item + pass + +func _selectItem(item:Item, isPress: bool): + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = true + if !is_showing_inventory: + return + if lastPressedItem == item and lastPressedType == "Item" and timerSelect > 0: + timerSelect = 0 + $SfxEquip.play() + GameManager.character_data.equip_item(selectedItem) + selectedEquipment = selectedItem + selectedItem = null + lastPressedItem = null + lastPressedType = "Equipment" + var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer + for child in eqContainer.get_children(): + child.grab_focus() + break + return + lastPressedType = "Item" + selectedItem = item + # update description in bottom + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = load(item.spritePath) + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.hframes = item.spriteFrames.x + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.vframes = item.spriteFrames.y + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.frame = item.spriteFrame + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = item.description + if isPress: + lastPressedItem = item + timerSelect = 0.4 + pass + +func _process(delta: float) -> void: + if timerSelect > 0: + timerSelect -= delta + if timerSelect <= 0: + timerSelect = 0 + pass + +func _input(_event: InputEvent): + #print("connected to newtwork:", GameManager.is_network_connected) + #print("finished tween:", finished_tween) + if !GameManager.is_network_connected or finished_tween == false: + return + if is_showing_inventory: + if !$ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.has_focus(): + if lastPressedType == "Equipment" and selectedEquipment != null and Input.is_action_just_pressed("ui_accept"): + $SfxUnequip.play() + GameManager.character_data.unequip_item(selectedEquipment) + selectedItem = selectedEquipment + selectedEquipment = null + lastPressedItem = null + lastPressedType = "Item" + var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory + for child in vboxInvent.find_child("HBoxControl1").get_children(): + child.grab_focus() + break + return + if lastPressedType == "Item" and selectedItem != null and Input.is_action_just_pressed("ui_accept"): + $SfxEquip.play() + GameManager.character_data.equip_item(selectedItem) + selectedEquipment = selectedItem + selectedItem = null + lastPressedItem = null + lastPressedType = "Equipment" + var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer + for child in eqContainer.get_children(): + child.grab_focus() + break + return + + if Input.is_action_just_pressed("inventory") and finished_tween == true: + finished_tween = false + var move_range = 119 + var move_duration = 0.3 + if is_showing_inventory: + move_range = -move_range + #stats movement + var move_tween = get_tree().create_tween() + move_tween.tween_property($ControlContainer/ControlStats, "position:x",$ControlContainer/ControlStats.position.x + move_range,move_duration).from($ControlContainer/ControlStats.position.x).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT) + move_range = -80 + if is_showing_inventory: + move_range = -move_range + #inventory movement + var move_tween2 = get_tree().create_tween() + move_tween2.tween_property($ControlContainer/ControlInventory, "position:x",$ControlContainer/ControlInventory.position.x + move_range,move_duration).from($ControlContainer/ControlInventory.position.x).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT) + + move_range = -33 + if is_showing_inventory: + $SfxInventoryClose.play() + move_range = -move_range + else: + $SfxInventoryOpen.play() + #info movement + var move_tween3 = get_tree().create_tween() + move_tween3.tween_property($ControlContainer/ControlInfo, "position:y",$ControlContainer/ControlInfo.position.y + move_range,move_duration).from($ControlContainer/ControlInfo.position.y).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN_OUT) + + is_showing_inventory = !is_showing_inventory + await move_tween3.finished + finished_tween = true + + if is_showing_inventory: + # preselect first item + var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory + var hadInventoryItem = false + for child in vboxInvent.find_child("HBoxControl1").get_children(): + child.grab_focus() + hadInventoryItem = true + break + lastPressedType = "Item" + if hadInventoryItem == false: + # preselect something in equipment instead + selectedItem = null + lastPressedItem = null + lastPressedType = "Equipment" + var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer + for child in eqContainer.get_children(): + child.grab_focus() + break + pass + + pass + ' + if Input.is_action_just_pressed("ui_right"): + $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer. + pass + if Input.is_action_just_pressed("ui_left"): + + pass' + ' + if Input.is_action_just_pressed("ui_up"): + $ControlContainer/ControlInventory/Sprite2DSelector.position.y -= 20 + pass + + if Input.is_action_just_pressed("ui_down"): + $ControlContainer/ControlInventory/Sprite2DSelector.position.y += 20 + pass' + + +func _on_button_drop_pressed() -> void: + if !is_showing_inventory: + return + if lastPressedType == "Item": + if selectedItem != null: + GameManager.character_data.drop_item(selectedItem) + selectedItem = null + # clear default stuff + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = "" + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = false + else: + if selectedEquipment != null: + GameManager.character_data.drop_equipment(selectedEquipment) + selectedEquipment = null + # clear default stuff + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control/Sprite2DItem.texture = null + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/LabelItemDescription.text = "" + $ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop.visible = false + + var vboxInvent = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory + var hadInventoryItem = false + for child in vboxInvent.find_child("HBoxControl1").get_children(): + child.grab_focus() + hadInventoryItem = true + break + lastPressedType = "Item" + if hadInventoryItem == false: + selectedItem = null + lastPressedItem = null + lastPressedType = "Equipment" + var eqContainer = $ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer + for child in eqContainer.get_children(): + child.grab_focus() + break + + + pass # Replace with function body. diff --git a/src/scripts/inspiration_scripts/inventory.gd.uid b/src/scripts/inspiration_scripts/inventory.gd.uid new file mode 100644 index 0000000..6e3ef11 --- /dev/null +++ b/src/scripts/inspiration_scripts/inventory.gd.uid @@ -0,0 +1 @@ +uid://20kfmxrtt20e diff --git a/src/scripts/inspiration_scripts/inventory.tscn b/src/scripts/inspiration_scripts/inventory.tscn new file mode 100644 index 0000000..25fac6f --- /dev/null +++ b/src/scripts/inspiration_scripts/inventory.tscn @@ -0,0 +1,693 @@ +[gd_scene format=3 uid="uid://du1cpug8yag6w"] + +[ext_resource type="Texture2D" uid="uid://b7u7dbiaub8lp" path="res://assets/gfx/ruinborn_mImwZSNWBM.png" id="1_0amil"] +[ext_resource type="Script" uid="uid://20kfmxrtt20e" path="res://assets/scripts/ui/inventory.gd" id="1_k81k7"] +[ext_resource type="Texture2D" uid="uid://hib38y541eog" path="res://assets/gfx/items_n_shit.png" id="2_7vwhs"] +[ext_resource type="Texture2D" uid="uid://ct0rllwve2s1y" path="res://assets/gfx/ui/inventory_panel_small.png" id="2_voqm7"] +[ext_resource type="Texture2D" uid="uid://who0clhmi5cl" path="res://assets/gfx/ui/inventory_panel.png" id="4_nlhqn"] +[ext_resource type="Texture2D" uid="uid://cxend0ndnfn32" path="res://assets/gfx/ui/inventory_slot_kenny_white.png" id="4_nxmsh"] +[ext_resource type="Texture2D" uid="uid://bsnfadlf1dgnw" path="res://assets/gfx/ui/inventory_slot_kenny_black_sword.png" id="6_k81k7"] +[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="7_kiwfx"] +[ext_resource type="Texture2D" uid="uid://tdivehfcj0el" path="res://assets/gfx/ui/inventory_slot_kenny_black_shield.png" id="7_vardb"] +[ext_resource type="Texture2D" uid="uid://b1l30o2ljhl2t" path="res://assets/gfx/ui/inventory_slot_kenny_black_armour.png" id="8_mnwqb"] +[ext_resource type="Texture2D" uid="uid://jgbrhnsaxvg" path="res://assets/gfx/ui/inventory_slot_kenny_black_helm.png" id="9_nbh80"] +[ext_resource type="Texture2D" uid="uid://b71gs7h2v0rdi" path="res://assets/gfx/ui/inventory_slot_kenny_black_ring.png" id="10_kiwfx"] +[ext_resource type="Texture2D" uid="uid://ckctmypotajtf" path="res://assets/gfx/ui/inventory_slot_kenny_black_shoes.png" id="11_ylqbh"] +[ext_resource type="Texture2D" uid="uid://c21a60s4funrr" path="res://assets/gfx/ui/inventory_info_panel.png" id="13_vardb"] +[ext_resource type="AudioStream" uid="uid://x6lxrywls7e2" path="res://assets/audio/sfx/inventory/inventory_open.mp3" id="14_mnwqb"] +[ext_resource type="AudioStream" uid="uid://cfsubtwvpi7yn" path="res://assets/audio/sfx/inventory/inventory_open_inverted.mp3" id="14_nbh80"] +[ext_resource type="AudioStream" uid="uid://djw6c5rb4mm60" path="res://assets/audio/sfx/cloth/leather_cloth_02.wav.mp3" id="17_51fgf"] +[ext_resource type="AudioStream" uid="uid://umoxmryvbm01" path="res://assets/audio/sfx/cloth/leather_cloth_01.wav.mp3" id="18_qk47y"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_nbh80"] +texture = ExtResource("4_nxmsh") +modulate_color = Color(0.511719, 0.511719, 0.511719, 1) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kiwfx"] +texture = ExtResource("4_nxmsh") +modulate_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_ylqbh"] +texture = ExtResource("4_nxmsh") + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nbh80"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_51fgf"] +texture = ExtResource("4_nxmsh") + +[node name="Inventory" type="CanvasLayer"] +layer = 21 +script = ExtResource("1_k81k7") + +[node name="Sprite2D" type="Sprite2D" parent="."] +visible = false +modulate = Color(0.980057, 0.975295, 1, 1) +z_index = -2 +z_as_relative = false +position = Vector2(164.625, 92.75) +scale = Vector2(0.258803, 0.263268) +texture = ExtResource("1_0amil") + +[node name="ControlContainer" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ControlStats" type="Control" parent="ControlContainer"] +layout_mode = 1 +anchors_preset = 9 +anchor_bottom = 1.0 +grow_vertical = 2 + +[node name="Sprite2DPanelz" type="Sprite2D" parent="ControlContainer/ControlStats"] +texture = ExtResource("4_nlhqn") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlStats"] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +theme_override_constants/margin_left = 3 +theme_override_constants/margin_top = 3 +theme_override_constants/margin_right = 3 +theme_override_constants/margin_bottom = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlStats/MarginContainer"] +layout_mode = 2 + +[node name="LabelPlayerName" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 6 +text = "Fronko" +horizontal_alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="LabelBaseStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "Level + +Hp +Mp + +Str +Dex +End +Int +Wis +Lck" + +[node name="LabelBaseStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "1 + +30 / 30 +20 / 20 + +10 +10 +10 +10 +10 +10" +horizontal_alignment = 2 + +[node name="LabelDerivedStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "Exp +Coin + + + +Damage +Defense +MovSpd +AtkSpd +Sight +SpellAmp +CritChance" + +[node name="LabelDerivedStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "400 / 1000 +1 + + + +2 +3 +2.1 +1.4 +7.0 +3.0 +12.0%" +horizontal_alignment = 2 + +[node name="ControlInventory" type="Control" parent="ControlContainer"] +custom_minimum_size = Vector2(80, 0) +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="Sprite2DPanel" type="Sprite2D" parent="ControlContainer/ControlInventory"] +modulate = Color(0, 0, 0, 0.862745) +texture = ExtResource("2_voqm7") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory"] +custom_minimum_size = Vector2(80, 0) +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="LabelInventory" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 6 +text = "Inventory" +horizontal_alignment = 1 + +[node name="ControlInventory" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 72) +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory"] +layout_mode = 2 +offset_left = 2.0 +offset_top = 2.0 +offset_right = 78.0 +offset_bottom = 70.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer"] +layout_mode = 2 +follow_focus = true + +[node name="VBoxContainerInventory" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer"] +layout_mode = 2 +theme_override_constants/separation = -4 + +[node name="HBoxControl1" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="LabelStack" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -26.0 +offset_top = 2.0 +offset_right = -2.0 +offset_bottom = 26.0 +grow_horizontal = 0 +theme_override_fonts/font = ExtResource("7_kiwfx") +theme_override_font_sizes/font_size = 8 +text = "5" +horizontal_alignment = 2 + +[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button6"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="HBoxControl2" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="HBoxControl3" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="ControlEquipment" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(80, 64) +layout_mode = 2 + +[node name="Label" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -21.5 +offset_right = 21.5 +offset_bottom = 23.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 6 +text = "Equipment" +horizontal_alignment = 1 + +[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(13, 21) +texture = ExtResource("6_k81k7") + +[node name="Sprite2D2" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(67, 21) +texture = ExtResource("7_vardb") + +[node name="Sprite2D3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(40, 21) +texture = ExtResource("8_mnwqb") + +[node name="Sprite2D4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(13, 48) +texture = ExtResource("9_nbh80") + +[node name="Sprite2D9" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(67, 48) +texture = ExtResource("10_kiwfx") + +[node name="Sprite2D5" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(40, 48) +texture = ExtResource("11_ylqbh") + +[node name="ControlEquipmentContainer" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Button" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 1.0 +offset_top = 9.0 +offset_right = 25.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button2" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 28.0 +offset_top = 9.0 +offset_right = 52.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button2"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 55.0 +offset_top = 9.0 +offset_right = 79.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button3"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 1.0 +offset_top = 36.0 +offset_right = 25.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button4"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 28.0 +offset_top = 36.0 +offset_right = 52.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button5"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 55.0 +offset_top = 36.0 +offset_right = 79.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button6"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="ControlInfo" type="Control" parent="ControlContainer"] +custom_minimum_size = Vector2(0, 33) +layout_mode = 1 +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 0 + +[node name="Control" type="Control" parent="ControlContainer/ControlInfo"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInfo/Control"] +texture = ExtResource("13_vardb") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInfo/Control"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlInfo/Control/MarginContainer"] +layout_mode = 2 + +[node name="Control" type="Control" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Sprite2DSelector" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"] +modulate = Color(0.20704, 0.205805, 0.214844, 1) +texture = ExtResource("4_nxmsh") +centered = false + +[node name="Sprite2DItem" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="LabelItemDescription" type="Label" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 1 +theme_override_font_sizes/font_size = 6 +text = "A small, but sturdy wooden shield. + 1 DEF" + +[node name="ButtonDrop" type="Button" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 8 +text = "Drop" + +[node name="SfxInventoryClose" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("14_nbh80") +volume_db = -10.0 + +[node name="SfxInventoryOpen" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("14_mnwqb") +volume_db = -10.0 + +[node name="SfxUnequip" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("17_51fgf") + +[node name="SfxEquip" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("18_qk47y") + +[connection signal="pressed" from="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/ButtonDrop" to="." method="_on_button_drop_pressed"] diff --git a/src/scripts/inspiration_scripts/inventory.tscn3980824984.tmp b/src/scripts/inspiration_scripts/inventory.tscn3980824984.tmp new file mode 100644 index 0000000..c796774 --- /dev/null +++ b/src/scripts/inspiration_scripts/inventory.tscn3980824984.tmp @@ -0,0 +1,692 @@ +[gd_scene load_steps=24 format=3 uid="uid://du1cpug8yag6w"] + +[ext_resource type="Texture2D" uid="uid://b7u7dbiaub8lp" path="res://assets/gfx/ruinborn_mImwZSNWBM.png" id="1_0amil"] +[ext_resource type="Script" uid="uid://20kfmxrtt20e" path="res://assets/scripts/ui/inventory.gd" id="1_k81k7"] +[ext_resource type="Texture2D" uid="uid://hib38y541eog" path="res://assets/gfx/items_n_shit.png" id="2_7vwhs"] +[ext_resource type="Texture2D" uid="uid://ct0rllwve2s1y" path="res://assets/gfx/ui/inventory_panel_small.png" id="2_voqm7"] +[ext_resource type="Texture2D" uid="uid://who0clhmi5cl" path="res://assets/gfx/ui/inventory_panel.png" id="4_nlhqn"] +[ext_resource type="Texture2D" uid="uid://cxend0ndnfn32" path="res://assets/gfx/ui/inventory_slot_kenny_white.png" id="4_nxmsh"] +[ext_resource type="Texture2D" uid="uid://bsnfadlf1dgnw" path="res://assets/gfx/ui/inventory_slot_kenny_black_sword.png" id="6_k81k7"] +[ext_resource type="FontFile" uid="uid://cbmcfue0ek0tk" path="res://assets/fonts/dmg_numbers.png" id="7_kiwfx"] +[ext_resource type="Texture2D" uid="uid://tdivehfcj0el" path="res://assets/gfx/ui/inventory_slot_kenny_black_shield.png" id="7_vardb"] +[ext_resource type="Texture2D" uid="uid://b1l30o2ljhl2t" path="res://assets/gfx/ui/inventory_slot_kenny_black_armour.png" id="8_mnwqb"] +[ext_resource type="Texture2D" uid="uid://jgbrhnsaxvg" path="res://assets/gfx/ui/inventory_slot_kenny_black_helm.png" id="9_nbh80"] +[ext_resource type="Texture2D" uid="uid://b71gs7h2v0rdi" path="res://assets/gfx/ui/inventory_slot_kenny_black_ring.png" id="10_kiwfx"] +[ext_resource type="Texture2D" uid="uid://ckctmypotajtf" path="res://assets/gfx/ui/inventory_slot_kenny_black_shoes.png" id="11_ylqbh"] +[ext_resource type="Texture2D" uid="uid://c21a60s4funrr" path="res://assets/gfx/ui/inventory_info_panel.png" id="13_vardb"] +[ext_resource type="AudioStream" uid="uid://x6lxrywls7e2" path="res://assets/audio/sfx/inventory/inventory_open.mp3" id="14_mnwqb"] +[ext_resource type="AudioStream" uid="uid://cfsubtwvpi7yn" path="res://assets/audio/sfx/inventory/inventory_open_inverted.mp3" id="14_nbh80"] +[ext_resource type="AudioStream" uid="uid://djw6c5rb4mm60" path="res://assets/audio/sfx/cloth/leather_cloth_02.wav.mp3" id="17_51fgf"] +[ext_resource type="AudioStream" uid="uid://umoxmryvbm01" path="res://assets/audio/sfx/cloth/leather_cloth_01.wav.mp3" id="18_qk47y"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_nbh80"] +texture = ExtResource("4_nxmsh") +modulate_color = Color(0.511719, 0.511719, 0.511719, 1) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kiwfx"] +texture = ExtResource("4_nxmsh") +modulate_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_ylqbh"] +texture = ExtResource("4_nxmsh") + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nbh80"] + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_51fgf"] +texture = ExtResource("4_nxmsh") + +[node name="Inventory" type="CanvasLayer"] +layer = 21 +script = ExtResource("1_k81k7") + +[node name="Sprite2D" type="Sprite2D" parent="."] +visible = false +modulate = Color(0.980057, 0.975295, 1, 1) +z_index = -2 +z_as_relative = false +position = Vector2(164.625, 92.75) +scale = Vector2(0.258803, 0.263268) +texture = ExtResource("1_0amil") + +[node name="ControlContainer" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ControlStats" type="Control" parent="ControlContainer"] +layout_mode = 1 +anchors_preset = 9 +anchor_bottom = 1.0 +grow_vertical = 2 + +[node name="Sprite2DPanelz" type="Sprite2D" parent="ControlContainer/ControlStats"] +texture = ExtResource("4_nlhqn") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlStats"] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +theme_override_constants/margin_left = 3 +theme_override_constants/margin_top = 3 +theme_override_constants/margin_right = 3 +theme_override_constants/margin_bottom = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlStats/MarginContainer"] +layout_mode = 2 + +[node name="LabelPlayerName" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 6 +text = "Fronko" +horizontal_alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="LabelBaseStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "Level + +Hp +Mp + +Str +Dex +End +Int +Wis +Lck" + +[node name="LabelBaseStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "1 + +30 / 30 +20 / 20 + +10 +10 +10 +10 +10 +10" +horizontal_alignment = 2 + +[node name="LabelDerivedStats" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "Exp +Coin + + + +Damage +Defense +MovSpd +AtkSpd +Sight +SpellAmp +CritChance" + +[node name="LabelDerivedStatsValue" type="Label" parent="ControlContainer/ControlStats/MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_override_constants/line_spacing = 0 +theme_override_font_sizes/font_size = 6 +text = "400 / 1000 +1 + + + +2 +3 +2.1 +1.4 +7.0 +3.0 +12.0%" +horizontal_alignment = 2 + +[node name="ControlInventory" type="Control" parent="ControlContainer"] +custom_minimum_size = Vector2(80, 0) +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="Sprite2DPanel" type="Sprite2D" parent="ControlContainer/ControlInventory"] +modulate = Color(0, 0, 0, 0.862745) +texture = ExtResource("2_voqm7") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory"] +custom_minimum_size = Vector2(80, 0) +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="LabelInventory" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 6 +text = "Inventory" +horizontal_alignment = 1 + +[node name="ControlInventory" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 72) +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory"] +layout_mode = 2 +offset_left = 2.0 +offset_top = 2.0 +offset_right = 78.0 +offset_bottom = 70.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer"] +layout_mode = 2 +follow_focus = true + +[node name="VBoxContainerInventory" type="VBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer"] +layout_mode = 2 +theme_override_constants/separation = -4 + +[node name="HBoxControl1" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="LabelStack" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button3"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -26.0 +offset_top = 2.0 +offset_right = -2.0 +offset_bottom = 26.0 +grow_horizontal = 0 +theme_override_fonts/font = ExtResource("7_kiwfx") +theme_override_font_sizes/font_size = 8 +text = "5" +horizontal_alignment = 2 + +[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button6"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl1/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="HBoxControl2" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl2/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="HBoxControl3" type="HBoxContainer" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 0 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button3"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button5"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +theme_override_styles/focus = SubResource("StyleBoxTexture_nbh80") +theme_override_styles/hover = SubResource("StyleBoxTexture_kiwfx") +theme_override_styles/pressed = SubResource("StyleBoxTexture_ylqbh") +theme_override_styles/normal = SubResource("StyleBoxEmpty_nbh80") +text = " " + +[node name="Sprite2DShield4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlInventory/MarginContainer/ScrollContainer/VBoxContainerInventory/HBoxControl3/Button4"] +process_mode = 4 +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="ControlEquipment" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(80, 64) +layout_mode = 2 + +[node name="Label" type="Label" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -21.5 +offset_right = 21.5 +offset_bottom = 23.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 6 +text = "Equipment" +horizontal_alignment = 1 + +[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(13, 21) +texture = ExtResource("6_k81k7") + +[node name="Sprite2D2" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(67, 21) +texture = ExtResource("7_vardb") + +[node name="Sprite2D3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(40, 21) +texture = ExtResource("8_mnwqb") + +[node name="Sprite2D4" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(13, 48) +texture = ExtResource("9_nbh80") + +[node name="Sprite2D9" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(67, 48) +texture = ExtResource("10_kiwfx") + +[node name="Sprite2D5" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +modulate = Color(1, 1, 1, 0.670588) +position = Vector2(40, 48) +texture = ExtResource("11_ylqbh") + +[node name="ControlEquipmentContainer" type="Control" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Button" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 1.0 +offset_top = 9.0 +offset_right = 25.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button2" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 28.0 +offset_top = 9.0 +offset_right = 52.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button2"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button3" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 55.0 +offset_top = 9.0 +offset_right = 79.0 +offset_bottom = 33.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button3"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button4" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 1.0 +offset_top = 36.0 +offset_right = 25.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button4"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button5" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 28.0 +offset_top = 36.0 +offset_right = 52.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button5"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="Button6" type="Button" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 0 +offset_left = 55.0 +offset_top = 36.0 +offset_right = 79.0 +offset_bottom = 60.0 +theme_override_styles/normal = SubResource("StyleBoxTexture_51fgf") + +[node name="Sprite2DShield3" type="Sprite2D" parent="ControlContainer/ControlInventory/MarginContainer/VBoxContainer/ControlEquipment/ControlEquipmentContainer/Button6"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="ControlInfo" type="Control" parent="ControlContainer"] +custom_minimum_size = Vector2(0, 33) +layout_mode = 1 +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 0 + +[node name="Control" type="Control" parent="ControlContainer/ControlInfo"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Sprite2D" type="Sprite2D" parent="ControlContainer/ControlInfo/Control"] +texture = ExtResource("13_vardb") +centered = false + +[node name="MarginContainer" type="MarginContainer" parent="ControlContainer/ControlInfo/Control"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="HBoxContainer" type="HBoxContainer" parent="ControlContainer/ControlInfo/Control/MarginContainer"] +layout_mode = 2 + +[node name="Control" type="Control" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_vertical = 4 + +[node name="Sprite2DSelector" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"] +modulate = Color(0.20704, 0.205805, 0.214844, 1) +texture = ExtResource("4_nxmsh") +centered = false + +[node name="Sprite2DItem" type="Sprite2D" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer/Control"] +position = Vector2(4, 4) +texture = ExtResource("2_7vwhs") +centered = false +hframes = 20 +vframes = 14 +frame = 8 + +[node name="LabelItemDescription" type="Label" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 1 +theme_override_font_sizes/font_size = 6 +text = "A small, but sturdy wooden shield. + 1 DEF" + +[node name="ButtonEquip" type="Button" parent="ControlContainer/ControlInfo/Control/MarginContainer/HBoxContainer"] +visible = false +layout_mode = 2 +theme_override_font_sizes/font_size = 8 +text = "Equip" + +[node name="SfxInventoryClose" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("14_nbh80") +volume_db = -10.0 + +[node name="SfxInventoryOpen" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("14_mnwqb") +volume_db = -10.0 + +[node name="SfxUnequip" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("17_51fgf") + +[node name="SfxEquip" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("18_qk47y") diff --git a/src/scripts/inspiration_scripts/item.gd b/src/scripts/inspiration_scripts/item.gd index 4c1e95f..2f92d0a 100644 --- a/src/scripts/inspiration_scripts/item.gd +++ b/src/scripts/inspiration_scripts/item.gd @@ -27,7 +27,11 @@ enum WeaponType { AMMUNITION, BOW, SWORD, - AXE + AXE, + DAGGER, + STAFF, + SPEAR, + MACE } var use_function = null diff --git a/src/scripts/interactable_object.gd b/src/scripts/interactable_object.gd index d455d1b..1b38a44 100644 --- a/src/scripts/interactable_object.gd +++ b/src/scripts/interactable_object.gd @@ -279,6 +279,19 @@ func _break_into_pieces(): $Shadow.visible = false $Sprite2DAbove.visible = false $Sprite2D.visible = false + + # Spawn item loot when breaking (30% chance) + if is_multiplayer_authority(): + var drop_chance = randf() + if drop_chance < 0.3: # 30% chance to drop item + var item = ItemDatabase.get_random_container_item() + if item: + var entities_node = get_parent() + var game_world = get_tree().get_first_node_in_group("game_world") + if entities_node and game_world: + ItemLootHelper.spawn_item_loot(item, global_position, entities_node, game_world) + print(name, " dropped item: ", item.item_name, " when broken") + if ($SfxShatter.playing): await $SfxShatter.finished if ($SfxBreakCrate.playing): @@ -562,13 +575,13 @@ func _open_chest(by_player: Node = null): $SfxChestOpen.play() print(name, " opened by ", by_player.name, "! Item given: ", selected_loot.name) + + # Sync chest opening visual to all clients (item already given on server) + if multiplayer.has_multiplayer_peer(): + var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0 + _sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id) else: push_error("Chest: ERROR - No valid player to give item to!") - - # Sync chest opening visual to all clients (item already given on server) - if multiplayer.has_multiplayer_peer(): - var player_peer_id = by_player.get_multiplayer_authority() if by_player else 0 - _sync_chest_open.rpc(selected_loot.type if by_player else "coin", player_peer_id) @rpc("any_peer", "reliable") func _request_chest_open(player_peer_id: int): diff --git a/src/scripts/inventory_ui.gd b/src/scripts/inventory_ui.gd new file mode 100644 index 0000000..ed3cd42 --- /dev/null +++ b/src/scripts/inventory_ui.gd @@ -0,0 +1,566 @@ +extends CanvasLayer + +# Minimalistic Inventory UI with Stats Panel +# Toggle with Tab key, shows stats, equipment slots and inventory items + +var is_open: bool = false +var local_player: Node = null + +# Selection tracking +var selected_item: Item = null # Selected inventory item +var selected_slot: String = "" # Selected equipment slot name +var selected_type: String = "" # "item" or "equipment" + +# UI Nodes +var container: Control = null +var stats_panel: Control = null +var equipment_panel: Control = null +var inventory_grid: GridContainer = null +var scroll_container: ScrollContainer = null + +# Stats labels +var label_base_stats: Label = null +var label_base_stats_value: Label = null +var label_derived_stats: Label = null +var label_derived_stats_value: Label = null + +# Store button/item mappings for selection highlighting +var inventory_buttons: Dictionary = {} # item -> button +var equipment_buttons: Dictionary = {} # slot_name -> button + +# Equipment slot buttons +var equipment_slots: Dictionary = { + "mainhand": null, + "offhand": null, + "headgear": null, + "armour": null, + "boots": null, + "accessory": null +} + +func _ready(): + # Set layer to be above game but below chat + layer = 150 + + # Create UI structure + _setup_ui() + + # Find local player + call_deferred("_find_local_player") + +func _find_local_player(): + # Find the local player + var game_world = get_tree().get_first_node_in_group("game_world") + if game_world: + var player_manager = game_world.get_node_or_null("PlayerManager") + if player_manager: + var local_players = player_manager.get_local_players() + if local_players.size() > 0: + local_player = local_players[0] + if local_player and local_player.character_stats: + # Connect to character_changed signal + if local_player.character_stats.character_changed.is_connected(_on_character_changed): + local_player.character_stats.character_changed.disconnect(_on_character_changed) + local_player.character_stats.character_changed.connect(_on_character_changed) + # Initial update + _update_ui() + _update_stats() + +func _setup_ui(): + # Main container (anchored to bottom-left, larger to fit stats + inventory) + container = Control.new() + container.name = "InventoryContainer" + container.set_anchors_preset(Control.PRESET_BOTTOM_LEFT) + container.offset_left = 10 # 10px padding from left + container.offset_right = 10 + 620 # Width 620px (stats 200px + inventory 400px + gap 20px) + container.offset_top = -350 # Height 340px (350 - 10) + container.offset_bottom = -10 # 10px padding from bottom + container.visible = false + add_child(container) + + # Background panel + var background = ColorRect.new() + background.name = "Background" + background.color = Color(0.1, 0.1, 0.1, 0.85) + background.mouse_filter = Control.MOUSE_FILTER_IGNORE + container.add_child(background) + background.set_anchors_preset(Control.PRESET_FULL_RECT) + + # HBox for stats and inventory side by side + var hbox = HBoxContainer.new() + hbox.name = "HBox" + container.add_child(hbox) + hbox.set_anchors_preset(Control.PRESET_FULL_RECT) + hbox.add_theme_constant_override("separation", 10) + + # Stats panel (left side) + _create_stats_panel() + hbox.add_child(stats_panel) + + # Inventory/Equipment panel (right side) + var inv_panel = VBoxContainer.new() + inv_panel.name = "InventoryPanel" + inv_panel.custom_minimum_size = Vector2(400, 0) + hbox.add_child(inv_panel) + inv_panel.add_theme_constant_override("separation", 5) + + # Equipment label + var eq_label = Label.new() + eq_label.text = "Equipment" + eq_label.add_theme_font_size_override("font_size", 14) + var standard_font_resource = null + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + standard_font_resource = load("res://assets/fonts/standard_font.png") + if standard_font_resource: + eq_label.add_theme_font_override("font", standard_font_resource) + inv_panel.add_child(eq_label) + + equipment_panel = GridContainer.new() + equipment_panel.name = "EquipmentPanel" + equipment_panel.columns = 3 + equipment_panel.add_theme_constant_override("h_separation", 5) + equipment_panel.add_theme_constant_override("v_separation", 5) + inv_panel.add_child(equipment_panel) + + # Create equipment slot buttons + _create_equipment_slots() + + # Inventory label + var inv_label = Label.new() + inv_label.text = "Inventory" + inv_label.add_theme_font_size_override("font_size", 14) + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + standard_font_resource = load("res://assets/fonts/standard_font.png") + if standard_font_resource: + inv_label.add_theme_font_override("font", standard_font_resource) + inv_panel.add_child(inv_label) + + # Scroll container for inventory + scroll_container = ScrollContainer.new() + scroll_container.name = "InventoryScroll" + scroll_container.custom_minimum_size = Vector2(380, 120) + inv_panel.add_child(scroll_container) + + # Inventory grid + inventory_grid = GridContainer.new() + inventory_grid.name = "InventoryGrid" + inventory_grid.columns = 6 + inventory_grid.add_theme_constant_override("h_separation", 3) + inventory_grid.add_theme_constant_override("v_separation", 3) + scroll_container.add_child(inventory_grid) + +func _create_stats_panel(): + stats_panel = VBoxContainer.new() + stats_panel.name = "StatsPanel" + stats_panel.custom_minimum_size = Vector2(200, 0) + + # Stats label + var stats_label = Label.new() + stats_label.text = "Stats" + stats_label.add_theme_font_size_override("font_size", 14) + var standard_font_resource = null + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + standard_font_resource = load("res://assets/fonts/standard_font.png") + if standard_font_resource: + stats_label.add_theme_font_override("font", standard_font_resource) + stats_panel.add_child(stats_label) + + # HBox for stats columns + var stats_hbox = HBoxContainer.new() + stats_hbox.name = "StatsHBox" + stats_panel.add_child(stats_hbox) + stats_hbox.add_theme_constant_override("separation", 5) + + # Base stats label + label_base_stats = Label.new() + label_base_stats.name = "LabelBaseStats" + label_base_stats.text = "Level\n\nHP\nMP\n\nSTR\nDEX\nEND\nINT\nWIS\nLCK" + label_base_stats.add_theme_font_size_override("font_size", 10) + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var font_resource = load("res://assets/fonts/standard_font.png") + if font_resource: + label_base_stats.add_theme_font_override("font", font_resource) + stats_hbox.add_child(label_base_stats) + + # Base stats values label + label_base_stats_value = Label.new() + label_base_stats_value.name = "LabelBaseStatsValue" + label_base_stats_value.text = "1\n\n30/30\n20/20\n\n10\n10\n10\n10\n10\n10" + label_base_stats_value.add_theme_font_size_override("font_size", 10) + label_base_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var font_resource = load("res://assets/fonts/standard_font.png") + if font_resource: + label_base_stats_value.add_theme_font_override("font", font_resource) + stats_hbox.add_child(label_base_stats_value) + + # Derived stats label + label_derived_stats = Label.new() + label_derived_stats.name = "LabelDerivedStats" + label_derived_stats.text = "XP\nCoin\n\n\n\nDMG\nDEF\nMovSpd\nAtkSpd\nSight\nSpellAmp\nCrit%" + label_derived_stats.add_theme_font_size_override("font_size", 10) + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var font_resource = load("res://assets/fonts/standard_font.png") + if font_resource: + label_derived_stats.add_theme_font_override("font", font_resource) + stats_hbox.add_child(label_derived_stats) + + # Derived stats values label + label_derived_stats_value = Label.new() + label_derived_stats_value.name = "LabelDerivedStatsValue" + label_derived_stats_value.text = "0/100\n0\n\n\n\n2.0\n2.0\n2.1\n1.4\n7.0\n5.0\n12.0%" + label_derived_stats_value.add_theme_font_size_override("font_size", 10) + label_derived_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var font_resource = load("res://assets/fonts/standard_font.png") + if font_resource: + label_derived_stats_value.add_theme_font_override("font", font_resource) + stats_hbox.add_child(label_derived_stats_value) + +func _update_stats(): + if not local_player or not local_player.character_stats: + return + + var char_stats = local_player.character_stats + + # Update base stats + label_base_stats_value.text = str(char_stats.level) + "\n\n" + \ + str(int(char_stats.hp)) + "/" + str(int(char_stats.maxhp)) + "\n" + \ + str(int(char_stats.mp)) + "/" + str(int(char_stats.maxmp)) + "\n\n" + \ + str(char_stats.baseStats.str) + "\n" + \ + str(char_stats.baseStats.dex) + "\n" + \ + str(char_stats.baseStats.end) + "\n" + \ + str(char_stats.baseStats.int) + "\n" + \ + str(char_stats.baseStats.wis) + "\n" + \ + str(char_stats.baseStats.lck) + + # Update derived stats + label_derived_stats_value.text = str(int(char_stats.xp)) + "/" + str(int(char_stats.xp_to_next_level)) + "\n" + \ + str(char_stats.coin) + "\n\n\n\n" + \ + str(char_stats.damage) + "\n" + \ + str(char_stats.defense) + "\n" + \ + str(char_stats.move_speed) + "\n" + \ + str(char_stats.attack_speed) + "\n" + \ + str(char_stats.sight) + "\n" + \ + str(char_stats.spell_amp) + "\n" + \ + str(char_stats.crit_chance) + "%" + +func _create_equipment_slots(): + # Equipment slot order: mainhand, offhand, headgear, armour, boots, accessory + var slot_names = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"] + var slot_labels = ["Weapon", "Shield", "Head", "Armour", "Boots", "Accessory"] + + for i in range(slot_names.size()): + var slot_name = slot_names[i] + var slot_label = slot_labels[i] + + # Container for slot + var slot_container = VBoxContainer.new() + slot_container.name = slot_name + "_slot" + equipment_panel.add_child(slot_container) + + # Label + var label = Label.new() + label.text = slot_label + label.add_theme_font_size_override("font_size", 10) + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + if ResourceLoader.exists("res://assets/fonts/standard_font.png"): + var font_resource = load("res://assets/fonts/standard_font.png") + if font_resource: + label.add_theme_font_override("font", font_resource) + slot_container.add_child(label) + + # Button + var button = Button.new() + button.name = slot_name + "_btn" + button.custom_minimum_size = Vector2(60, 60) + button.flat = true + button.connect("pressed", _on_equipment_slot_pressed.bind(slot_name)) + slot_container.add_child(button) + + equipment_slots[slot_name] = button + equipment_buttons[slot_name] = button + +func _on_equipment_slot_pressed(slot_name: String): + if not local_player or not local_player.character_stats: + return + + # Select this slot + selected_slot = slot_name + selected_item = local_player.character_stats.equipment[slot_name] + selected_type = "equipment" if selected_item else "" + _update_selection_highlight() + +func _update_selection_highlight(): + # Reset all button styles + for button in equipment_buttons.values(): + if button: + var highlight = button.get_node_or_null("Highlight") + if highlight: + highlight.queue_free() + + for button in inventory_buttons.values(): + if button: + var highlight = button.get_node_or_null("Highlight") + if highlight: + highlight.queue_free() + + # Highlight selected equipment slot + if selected_type == "equipment" and selected_slot != "": + var button = equipment_buttons.get(selected_slot) + if button: + var highlight = ColorRect.new() + highlight.name = "Highlight" + highlight.color = Color(1.0, 1.0, 0.0, 0.6) + highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE + highlight.z_index = 10 + button.add_child(highlight) + highlight.set_anchors_preset(Control.PRESET_FULL_RECT) + + # Highlight selected inventory item + if selected_type == "item" and selected_item: + var button = inventory_buttons.get(selected_item) + if button: + var highlight = ColorRect.new() + highlight.name = "Highlight" + highlight.color = Color(1.0, 1.0, 0.0, 0.6) + highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE + highlight.z_index = 10 + button.add_child(highlight) + highlight.set_anchors_preset(Control.PRESET_FULL_RECT) + +func _update_ui(): + if not local_player or not local_player.character_stats: + return + + var char_stats = local_player.character_stats + + # Clear button mappings + inventory_buttons.clear() + + # Update equipment slots + for slot_name in equipment_slots.keys(): + var button = equipment_slots[slot_name] + if not button: + continue + + # Clear existing children (sprite, highlight) + for child in button.get_children(): + child.queue_free() + + var equipped_item = char_stats.equipment[slot_name] + if equipped_item: + var sprite = Sprite2D.new() + var texture_path = "" + var use_equipment_path = false + + if slot_name == "mainhand" or slot_name == "offhand": + texture_path = equipped_item.spritePath + use_equipment_path = false + else: + texture_path = equipped_item.equipmentPath if equipped_item.equipmentPath != "" else equipped_item.spritePath + use_equipment_path = (equipped_item.equipmentPath != "") + + var texture = load(texture_path) + if texture: + sprite.texture = texture + if use_equipment_path: + sprite.hframes = 35 + sprite.vframes = 8 + else: + sprite.hframes = equipped_item.spriteFrames.x if equipped_item.spriteFrames.x > 0 else 20 + sprite.vframes = equipped_item.spriteFrames.y if equipped_item.spriteFrames.y > 0 else 14 + sprite.frame = equipped_item.spriteFrame + sprite.scale = Vector2(1.2, 1.2) + button.add_child(sprite) + + # Update inventory grid + for child in inventory_grid.get_children(): + child.queue_free() + + for item in char_stats.inventory: + var button = Button.new() + button.custom_minimum_size = Vector2(60, 60) + button.flat = true + button.connect("pressed", _on_inventory_item_pressed.bind(item)) + + if item.spritePath != "": + var sprite = Sprite2D.new() + var texture = load(item.spritePath) + if texture: + sprite.texture = texture + sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20 + sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14 + sprite.frame = item.spriteFrame + sprite.scale = Vector2(1.2, 1.2) + button.add_child(sprite) + + inventory_grid.add_child(button) + inventory_buttons[item] = button + + # Update selection highlight + _update_selection_highlight() + + # Update stats + _update_stats() + +func _on_inventory_item_pressed(item: Item): + if not local_player or not local_player.character_stats: + return + + selected_item = item + selected_slot = "" + selected_type = "item" + _update_selection_highlight() + +func _on_character_changed(_char: CharacterStats): + _update_ui() + _update_stats() + +func _input(event): + # Toggle with Tab key + if event is InputEventKey and event.keycode == KEY_TAB and event.pressed and not event.echo: + if is_open: + _close_inventory() + else: + _open_inventory() + get_viewport().set_input_as_handled() + return + + if not is_open: + return + + # F key: Unequip/equip items + if event is InputEventKey and event.keycode == KEY_F and event.pressed and not event.echo: + _handle_f_key() + get_viewport().set_input_as_handled() + return + + # E key: Drop selected item + if event is InputEventKey and event.keycode == KEY_E and event.pressed and not event.echo: + _handle_e_key() + get_viewport().set_input_as_handled() + return + +func _handle_f_key(): + if not local_player or not local_player.character_stats: + return + + var char_stats = local_player.character_stats + + if selected_type == "equipment" and selected_slot != "": + var equipped_item = char_stats.equipment[selected_slot] + if equipped_item: + char_stats.unequip_item(equipped_item) + selected_item = null + selected_slot = "" + selected_type = "" + return + + if selected_type == "item" and selected_item: + if selected_item.item_type == Item.ItemType.Equippable and selected_item.equipment_type != Item.EquipmentType.NONE: + char_stats.equip_item(selected_item) + selected_item = null + selected_slot = "" + selected_type = "" + elif selected_item.item_type == Item.ItemType.Restoration: + _use_consumable_item(selected_item) + selected_item = null + selected_slot = "" + selected_type = "" + +func _use_consumable_item(item: Item): + if not local_player or not local_player.character_stats: + return + + var char_stats = local_player.character_stats + + if item.modifiers.has("hp"): + var hp_heal = item.modifiers["hp"] + if local_player.has_method("heal"): + local_player.heal(hp_heal) + if item.modifiers.has("mp"): + var mana_amount = item.modifiers["mp"] + char_stats.restore_mana(mana_amount) + + var index = char_stats.inventory.find(item) + if index >= 0: + char_stats.inventory.remove_at(index) + char_stats.character_changed.emit(char_stats) + + print(local_player.name, " used item: ", item.item_name) + +func _handle_e_key(): + if not local_player or not local_player.character_stats: + return + + if selected_type != "item" or not selected_item: + return + + var char_stats = local_player.character_stats + + if not selected_item in char_stats.inventory: + return + + var index = char_stats.inventory.find(selected_item) + if index >= 0: + char_stats.inventory.remove_at(index) + + var drop_position = local_player.global_position + Vector2(randf_range(-20, 20), randf_range(-20, 20)) + var game_world = get_tree().get_first_node_in_group("game_world") + var entities_node = null + if game_world: + entities_node = game_world.get_node_or_null("Entities") + + if entities_node: + var loot = ItemLootHelper.spawn_item_loot(selected_item, drop_position, entities_node, game_world) + if loot: + if local_player.has_method("get_multiplayer_authority"): + var player_peer_id = local_player.get_multiplayer_authority() + loot.set_meta("dropped_by_peer_id", player_peer_id) + loot.set_meta("drop_time", Time.get_ticks_msec()) + + selected_item = null + selected_slot = "" + selected_type = "" + + char_stats.character_changed.emit(char_stats) + + print(local_player.name, " dropped item") + +func _open_inventory(): + if is_open: + return + + is_open = true + if container: + container.visible = true + + _lock_player_controls(true) + _update_ui() + + if not local_player: + _find_local_player() + +func _close_inventory(): + if not is_open: + return + + is_open = false + if container: + container.visible = false + + selected_item = null + selected_slot = "" + selected_type = "" + + _lock_player_controls(false) + +func _lock_player_controls(lock: bool): + var game_world = get_tree().get_first_node_in_group("game_world") + if game_world: + var player_manager = game_world.get_node_or_null("PlayerManager") + if player_manager: + var local_players = player_manager.get_local_players() + for player in local_players: + player.controls_disabled = lock diff --git a/src/scripts/inventory_ui.gd.uid b/src/scripts/inventory_ui.gd.uid new file mode 100644 index 0000000..9b43f5f --- /dev/null +++ b/src/scripts/inventory_ui.gd.uid @@ -0,0 +1 @@ +uid://vm6intetgl40 diff --git a/src/scripts/item_database.gd b/src/scripts/item_database.gd new file mode 100644 index 0000000..e462e76 --- /dev/null +++ b/src/scripts/item_database.gd @@ -0,0 +1,1361 @@ +class_name ItemDatabase +extends RefCounted + +# Item Database - Contains all item definitions and generation functions +# Coordinates are (x, y) where x is column, y is row in items_n_shit.png (20x14 grid) +# Frame calculation: frame = y * 20 + x + +# Item rarity tiers for random generation +enum ItemRarity { + COMMON, # Basic items (food, low-tier equipment) + UNCOMMON, # Mid-tier equipment + RARE, # High-tier equipment + EPIC, # Legendary equipment + CONSUMABLE # Potions and consumables +} + +# Dictionary to store all item definitions by ID +# Format: { "item_id": { item_data } } +static var item_definitions: Dictionary = {} + +# Initialize item database +static func _initialize(): + if item_definitions.is_empty(): + _load_all_items() + +# Load all item definitions +static func _load_all_items(): + # ARMOUR items (row 0) + _register_item("silk_robe", { + "item_name": "Silk Robe", + "description": "A luxurious silk robe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 10, # 10,0 + "modifiers": {"def": 1, "end": 1}, + "buy_cost": 50, + "sell_worth": 15, + "rarity": ItemRarity.COMMON + }) + + _register_item("padded_robe", { + "item_name": "Padded Robe", + "description": "A padded robe for protection", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 11, # 11,0 + "modifiers": {"def": 2}, + "buy_cost": 60, + "sell_worth": 18, + "rarity": ItemRarity.COMMON + }) + + _register_item("rags", { + "item_name": "Rags", + "description": "Tattered rags", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 12, # 12,0 + "modifiers": {"def": 0}, + "buy_cost": 10, + "sell_worth": 3, + "rarity": ItemRarity.COMMON + }) + + _register_item("leather_armour", { + "item_name": "Leather Armour", + "description": "A nice leather armour", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 13, # 13,0 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Tunic Body/LeatherTunic.png", + "modifiers": {"def": 3}, + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.COMMON + }) + + _register_item("plate", { + "item_name": "Plate", + "description": "Heavy plate armour", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 14, # 14,0 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/SteelArmour.png", + "modifiers": {"def": 5, "end": 1}, + "buy_cost": 150, + "sell_worth": 45, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("full_mail", { + "item_name": "Full Mail", + "description": "Complete chainmail armour", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 15, # 15,0 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/IronArmour.png", + "modifiers": {"def": 4}, + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("golden_mail", { + "item_name": "Golden Mail", + "description": "Exquisite golden chainmail", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ARMOUR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 16, # 16,0 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 2 - Clothes/Armour Body/GoldArmour.png", + "modifiers": {"def": 6, "end": 2}, + "buy_cost": 300, + "sell_worth": 90, + "rarity": ItemRarity.RARE + }) + + # OFFHAND items (shields, row 0) + _register_item("wooden_shield", { + "item_name": "Wooden Shield", + "description": "A basic wooden shield", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 17, # 17,0 + "modifiers": {"def": 2}, + "buy_cost": 40, + "sell_worth": 12, + "rarity": ItemRarity.COMMON + }) + + _register_item("sturdy_shield", { + "item_name": "Sturdy Shield", + "description": "A reinforced shield", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 18, # 18,0 + "modifiers": {"def": 3}, + "buy_cost": 70, + "sell_worth": 21, + "rarity": ItemRarity.COMMON + }) + + _register_item("metal_shield", { + "item_name": "Metal Shield", + "description": "A strong metal shield", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 0 * 20 + 19, # 19,0 + "modifiers": {"def": 4}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.UNCOMMON + }) + + # HEADGEAR items (row 1) + _register_item("beanie", { + "item_name": "Beanie", + "description": "A warm beanie", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 10, # 10,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Headband.png", + "modifiers": {"def": 1}, + "buy_cost": 20, + "sell_worth": 6, + "rarity": ItemRarity.COMMON + }) + + _register_item("leather_helm", { + "item_name": "Leather Helm", + "description": "A nice leather helm", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 11, # 11,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Melee/NoviceHelm.png", + "modifiers": {"def": 2}, + "buy_cost": 50, + "sell_worth": 15, + "rarity": ItemRarity.COMMON + }) + + _register_item("nomads_helm", { + "item_name": "Nomad's Helm", + "description": "A helm for travelers", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 12, # 12,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Range/RangerHatGreen.png", + "modifiers": {"def": 2, "dex": 1}, + "buy_cost": 60, + "sell_worth": 18, + "rarity": ItemRarity.COMMON + }) + + _register_item("strong_helm", { + "item_name": "Strong Helm", + "description": "A reinforced helm", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 13, # 13,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Melee/KnightHelm.png", + "modifiers": {"def": 3}, + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("plate_helm", { + "item_name": "Plate Helm", + "description": "Heavy plate helm", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 14, # 14,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Melee/SoldierSteelHelmBlue.png", + "modifiers": {"def": 4}, + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("warriors_helm", { + "item_name": "Warrior's Helm", + "description": "A helm for true warriors", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 15, # 15,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Melee/DragonKnightHelm.png", + "modifiers": {"def": 5, "str": 1}, + "buy_cost": 180, + "sell_worth": 54, + "rarity": ItemRarity.RARE + }) + + _register_item("paladins_helm", { + "item_name": "Paladin's Helm", + "description": "A blessed paladin helm", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.HEADGEAR, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 16, # 16,1 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 6 - Headgears/Basic Melee/PaladinHelmCyan.png", + "modifiers": {"def": 5, "wis": 1}, + "buy_cost": 200, + "sell_worth": 60, + "rarity": ItemRarity.RARE + }) + + # ACCESSORY items (row 1) + _register_item("amulet_of_strength", { + "item_name": "Amulet of Strength", + "description": "Grants +3 STR", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ACCESSORY, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 17, # 17,1 + "modifiers": {"str": 3}, + "buy_cost": 250, + "sell_worth": 75, + "rarity": ItemRarity.RARE + }) + + _register_item("elven_shield", { + "item_name": "Elven Shield", + "description": "An elegant elven shield", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 18, # 18,1 + "modifiers": {"def": 5, "dex": 1}, + "buy_cost": 180, + "sell_worth": 54, + "rarity": ItemRarity.RARE + }) + + _register_item("tower_shield", { + "item_name": "Tower Shield", + "description": "A massive tower shield", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 1 * 20 + 19, # 19,1 + "modifiers": {"def": 6, "end": 1}, + "buy_cost": 220, + "sell_worth": 66, + "rarity": ItemRarity.RARE + }) + + # BOOTS items (row 2) + _register_item("sandals", { + "item_name": "Sandals", + "description": "A pair of shitty sandals", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 10, # 10,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesBrown.png", + "modifiers": {"def": 1}, + "buy_cost": 15, + "sell_worth": 4, + "rarity": ItemRarity.COMMON + }) + + _register_item("leather_boots", { + "item_name": "Leather Boots", + "description": "Comfortable leather boots", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 11, # 11,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesBrown.png", + "modifiers": {"def": 2}, + "buy_cost": 40, + "sell_worth": 12, + "rarity": ItemRarity.COMMON + }) + + _register_item("sturdy_boots", { + "item_name": "Sturdy Boots", + "description": "Reinforced boots", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 12, # 12,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesMaple.png", + "modifiers": {"def": 3}, + "buy_cost": 60, + "sell_worth": 18, + "rarity": ItemRarity.COMMON + }) + + _register_item("smiths_boots", { + "item_name": "Smith's Boots", + "description": "Heavy boots for smiths", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 13, # 13,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/ShoesMaple.png", + "modifiers": {"def": 3, "end": 1}, + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("chain_boots", { + "item_name": "Chain Boots", + "description": "Chainmail boots", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 14, # 14,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png", + "modifiers": {"def": 4}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("plate_boots", { + "item_name": "Plate Boots", + "description": "Heavy plate boots", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 15, # 15,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png", + "modifiers": {"def": 5}, + "buy_cost": 140, + "sell_worth": 42, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("gold_boots", { + "item_name": "Gold Boots", + "description": "Exquisite golden boots", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.BOOTS, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 16, # 16,2 + "equipmentPath": "res://assets/gfx/Puny-Characters/Layer 1 - Shoes/IronBoots.png", + "modifiers": {"def": 6, "lck": 1}, + "buy_cost": 250, + "sell_worth": 75, + "rarity": ItemRarity.RARE + }) + + # ACCESSORY items (row 2) + _register_item("ring_of_healing", { + "item_name": "Ring of Healing", + "description": "Regenerates health over time", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ACCESSORY, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 17, # 17,2 + "modifiers": {"end": 2, "hp": 20}, + "buy_cost": 200, + "sell_worth": 60, + "rarity": ItemRarity.RARE + }) + + _register_item("ring_of_magic", { + "item_name": "Ring of Magic", + "description": "Increases mana and spell power", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ACCESSORY, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 18, # 18,2 + "modifiers": {"int": 2, "mp": 30}, + "buy_cost": 200, + "sell_worth": 60, + "rarity": ItemRarity.RARE + }) + + _register_item("ring_of_light", { + "item_name": "Ring of Light", + "description": "Increases light radius", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.ACCESSORY, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 2 * 20 + 19, # 19,2 + "modifiers": {"light_radius": 2}, + "buy_cost": 150, + "sell_worth": 45, + "rarity": ItemRarity.UNCOMMON + }) + + # WEAPONS - SWORDS (row 3) + _register_item("long_sword", { + "item_name": "Long Sword", + "description": "A standard longsword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 10, # 10,3 + "modifiers": {"dmg": 5}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.COMMON + }) + + _register_item("sword_of_blaze", { + "item_name": "Sword of Blaze", + "description": "A fiery sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 11, # 11,3 + "modifiers": {"dmg": 8, "int": 2}, + "buy_cost": 250, + "sell_worth": 75, + "rarity": ItemRarity.RARE + }) + + _register_item("excalibur", { + "item_name": "Excalibur", + "description": "The legendary sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 12, # 12,3 + "modifiers": {"dmg": 15, "str": 3, "wis": 2}, + "buy_cost": 1000, + "sell_worth": 300, + "rarity": ItemRarity.EPIC + }) + + _register_item("sword_of_the_wind", { + "item_name": "Sword of the Wind", + "description": "A swift sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 13, # 13,3 + "modifiers": {"dmg": 7, "dex": 3}, + "buy_cost": 300, + "sell_worth": 90, + "rarity": ItemRarity.RARE + }) + + _register_item("halbrand", { + "item_name": "Halbrand", + "description": "A powerful sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 14, # 14,3 + "modifiers": {"dmg": 12, "str": 2}, + "buy_cost": 400, + "sell_worth": 120, + "rarity": ItemRarity.RARE + }) + + _register_item("thunders_wrath", { + "item_name": "Thunder's Wrath", + "description": "A sword crackling with lightning", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 15, # 15,3 + "modifiers": {"dmg": 10, "int": 3}, + "buy_cost": 350, + "sell_worth": 105, + "rarity": ItemRarity.RARE + }) + + _register_item("bastard_sword", { + "item_name": "Bastard Sword", + "description": "A versatile sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 16, # 16,3 + "modifiers": {"dmg": 6}, + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.COMMON + }) + + _register_item("blade_of_darkness", { + "item_name": "Blade of Darkness", + "description": "A dark and deadly blade", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 17, # 17,3 + "modifiers": {"dmg": 11, "lck": 2}, + "buy_cost": 380, + "sell_worth": 114, + "rarity": ItemRarity.RARE + }) + + _register_item("golden_blade", { + "item_name": "Golden Blade", + "description": "An ornate golden sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 18, # 18,3 + "modifiers": {"dmg": 9, "lck": 3}, + "buy_cost": 320, + "sell_worth": 96, + "rarity": ItemRarity.RARE + }) + + _register_item("reapers_edge", { + "item_name": "Reaper's Edge", + "description": "A blade of death", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 3 * 20 + 19, # 19,3 + "modifiers": {"dmg": 13, "lck": 2, "str": 1}, + "buy_cost": 450, + "sell_worth": 135, + "rarity": ItemRarity.EPIC + }) + + # More SWORDS (row 4) + _register_item("singers_edge", { + "item_name": "Singer's Edge", + "description": "An elegant rapier", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 4 * 20 + 10, # 10,4 + "modifiers": {"dmg": 6, "dex": 2}, + "buy_cost": 150, + "sell_worth": 45, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("rapier", { + "item_name": "Rapier", + "description": "A precise rapier", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 4 * 20 + 11, # 11,4 + "modifiers": {"dmg": 5, "dex": 3}, + "buy_cost": 130, + "sell_worth": 39, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("buster_sword", { + "item_name": "Buster Sword", + "description": "A massive sword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "two_handed": true, + "spriteFrame": 4 * 20 + 12, # 12,4 + "modifiers": {"dmg": 14, "str": 2}, + "buy_cost": 500, + "sell_worth": 150, + "rarity": ItemRarity.EPIC + }) + + _register_item("doom_guard", { + "item_name": "Doom Guard", + "description": "A sword of doom", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 4 * 20 + 13, # 13,4 + "modifiers": {"dmg": 12, "str": 3}, + "buy_cost": 420, + "sell_worth": 126, + "rarity": ItemRarity.EPIC + }) + + _register_item("ice_age", { + "item_name": "Ice Age", + "description": "A frozen blade", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 4 * 20 + 14, # 14,4 + "modifiers": {"dmg": 10, "int": 2, "wis": 1}, + "buy_cost": 350, + "sell_worth": 105, + "rarity": ItemRarity.RARE + }) + + _register_item("god_blade", { + "item_name": "God Blade", + "description": "A divine blade", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 4 * 20 + 15, # 15,4 + "modifiers": {"dmg": 16, "str": 2, "wis": 3}, + "buy_cost": 800, + "sell_worth": 240, + "rarity": ItemRarity.EPIC + }) + + # DAGGERS (row 4) + _register_item("knife", { + "item_name": "Knife", + "description": "A sharp dagger", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.DAGGER, + "spriteFrame": 4 * 20 + 16, # 16,4 + "modifiers": {"dmg": 3, "dex": 1}, + "buy_cost": 40, + "sell_worth": 12, + "rarity": ItemRarity.COMMON + }) + + _register_item("black_shiv", { + "item_name": "Black Shiv", + "description": "A dark dagger", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.DAGGER, + "spriteFrame": 4 * 20 + 17, # 17,4 + "modifiers": {"dmg": 4, "dex": 2, "lck": 1}, + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("golden_knife", { + "item_name": "Golden Knife", + "description": "An ornate dagger", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.DAGGER, + "spriteFrame": 4 * 20 + 18, # 18,4 + "modifiers": {"dmg": 5, "lck": 2}, + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("bloodletter", { + "item_name": "Bloodletter", + "description": "A deadly dagger", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.DAGGER, + "spriteFrame": 4 * 20 + 19, # 19,4 + "modifiers": {"dmg": 6, "dex": 3, "lck": 1}, + "buy_cost": 180, + "sell_worth": 54, + "rarity": ItemRarity.RARE + }) + + # SWORDS and AXES (row 5) + _register_item("short_sword", { + "item_name": "Short Sword", + "description": "A basic shortsword", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SWORD, + "spriteFrame": 5 * 20 + 10, # 10,5 + "modifiers": {"dmg": 4}, + "buy_cost": 60, + "sell_worth": 18, + "rarity": ItemRarity.COMMON + }) + + _register_item("hatchet", { + "item_name": "Hatchet", + "description": "A small axe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.AXE, + "spriteFrame": 5 * 20 + 11, # 11,5 + "modifiers": {"dmg": 5, "str": 1}, + "buy_cost": 70, + "sell_worth": 21, + "rarity": ItemRarity.COMMON + }) + + _register_item("axe", { + "item_name": "Axe", + "description": "A standard axe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.AXE, + "spriteFrame": 5 * 20 + 12, # 12,5 + "modifiers": {"dmg": 7, "str": 1}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.COMMON + }) + + _register_item("golden_axe", { + "item_name": "Golden Axe", + "description": "An ornate golden axe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.AXE, + "spriteFrame": 5 * 20 + 13, # 13,5 + "modifiers": {"dmg": 9, "str": 2, "lck": 1}, + "buy_cost": 250, + "sell_worth": 75, + "rarity": ItemRarity.RARE + }) + + _register_item("blood_axe", { + "item_name": "Blood Axe", + "description": "A savage axe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.AXE, + "spriteFrame": 5 * 20 + 14, # 14,5 + "modifiers": {"dmg": 10, "str": 3}, + "buy_cost": 300, + "sell_worth": 90, + "rarity": ItemRarity.RARE + }) + + _register_item("battle_axe", { + "item_name": "Battle Axe", + "description": "A massive two-handed axe", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.AXE, + "two_handed": true, + "spriteFrame": 5 * 20 + 15, # 15,5 + "modifiers": {"dmg": 13, "str": 3}, + "buy_cost": 450, + "sell_worth": 135, + "rarity": ItemRarity.RARE + }) + + # STAVES (row 5) + _register_item("staff", { + "item_name": "Staff", + "description": "A basic staff", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.STAFF, + "two_handed": true, + "spriteFrame": 5 * 20 + 16, # 16,5 + "modifiers": {"dmg": 3, "int": 2, "mp": 20}, + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.COMMON + }) + + _register_item("staff_of_darkness", { + "item_name": "Staff of Darkness", + "description": "A dark staff", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.STAFF, + "two_handed": true, + "spriteFrame": 5 * 20 + 17, # 17,5 + "modifiers": {"dmg": 6, "int": 4, "mp": 40}, + "buy_cost": 350, + "sell_worth": 105, + "rarity": ItemRarity.RARE + }) + + _register_item("staff_of_light", { + "item_name": "Staff of Light", + "description": "A blessed staff", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.STAFF, + "two_handed": true, + "spriteFrame": 5 * 20 + 18, # 18,5 + "modifiers": {"dmg": 6, "wis": 4, "mp": 40}, + "buy_cost": 350, + "sell_worth": 105, + "rarity": ItemRarity.RARE + }) + + _register_item("staff_of_fire", { + "item_name": "Staff of Fire", + "description": "A fiery staff", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.STAFF, + "two_handed": true, + "spriteFrame": 5 * 20 + 19, # 19,5 + "modifiers": {"dmg": 7, "int": 3, "mp": 35}, + "buy_cost": 380, + "sell_worth": 114, + "rarity": ItemRarity.RARE + }) + + # More STAVES and SPEARS (row 6) + _register_item("staff_of_galactic_magic", { + "item_name": "Staff of Galactic Magic", + "description": "A legendary staff", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.STAFF, + "two_handed": true, + "spriteFrame": 6 * 20 + 10, # 10,6 + "modifiers": {"dmg": 10, "int": 5, "wis": 3, "mp": 60}, + "buy_cost": 700, + "sell_worth": 210, + "rarity": ItemRarity.EPIC + }) + + _register_item("spear", { + "item_name": "Spear", + "description": "A standard spear", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SPEAR, + "two_handed": true, + "spriteFrame": 6 * 20 + 11, # 11,6 + "modifiers": {"dmg": 6, "dex": 1}, + "buy_cost": 90, + "sell_worth": 27, + "rarity": ItemRarity.COMMON + }) + + _register_item("dark_spear", { + "item_name": "Dark Spear", + "description": "A dark spear", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SPEAR, + "two_handed": true, + "spriteFrame": 6 * 20 + 12, # 12,6 + "modifiers": {"dmg": 8, "dex": 2, "lck": 1}, + "buy_cost": 200, + "sell_worth": 60, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("golden_spear", { + "item_name": "Golden Spear", + "description": "An ornate spear", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SPEAR, + "two_handed": true, + "spriteFrame": 6 * 20 + 13, # 13,6 + "modifiers": {"dmg": 9, "dex": 2, "lck": 2}, + "buy_cost": 280, + "sell_worth": 84, + "rarity": ItemRarity.RARE + }) + + _register_item("blood_spear", { + "item_name": "Blood Spear", + "description": "A deadly spear", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.SPEAR, + "two_handed": true, + "spriteFrame": 6 * 20 + 14, # 14,6 + "modifiers": {"dmg": 10, "dex": 3}, + "buy_cost": 320, + "sell_worth": 96, + "rarity": ItemRarity.RARE + }) + + # MACE and BOWS (row 6) + _register_item("mace", { + "item_name": "Mace", + "description": "A heavy mace", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.MACE, + "spriteFrame": 6 * 20 + 15, # 15,6 + "modifiers": {"dmg": 8, "str": 2}, + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.COMMON + }) + + _register_item("short_bow", { + "item_name": "Short Bow", + "description": "A wooden bow made of elfish lembas bread", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.BOW, + "spriteFrame": 6 * 20 + 16, # 16,6 + "modifiers": {"dmg": 4, "dex": 2}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.COMMON + }) + + _register_item("dark_bow", { + "item_name": "Dark Bow", + "description": "A dark bow", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.BOW, + "spriteFrame": 6 * 20 + 17, # 17,6 + "modifiers": {"dmg": 6, "dex": 3, "lck": 1}, + "buy_cost": 220, + "sell_worth": 66, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("long_bow", { + "item_name": "Long Bow", + "description": "A powerful longbow", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.BOW, + "spriteFrame": 6 * 20 + 18, # 18,6 + "modifiers": {"dmg": 7, "dex": 2}, + "buy_cost": 180, + "sell_worth": 54, + "rarity": ItemRarity.UNCOMMON + }) + + _register_item("blood_bow", { + "item_name": "Blood Bow", + "description": "A deadly bow", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.BOW, + "spriteFrame": 6 * 20 + 19, # 19,6 + "modifiers": {"dmg": 8, "dex": 3}, + "buy_cost": 280, + "sell_worth": 84, + "rarity": ItemRarity.RARE + }) + + # More BOWS and AMMUNITION (row 7) + _register_item("strike_force", { + "item_name": "Strike Force", + "description": "A legendary bow", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.MAINHAND, + "weapon_type": Item.WeaponType.BOW, + "spriteFrame": 7 * 20 + 10, # 10,7 + "modifiers": {"dmg": 10, "dex": 4, "lck": 2}, + "buy_cost": 500, + "sell_worth": 150, + "rarity": ItemRarity.EPIC + }) + + _register_item("arrow", { + "item_name": "Arrow", + "description": "A sharp arrow made of iron and feathers from pelican birds", + "item_type": Item.ItemType.Equippable, + "equipment_type": Item.EquipmentType.OFFHAND, + "weapon_type": Item.WeaponType.AMMUNITION, + "spriteFrame": 7 * 20 + 11, # 11,7 + "quantity": 13, + "can_have_multiple_of": true, + "modifiers": {"dmg": 2}, + "buy_cost": 20, + "sell_worth": 6, + "rarity": ItemRarity.COMMON + }) + + # CONSUMABLE FOOD ITEMS (row 7) + _register_item("cheese", { + "item_name": "Cheese", + "description": "Restores 10 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 12, # 12,7 + "modifiers": {"hp": 10}, + "buy_cost": 15, + "sell_worth": 4, + "rarity": ItemRarity.COMMON + }) + + _register_item("meat", { + "item_name": "Meat", + "description": "Restores 20 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 13, # 13,7 + "modifiers": {"hp": 20}, + "buy_cost": 25, + "sell_worth": 7, + "rarity": ItemRarity.COMMON + }) + + _register_item("egg", { + "item_name": "Egg", + "description": "Restores 3 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 14, # 14,7 + "modifiers": {"hp": 3}, + "buy_cost": 5, + "sell_worth": 1, + "rarity": ItemRarity.COMMON + }) + + _register_item("fish", { + "item_name": "Fish", + "description": "Restores 5 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 15, # 15,7 + "modifiers": {"hp": 5}, + "buy_cost": 8, + "sell_worth": 2, + "rarity": ItemRarity.COMMON + }) + + _register_item("chicken_leg", { + "item_name": "Chicken Leg", + "description": "Restores 8 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 16, # 16,7 + "modifiers": {"hp": 8}, + "buy_cost": 12, + "sell_worth": 3, + "rarity": ItemRarity.COMMON + }) + + _register_item("sandwich", { + "item_name": "Sandwich", + "description": "Restores 10 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 17, # 17,7 + "modifiers": {"hp": 10}, + "buy_cost": 15, + "sell_worth": 4, + "rarity": ItemRarity.COMMON + }) + + _register_item("potato", { + "item_name": "Potato", + "description": "Restores 2 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 18, # 18,7 + "modifiers": {"hp": 2}, + "buy_cost": 3, + "sell_worth": 1, + "rarity": ItemRarity.COMMON + }) + + _register_item("steak", { + "item_name": "Steak", + "description": "Restores 14 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 7 * 20 + 19, # 19,7 + "modifiers": {"hp": 14}, + "buy_cost": 20, + "sell_worth": 6, + "rarity": ItemRarity.COMMON + }) + + # More CONSUMABLE ITEMS (row 8) + _register_item("cookie", { + "item_name": "Cookie", + "description": "Restores 4 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 13, # 13,8 + "modifiers": {"hp": 4}, + "buy_cost": 6, + "sell_worth": 2, + "rarity": ItemRarity.COMMON + }) + + _register_item("candy", { + "item_name": "Candy", + "description": "Restores 7 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 14, # 14,8 + "modifiers": {"hp": 7}, + "buy_cost": 10, + "sell_worth": 3, + "rarity": ItemRarity.COMMON + }) + + _register_item("healing_potion", { + "item_name": "Healing Potion", + "description": "Restores 50 HP", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 15, # 15,8 + "modifiers": {"hp": 50}, + "buy_cost": 50, + "sell_worth": 15, + "rarity": ItemRarity.CONSUMABLE + }) + + _register_item("rejuvenation_potion", { + "item_name": "Rejuvenation Potion", + "description": "Restores 75 HP and 75 MANA", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 16, # 16,8 + "modifiers": {"hp": 75, "mp": 75}, + "buy_cost": 100, + "sell_worth": 30, + "rarity": ItemRarity.CONSUMABLE + }) + + _register_item("dodge_potion", { + "item_name": "Dodge Potion", + "description": "Grants higher dodge chance", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 17, # 17,8 + "modifiers": {"dodge_chance": 0.15}, # +15% dodge chance (temporary) + "duration": 60.0, # 60 seconds + "buy_cost": 80, + "sell_worth": 24, + "rarity": ItemRarity.CONSUMABLE + }) + + _register_item("mana_potion", { + "item_name": "Mana Potion", + "description": "Restores 50 MANA", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 18, # 18,8 + "modifiers": {"mp": 50}, + "buy_cost": 40, + "sell_worth": 12, + "rarity": ItemRarity.CONSUMABLE + }) + + _register_item("resistance_potion", { + "item_name": "Resistance Potion", + "description": "+25% all resistances", + "item_type": Item.ItemType.Restoration, + "equipment_type": Item.EquipmentType.NONE, + "weapon_type": Item.WeaponType.NONE, + "spriteFrame": 8 * 20 + 19, # 19,8 + "modifiers": {"res_all": 25}, # +25% to all resistances + "duration": 120.0, # 120 seconds + "buy_cost": 120, + "sell_worth": 36, + "rarity": ItemRarity.CONSUMABLE + }) + +# Register an item in the database +static func _register_item(item_id: String, item_data: Dictionary): + item_data["item_id"] = item_id + item_definitions[item_id] = item_data + +# Create an Item instance from an item ID +static func create_item(item_id: String) -> Item: + _initialize() + + if not item_definitions.has(item_id): + push_error("ItemDatabase: Item ID '%s' not found!" % item_id) + return null + + var item_data = item_definitions[item_id].duplicate(true) + var item = Item.new() + + # Set all properties from item_data + item.item_name = item_data.get("item_name", "Unknown Item") + item.description = item_data.get("description", "") + item.item_type = item_data.get("item_type", Item.ItemType.Restoration) + item.equipment_type = item_data.get("equipment_type", Item.EquipmentType.NONE) + item.weapon_type = item_data.get("weapon_type", Item.WeaponType.NONE) + item.spriteFrame = item_data.get("spriteFrame", 0) + item.modifiers = item_data.get("modifiers", {}).duplicate(true) + item.buy_cost = item_data.get("buy_cost", 10) + item.sell_worth = item_data.get("sell_worth", 3) + item.two_handed = item_data.get("two_handed", false) + item.quantity = item_data.get("quantity", 1) + item.can_have_multiple_of = item_data.get("can_have_multiple_of", false) + item.duration = item_data.get("duration", 0.0) + + # spritePath defaults to items_n_shit.png in Item class, which is correct + # spriteFrames defaults to Vector2i(20,14) in Item class, which is correct + + if item_data.has("equipmentPath"): + item.equipmentPath = item_data["equipmentPath"] + + if item_data.has("colorReplacements"): + item.colorReplacements = item_data["colorReplacements"].duplicate(true) + + # Remove item_id from data (internal use only) + item_data.erase("item_id") + item_data.erase("rarity") # Remove rarity (internal use only) + + return item + +# Get a random item by rarity +static func get_random_item_by_rarity(rarity: ItemRarity) -> Item: + _initialize() + + var candidates = [] + for item_id in item_definitions.keys(): + var item_data = item_definitions[item_id] + if item_data.has("rarity") and item_data["rarity"] == rarity: + candidates.append(item_id) + + if candidates.is_empty(): + # Fallback to common items + return get_random_item_by_rarity(ItemRarity.COMMON) + + var random_item_id = candidates[randi() % candidates.size()] + return create_item(random_item_id) + +# Get a random item (weighted by rarity) +static func get_random_item() -> Item: + _initialize() + + # Weighted random: 50% common, 25% uncommon, 15% rare, 5% epic, 5% consumable + var roll = randf() + var rarity: ItemRarity + + if roll < 0.5: + rarity = ItemRarity.COMMON + elif roll < 0.75: + rarity = ItemRarity.UNCOMMON + elif roll < 0.90: + rarity = ItemRarity.RARE + elif roll < 0.95: + rarity = ItemRarity.EPIC + else: + rarity = ItemRarity.CONSUMABLE + + return get_random_item_by_rarity(rarity) + +# Get random items for enemies (weighted towards common/uncommon) +static func get_random_enemy_drop() -> Item: + _initialize() + + # Enemies: 60% common, 30% uncommon, 8% rare, 2% epic/consumable + var roll = randf() + var rarity: ItemRarity + + if roll < 0.6: + rarity = ItemRarity.COMMON + elif roll < 0.9: + rarity = ItemRarity.UNCOMMON + elif roll < 0.98: + rarity = ItemRarity.RARE + else: + # 50/50 between epic and consumable + rarity = ItemRarity.EPIC if randf() < 0.5 else ItemRarity.CONSUMABLE + + return get_random_item_by_rarity(rarity) + +# Get random items for chests (better loot) +static func get_random_chest_item() -> Item: + _initialize() + + # Chests: 40% common, 35% uncommon, 20% rare, 5% epic/consumable + var roll = randf() + var rarity: ItemRarity + + if roll < 0.4: + rarity = ItemRarity.COMMON + elif roll < 0.75: + rarity = ItemRarity.UNCOMMON + elif roll < 0.95: + rarity = ItemRarity.RARE + else: + rarity = ItemRarity.EPIC if randf() < 0.5 else ItemRarity.CONSUMABLE + + return get_random_item_by_rarity(rarity) + +# Get random items for destroyed containers (mostly consumables, some equipment) +static func get_random_container_item() -> Item: + _initialize() + + # Containers: 50% food (common restoration), 30% common equipment, 15% uncommon, 5% rare + var roll = randf() + + if roll < 0.5: + # Food items (restoration items with rarity COMMON) + var food_items = [] + for item_id in item_definitions.keys(): + var item_data = item_definitions[item_id] + if item_data.has("rarity") and item_data["rarity"] == ItemRarity.COMMON: + if item_data.has("item_type") and item_data["item_type"] == Item.ItemType.Restoration: + food_items.append(item_id) + + if not food_items.is_empty(): + var random_food_id = food_items[randi() % food_items.size()] + return create_item(random_food_id) + + # Fall back to normal random item with adjusted weights + roll = randf() + var rarity: ItemRarity + if roll < 0.6: + rarity = ItemRarity.COMMON + elif roll < 0.85: + rarity = ItemRarity.UNCOMMON + else: + rarity = ItemRarity.RARE + + return get_random_item_by_rarity(rarity) diff --git a/src/scripts/item_database.gd.uid b/src/scripts/item_database.gd.uid new file mode 100644 index 0000000..276dfab --- /dev/null +++ b/src/scripts/item_database.gd.uid @@ -0,0 +1 @@ +uid://drle8r6y7mwq8 diff --git a/src/scripts/item_loot_helper.gd b/src/scripts/item_loot_helper.gd new file mode 100644 index 0000000..96f2704 --- /dev/null +++ b/src/scripts/item_loot_helper.gd @@ -0,0 +1,73 @@ +class_name ItemLootHelper +extends RefCounted + +# Helper class for spawning Item instances as loot + +# Spawn an Item instance as loot at a position +static func spawn_item_loot(item: Item, position: Vector2, entities_node: Node, game_world: Node = null) -> Node: + if not item: + push_error("ItemLootHelper: Cannot spawn null item!") + return null + + var loot_scene = preload("res://scenes/loot.tscn") + if not loot_scene: + push_error("ItemLootHelper: Could not load loot.tscn scene!") + return null + + # Generate random velocity for physics + var random_angle = randf() * PI * 2 + var random_force = randf_range(50.0, 100.0) + var random_velocity_z = randf_range(80.0, 120.0) + var initial_velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force + + # Find safe spawn position if game_world is provided + var safe_spawn_pos = position + if game_world and game_world.has_method("_find_nearby_safe_spawn_position"): + safe_spawn_pos = game_world._find_nearby_safe_spawn_position(position, 64.0) + + # Spawn loot instance + var loot = loot_scene.instantiate() + if not entities_node: + push_error("ItemLootHelper: entities_node is null!") + return null + + # Set properties before adding to scene tree (to avoid physics state change errors) + loot.global_position = safe_spawn_pos + loot.loot_type = loot.LootType.ITEM + loot.item = item # Set the item instance + # Set initial velocity before _ready() processes + loot.velocity = initial_velocity + loot.velocity_z = random_velocity_z + loot.velocity_set_by_spawner = true + loot.is_airborne = true + + # Add to scene tree using call_deferred to avoid physics state change errors + entities_node.call_deferred("add_child", loot) + + # Set multiplayer authority to server if in multiplayer + # Since this is a static helper function (RefCounted), we can't access the global multiplayer directly + # Instead, check if game_world is provided and has the sync method (which implies multiplayer) + # We'll also check if entities_node is in a scene tree with multiplayer + var is_multiplayer = false + if game_world and game_world.has_method("_sync_item_loot_spawn"): + # If game_world has the sync method, we're likely in multiplayer + # Use a script call to check multiplayer (game_world is a Node and has access to multiplayer) + # We'll rely on the sync method existence as an indicator + is_multiplayer = true + + if is_multiplayer: + loot.set_multiplayer_authority(1) + + # Sync to clients if game_world is provided + if game_world and game_world.has_method("_sync_item_loot_spawn"): + # Generate unique loot ID + # loot_id_counter is declared as a variable in game_world.gd, so it always exists + var loot_id = game_world.loot_id_counter + game_world.loot_id_counter += 1 + loot.set_meta("loot_id", loot_id) + + # Sync item data to clients + game_world._sync_item_loot_spawn.rpc(safe_spawn_pos, item.save(), initial_velocity, random_velocity_z, loot_id) + + print("ItemLootHelper: Spawned item loot: ", item.item_name, " at ", safe_spawn_pos) + return loot diff --git a/src/scripts/item_loot_helper.gd.uid b/src/scripts/item_loot_helper.gd.uid new file mode 100644 index 0000000..bd2f258 --- /dev/null +++ b/src/scripts/item_loot_helper.gd.uid @@ -0,0 +1 @@ +uid://2jn53es04orw diff --git a/src/scripts/loot.gd b/src/scripts/loot.gd index 993ffe0..b1ca6f1 100644 --- a/src/scripts/loot.gd +++ b/src/scripts/loot.gd @@ -7,7 +7,8 @@ enum LootType { APPLE, BANANA, CHERRY, - KEY + KEY, + ITEM # Item instance (equipment, consumables, etc.) } @export var loot_type: LootType = LootType.COIN @@ -29,6 +30,7 @@ var bounce_timer: float = 0.0 # Prevent rapid bounce sounds var coin_value: int = 1 var heal_amount: float = 20.0 var collected: bool = false +var item: Item = null # Item instance (for LootType.ITEM) @onready var sprite = $Sprite2D @onready var shadow = $Shadow @@ -125,6 +127,20 @@ func _setup_sprite(): sprite.hframes = 20 sprite.vframes = 14 sprite.frame = (13 * 20) + 10 + LootType.ITEM: + # Item instance - use item's spritePath and spriteFrame + if item and item.spritePath != "": + var items_texture = load(item.spritePath) + if items_texture: + sprite.texture = items_texture + sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20 + sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14 + sprite.frame = item.spriteFrame + print("Loot: Set up item sprite for ", item.item_name, " frame=", sprite.frame) + else: + print("Loot: ERROR - Could not load texture from spritePath: ", item.spritePath) + else: + print("Loot: ERROR - Item loot has no item instance or spritePath! item=", item) func _setup_collision_shape(): if not collision_shape: @@ -278,6 +294,21 @@ func _animate_coin(delta): func _on_pickup_area_body_entered(body): if body and body.is_in_group("player") and not body.is_dead: + # Check if this item was dropped by this player recently (5 second cooldown) + if has_meta("dropped_by_peer_id") and has_meta("drop_time"): + var dropped_by_peer_id = get_meta("dropped_by_peer_id") + var drop_time = get_meta("drop_time") + var current_time = Time.get_ticks_msec() + var time_since_drop = (current_time - drop_time) / 1000.0 # Convert to seconds + + # Check if this player dropped the item and cooldown hasn't expired + if body.has_method("get_multiplayer_authority"): + var player_peer_id = body.get_multiplayer_authority() + if player_peer_id == dropped_by_peer_id and time_since_drop < 5.0: + # Player can't pick up their own dropped item for 5 seconds + print("Loot: Player ", body.name, " cannot pick up item they dropped (", time_since_drop, "s ago, need 5s cooldown)") + return + print("Loot: Pickup area entered by player: ", body.name, " is_local: ", body.is_local_player if "is_local_player" in body else "unknown", " is_server: ", multiplayer.is_server()) _pickup(body) @@ -456,6 +487,61 @@ func _process_pickup_on_server(player: Node): if sfx_key_collect and sfx_key_collect.playing: await $SfxKeyCollect.finished queue_free() + LootType.ITEM: + # Item instance pickup + if not item: + print("Loot: ERROR - Item loot has no item instance!") + queue_free() + return + + if sfx_loot_collect: + sfx_loot_collect.play() + + # Handle item pickup based on type + if item.item_type == Item.ItemType.Equippable: + # Equippable item - add to inventory + if player.character_stats: + player.character_stats.add_item(item) + print(name, " picked up item: ", item.item_name, " (added to inventory)") + elif item.item_type == Item.ItemType.Restoration: + # Consumable item - use immediately + if player.character_stats: + # Apply modifiers (hp, mp, etc.) + if item.modifiers.has("hp"): + var hp_heal = item.modifiers["hp"] + if player.has_method("heal"): + player.heal(hp_heal) + if item.modifiers.has("mp"): + var mana_amount = item.modifiers["mp"] + player.character_stats.restore_mana(mana_amount) + + # TODO: Handle other modifiers (dodge_chance, res_all, etc.) - these would need duration tracking + + print(name, " used item: ", item.item_name) + + # Show floating text with item name + var items_texture = load(item.spritePath) + var display_text = item.item_name + var text_color = Color.WHITE + + # Color code based on item type + if item.item_type == Item.ItemType.Equippable: + text_color = Color.CYAN # Cyan for equipment + elif item.item_type == Item.ItemType.Restoration: + text_color = Color.GREEN # Green for consumables + + _show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame) + + # Sync floating text to client + if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1: + _sync_show_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, 0, item.spriteFrame, player.get_multiplayer_authority()) + + self.visible = false + + # Wait for sound to finish before removing + if sfx_loot_collect and sfx_loot_collect.playing: + await sfx_loot_collect.finished + queue_free() var processing_pickup: bool = false # Mutex to prevent concurrent pickup processing @@ -526,7 +612,7 @@ func _sync_remove(): LootType.COIN: if sfx_coin_collect: sfx_coin_collect.play() - LootType.APPLE, LootType.BANANA, LootType.CHERRY: + LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM: if sfx_loot_collect: sfx_loot_collect.play() @@ -543,7 +629,7 @@ func _sync_remove(): if loot_type == LootType.COIN and sfx_coin_collect and sfx_coin_collect.playing: _sound_playing = true await sfx_coin_collect.finished - elif loot_type in [LootType.APPLE, LootType.BANANA, LootType.CHERRY] and sfx_loot_collect and sfx_loot_collect.playing: + elif loot_type in [LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM] and sfx_loot_collect and sfx_loot_collect.playing: _sound_playing = true await sfx_loot_collect.finished @@ -582,6 +668,13 @@ func _sync_show_floating_text(loot_type_value: int, text: String, color_value: C item_texture = load("res://assets/gfx/pickups/items_n_shit.png") sprite_hframes = 20 sprite_vframes = 14 + LootType.ITEM: + # Item instance - use item's sprite path + # Note: item data is not available on client in this sync, so we use default + # The actual item sprite is set when the loot is created + item_texture = load("res://assets/gfx/pickups/items_n_shit.png") + sprite_hframes = 20 + sprite_vframes = 14 # Show floating text on client _show_floating_text(player, text, color_value, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame_value) diff --git a/src/scripts/player.gd b/src/scripts/player.gd index 71c791e..617d62d 100644 --- a/src/scripts/player.gd +++ b/src/scripts/player.gd @@ -389,6 +389,11 @@ func _initialize_character_stats(): # Initialize health/mana from stats character_stats.hp = character_stats.maxhp character_stats.mp = character_stats.maxmp + + # Connect signals + if character_stats: + character_stats.level_up_stats.connect(_on_level_up_stats) + character_stats.character_changed.connect(_on_character_changed) func _randomize_stats(): # Randomize base stats within reasonable ranges @@ -471,13 +476,33 @@ func _apply_appearance_to_sprites(): sprite_body.vframes = 8 sprite_body.modulate = Color.WHITE # Remove old color tint - # Boots - empty (bare) + # Boots if sprite_boots: - sprite_boots.texture = null + var equipped_boots = character_stats.equipment["boots"] + if equipped_boots and equipped_boots.equipmentPath != "": + var boots_texture = load(equipped_boots.equipmentPath) + if boots_texture: + sprite_boots.texture = boots_texture + sprite_boots.hframes = 35 + sprite_boots.vframes = 8 + else: + sprite_boots.texture = null + else: + sprite_boots.texture = null - # Armour - empty (bare) + # Armour if sprite_armour: - sprite_armour.texture = null + var equipped_armour = character_stats.equipment["armour"] + if equipped_armour and equipped_armour.equipmentPath != "": + var armour_texture = load(equipped_armour.equipmentPath) + if armour_texture: + sprite_armour.texture = armour_texture + sprite_armour.hframes = 35 + sprite_armour.vframes = 8 + else: + sprite_armour.texture = null + else: + sprite_armour.texture = null # Facial Hair if sprite_facial_hair: @@ -546,19 +571,35 @@ func _apply_appearance_to_sprites(): else: sprite_addons.texture = null - # Headgear - empty (bare) + # Headgear if sprite_headgear: - sprite_headgear.texture = null + var equipped_headgear = character_stats.equipment["headgear"] + if equipped_headgear and equipped_headgear.equipmentPath != "": + var headgear_texture = load(equipped_headgear.equipmentPath) + if headgear_texture: + sprite_headgear.texture = headgear_texture + sprite_headgear.hframes = 35 + sprite_headgear.vframes = 8 + else: + sprite_headgear.texture = null + else: + sprite_headgear.texture = null - # Weapon - empty (bare) + # Weapon (Mainhand) + # NOTE: Weapons should NEVER use equipmentPath - they don't have character sprite sheets + # Weapons are only displayed as inventory icons (spritePath), not as character sprite layers if sprite_weapon: - sprite_weapon.texture = null + sprite_weapon.texture = null # Weapons don't use character sprite layers print(name, " appearance applied: skin=", character_stats.skin, " hair=", character_stats.hairstyle, " facial_hair=", character_stats.facial_hair, " eyes=", character_stats.eyes) +func _on_character_changed(_char: CharacterStats): + # Update appearance when character stats change (e.g., equipment) + _apply_appearance_to_sprites() + func _get_player_color() -> Color: # Legacy function - now returns white (no color tint) return Color.WHITE @@ -1582,15 +1623,40 @@ func _perform_attack(): # Delay before spawning sword slash await get_tree().create_timer(0.15).timeout + # Calculate damage from character_stats with randomization + var base_damage = 20.0 # Default damage + if character_stats: + base_damage = character_stats.damage + + # D&D style randomization: ±20% variance + var damage_variance = 0.2 + var damage_multiplier = 1.0 + randf_range(-damage_variance, damage_variance) + var final_damage = base_damage * damage_multiplier + + # Critical strike chance (based on LCK stat) + var crit_chance = 0.0 + if character_stats: + crit_chance = (character_stats.baseStats.lck + character_stats.get_pass("lck")) * 0.01 # 1% per LCK point + var is_crit = randf() < crit_chance + if is_crit: + final_damage *= 2.0 # Critical strikes deal 2x damage + print(name, " CRITICAL STRIKE! (LCK: ", character_stats.baseStats.lck + character_stats.get_pass("lck"), ")") + + # Round to 1 decimal place + final_damage = round(final_damage * 10.0) / 10.0 + # Spawn sword projectile if sword_projectile_scene: var projectile = sword_projectile_scene.instantiate() get_parent().add_child(projectile) - projectile.setup(attack_direction, self) + projectile.setup(attack_direction, self, final_damage) + # Store crit status for visual feedback + if is_crit: + projectile.set_meta("is_crit", true) # Spawn projectile a bit in front of the player var spawn_offset = attack_direction * 10.0 # 10 pixels in front projectile.global_position = global_position + spawn_offset - print(name, " attacked with sword projectile!") + print(name, " attacked with sword projectile! Damage: ", final_damage, " (base: ", base_damage, ", crit: ", is_crit, ")") # Sync attack over network if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree(): @@ -2093,13 +2159,37 @@ func take_damage(amount: float, attacker_position: Vector2): if is_dead: return - # Take damage using character_stats + # Check for dodge chance (based on DEX) + var _was_dodged = false if character_stats: - character_stats.take_damage(amount, false) # false = not magical damage - print(name, " took ", amount, " damage! Health: ", character_stats.hp, "/", character_stats.maxhp) + var dodge_roll = randf() + var dodge_chance = character_stats.dodge_chance + if dodge_roll < dodge_chance: + _was_dodged = true + print(name, " DODGED the attack! (DEX: ", character_stats.baseStats.dex + character_stats.get_pass("dex"), ", dodge chance: ", dodge_chance * 100.0, "%)") + # Show "DODGED" text + _show_damage_number(0.0, attacker_position, false, false, true) # is_dodged = true + # Sync dodge visual to other clients + if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): + _sync_damage.rpc(0.0, attacker_position, false, false, true) # is_dodged = true + return # No damage taken, exit early + + # If not dodged, apply damage with DEF reduction + var actual_damage = amount + if character_stats: + # Calculate damage after DEF reduction (critical hits pierce 80% of DEF) + actual_damage = character_stats.calculate_damage(amount, false, false) # false = not magical, false = not critical (enemy attacks don't crit yet) + # Apply the reduced damage using take_damage (which handles health modification and signals) + var _old_hp = character_stats.hp + character_stats.modify_health(-actual_damage) + if character_stats.hp <= 0: + character_stats.no_health.emit() + character_stats.character_changed.emit(character_stats) + print(name, " took ", actual_damage, " damage (", amount, " base - ", character_stats.defense, " DEF = ", actual_damage, ")! Health: ", character_stats.hp, "/", character_stats.maxhp) else: # Fallback for legacy current_health -= amount + actual_damage = amount print(name, " took ", amount, " damage! Health: ", current_health) # Play damage sound effect @@ -2129,11 +2219,11 @@ func take_damage(amount: float, attacker_position: Vector2): tween.tween_property(sprite_body, "modulate", Color.WHITE, 0.1) # Show damage number (red, using dmg_numbers.png font) - _show_damage_number(amount, attacker_position) + _show_damage_number(actual_damage, attacker_position) # Sync damage visual effects to other clients (including damage numbers) if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree(): - _sync_damage.rpc(amount, attacker_position) + _sync_damage.rpc(actual_damage, attacker_position) # Check if dead - but wait for damage animation to play first var health = character_stats.hp if character_stats else current_health @@ -2493,11 +2583,9 @@ func use_key(): return false @rpc("authority", "reliable") -func _show_damage_number(amount: float, from_position: Vector2): +func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false): # Show damage number (red, using dmg_numbers.png font) above player - # Only show if damage > 0 - if amount <= 0: - return + # Show even if amount is 0 for MISS/DODGED var damage_number_scene = preload("res://scenes/damage_number.tscn") if not damage_number_scene: @@ -2507,9 +2595,16 @@ func _show_damage_number(amount: float, from_position: Vector2): if not damage_label: return - # Set damage text and red color - damage_label.label = str(int(amount)) - damage_label.color = Color.RED + # Set text and color based on type + if is_dodged: + damage_label.label = "DODGED" + damage_label.color = Color.CYAN + elif is_miss: + damage_label.label = "MISS" + damage_label.color = Color.GRAY + else: + damage_label.label = str(int(amount)) + damage_label.color = Color.ORANGE if is_crit else Color.RED # Calculate direction from attacker (slight upward variation) var direction_from_attacker = (global_position - from_position).normalized() @@ -2531,12 +2626,81 @@ func _show_damage_number(amount: float, from_position: Vector2): get_tree().current_scene.add_child(damage_label) damage_label.global_position = global_position + Vector2(0, -16) +func _on_level_up_stats(stats_increased: Array): + # Show floating text for level up - "LEVEL UP!" and stat increases + # Use damage_number scene with damage_numbers font + if not character_stats: + return + + # Stat name to display name mapping + var stat_display_names = { + "str": "STR", + "dex": "DEX", + "int": "INT", + "end": "END", + "wis": "WIS", + "lck": "LCK" + } + + # Stat name to color mapping + var stat_colors = { + "str": Color.RED, + "dex": Color.GREEN, + "int": Color.BLUE, + "end": Color.WHITE, + "wis": Color(0.5, 0.0, 0.5), # Purple + "lck": Color.YELLOW + } + + var damage_number_scene = preload("res://scenes/damage_number.tscn") + if not damage_number_scene: + return + + # Get entities node for adding text + var game_world = get_tree().get_first_node_in_group("game_world") + var entities_node = null + if game_world: + entities_node = game_world.get_node_or_null("Entities") + if not entities_node: + entities_node = get_tree().current_scene + + var base_y_offset = -32.0 # Start above player head + var y_spacing = 12.0 # Space between each text + + # Show "LEVEL UP!" first (in white) + var level_up_text = damage_number_scene.instantiate() + if level_up_text: + level_up_text.label = "LEVEL UP!" + level_up_text.color = Color.WHITE + level_up_text.direction = Vector2(0, -1) # Straight up + entities_node.add_child(level_up_text) + level_up_text.global_position = global_position + Vector2(0, base_y_offset) + base_y_offset -= y_spacing + + # Show each stat increase + for i in range(stats_increased.size()): + var stat_name = stats_increased[i] + var stat_text = damage_number_scene.instantiate() + if stat_text: + var display_name = stat_display_names.get(stat_name, stat_name.to_upper()) + stat_text.label = "+1 " + display_name + stat_text.color = stat_colors.get(stat_name, Color.WHITE) + stat_text.direction = Vector2(randf_range(-0.2, 0.2), -1.0).normalized() # Slight random spread + entities_node.add_child(stat_text) + stat_text.global_position = global_position + Vector2(0, base_y_offset) + base_y_offset -= y_spacing + @rpc("any_peer", "reliable") -func _sync_damage(_amount: float, attacker_position: Vector2): +func _sync_damage(_amount: float, attacker_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false): # This RPC only syncs visual effects, not damage application # (damage is already applied via rpc_take_damage) if not is_multiplayer_authority(): - # Play damage sound effect on clients + # If dodged, only show dodge text, no other effects + if is_dodged: + _show_damage_number(0.0, attacker_position, false, false, true) + return + + # Play damage sound and effects if sfx_take_damage: sfx_take_damage.play() @@ -2561,6 +2725,9 @@ func _sync_damage(_amount: float, attacker_position: Vector2): var tween = create_tween() tween.tween_property(sprite_body, "modulate", Color.RED, 0.1) tween.tween_property(sprite_body, "modulate", Color.WHITE, 0.1) + + # Show damage number + _show_damage_number(_amount, attacker_position, is_crit, is_miss, false) func on_grabbed(by_player): print(name, " grabbed by ", by_player.name) diff --git a/src/scripts/sword_projectile.gd b/src/scripts/sword_projectile.gd index c833405..d5f0377 100644 --- a/src/scripts/sword_projectile.gd +++ b/src/scripts/sword_projectile.gd @@ -25,9 +25,10 @@ func _ready(): if hit_area: hit_area.body_entered.connect(_on_body_entered) -func setup(direction: Vector2, owner_player: Node): +func setup(direction: Vector2, owner_player: Node, damage_value: float = 20.0): travel_direction = direction.normalized() player_owner = owner_player + damage = damage_value # Set damage from player current_speed = initial_speed # Rotate sprite to face travel direction @@ -102,19 +103,37 @@ func _on_body_entered(body): # Deal damage to enemies - only authority (creator) deals damage elif body.is_in_group("enemy") and body.has_method("rpc_take_damage"): - $SfxImpact.play() var attacker_pos = player_owner.global_position if player_owner else global_position + var is_crit = get_meta("is_crit") if has_meta("is_crit") else false + + # Check hit chance (based on player's DEX stat) + var hit_roll = randf() + var hit_chance = 0.95 # Base hit chance + if player_owner and player_owner.character_stats: + hit_chance = player_owner.character_stats.hit_chance + var is_miss = hit_roll >= hit_chance + + if is_miss: + # Attack missed - show MISS text, don't deal damage, don't play impact sound + print("Player MISSED enemy: ", body.name, "! (hit chance: ", hit_chance * 100.0, "%)") + # Show MISS text on the enemy + if body.has_method("_show_damage_number"): + body._show_damage_number(0.0, attacker_pos, false, true, false) # is_miss = true + return # Don't deal damage, don't play impact sound, don't cause knockback + + # Hit successful - play impact sound and deal damage + $SfxImpact.play() var enemy_peer_id = body.get_multiplayer_authority() if enemy_peer_id != 0: # If enemy is on the same peer (server), call directly if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id(): - body.rpc_take_damage(damage, attacker_pos) + body.rpc_take_damage(damage, attacker_pos, is_crit) else: # Send RPC to enemy's authority (server) - clients can do this! - body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos) + body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, is_crit) else: # Fallback: broadcast if we can't get peer_id - body.rpc_take_damage.rpc(damage, attacker_pos) + body.rpc_take_damage.rpc(damage, attacker_pos, is_crit) # Debug print - handle null player_owner safely var owner_name: String = "none" var is_authority: bool = false