Implements modifier system for weapons
Adds a modifier system allowing dynamic modification of weapon stats and behavior. This includes: - Creating ModifierLibrary to manage available modifiers. - Adds ModifierManager to handle equipping and unequipping modifiers - Adds a new RangedWeaponComponent to handle firing projectiles and managing modifiers. - Introduces a DebugUI for in-game modifier management. - Introduces an "Unlimited Power" modifier that changes the projectile scene. - Modifies stats components to work with the new modifier system. This system allows for more flexible and customizable weapon functionality.
This commit is contained in:
parent
9f66ab0a73
commit
70839387ca
22 changed files with 432 additions and 40 deletions
85
UI/DebugUI.tscn
Normal file
85
UI/DebugUI.tscn
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://b0w0oxbtax5si"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bblhlxj8sqtql" path="res://UI/_scripts/debug_ui.gd" id="1_pwlud"]
|
||||||
|
|
||||||
|
[node name="DebugUi" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 0
|
||||||
|
script = ExtResource("1_pwlud")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
offset_left = 13.0
|
||||||
|
offset_top = 15.0
|
||||||
|
offset_right = 91.0
|
||||||
|
offset_bottom = 173.0
|
||||||
|
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="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="FPS" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="FPSLabel" type="Label" parent="MarginContainer/VBoxContainer/FPS"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "FPS: "
|
||||||
|
|
||||||
|
[node name="FPSValue" type="Label" parent="MarginContainer/VBoxContainer/FPS"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "123"
|
||||||
|
|
||||||
|
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PlayerWeapon" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="PlayerWeaponLabel" type="Label" parent="MarginContainer/VBoxContainer/PlayerWeapon"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Player Weapon:"
|
||||||
|
|
||||||
|
[node name="PlayerWeaponValue" type="Label" parent="MarginContainer/VBoxContainer/PlayerWeapon"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="PlayerWeaponMods" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 2
|
||||||
|
|
||||||
|
[node name="PlayerWeaponModsLabel" type="Label" parent="MarginContainer/VBoxContainer/PlayerWeaponMods"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Mods:"
|
||||||
|
|
||||||
|
[node name="PlayerWeaponModsValue" type="Label" parent="MarginContainer/VBoxContainer/PlayerWeaponMods"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "123
|
||||||
|
456
|
||||||
|
678"
|
||||||
|
|
||||||
|
[node name="ModifierButtons" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/ModifierButtons"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "123"
|
||||||
|
|
||||||
|
[node name="PlayerStats" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 2
|
||||||
|
|
||||||
|
[node name="PlayerStatsValue" type="Label" parent="MarginContainer/VBoxContainer/PlayerStats"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
text = "123
|
||||||
|
456
|
||||||
|
678"
|
||||||
107
UI/_scripts/debug_ui.gd
Normal file
107
UI/_scripts/debug_ui.gd
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
extends Control
|
||||||
|
class_name DebugUI
|
||||||
|
|
||||||
|
var fps_label: Label
|
||||||
|
var player_weapon_mods_label: Label
|
||||||
|
|
||||||
|
var player: Player
|
||||||
|
var was_debug_key_pressed: bool = false
|
||||||
|
|
||||||
|
var is_mouse_over_ui: bool = false
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
self.visible = false
|
||||||
|
player = get_tree().get_first_node_in_group("player")
|
||||||
|
|
||||||
|
fps_label = %FPSValue
|
||||||
|
player_weapon_mods_label = %PlayerWeaponModsValue
|
||||||
|
|
||||||
|
self.position = Vector2.ZERO
|
||||||
|
self.size = get_viewport_rect().size
|
||||||
|
self.scale = Vector2(1.2, 1.2)
|
||||||
|
|
||||||
|
# Connect the mouse entered/exited signals to track when mouse is over UI
|
||||||
|
self.mouse_entered.connect(_on_mouse_entered)
|
||||||
|
self.mouse_exited.connect(_on_mouse_exited)
|
||||||
|
|
||||||
|
# Make the UI control consume input events
|
||||||
|
self.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
|
||||||
|
func _on_mouse_entered() -> void:
|
||||||
|
is_mouse_over_ui = true
|
||||||
|
|
||||||
|
func _on_mouse_exited() -> void:
|
||||||
|
is_mouse_over_ui = false
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
# Only process if debug UI is visible
|
||||||
|
if not self.visible:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if it's a mouse button event and if mouse is over UI
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if is_mouse_over_ui:
|
||||||
|
# Consume the event so it doesn't propagate to the game
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if Input.is_action_pressed("debug_menu"):
|
||||||
|
if not was_debug_key_pressed:
|
||||||
|
populate_modifier_buttons()
|
||||||
|
was_debug_key_pressed = true
|
||||||
|
self.visible = !self.visible
|
||||||
|
else:
|
||||||
|
was_debug_key_pressed = false
|
||||||
|
|
||||||
|
var current_fps = Engine.get_frames_per_second()
|
||||||
|
fps_label.text = str(current_fps)
|
||||||
|
|
||||||
|
var player_stats = player.stats.get_nested_stat("ranged")
|
||||||
|
var player_stats_label = %PlayerStatsValue
|
||||||
|
player_stats_label.text = ""
|
||||||
|
for stat in player_stats:
|
||||||
|
player_stats_label.text += str(stat) + ": " + str(player_stats[stat]) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
var player_mods = player.ranged.modifier_manager.modifiers
|
||||||
|
player_weapon_mods_label.text = ""
|
||||||
|
for mod in player_mods:
|
||||||
|
player_weapon_mods_label.text += str(mod.id) + " (" + str(mod.description) + ") \n"
|
||||||
|
|
||||||
|
|
||||||
|
func populate_modifier_buttons() -> void:
|
||||||
|
# Clear existing buttons
|
||||||
|
for child in %ModifierButtons.get_children():
|
||||||
|
if child is Button:
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
# Create buttons for each modifier
|
||||||
|
for modifier in ModLib.get_all_modifiers():
|
||||||
|
var button = Button.new()
|
||||||
|
button.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
button.toggle_mode = true
|
||||||
|
button.text = modifier.id + " [" + modifier.description + "]"
|
||||||
|
|
||||||
|
# If the modifier exists in the player's weapon, set the button to "pressed"
|
||||||
|
if player.ranged.modifier_manager.has_modifier(modifier.id):
|
||||||
|
button.button_pressed = true
|
||||||
|
else:
|
||||||
|
button.button_pressed = false
|
||||||
|
|
||||||
|
# Store modifier id in the button's metadata for reference in the callback
|
||||||
|
button.set_meta("modifier_id", modifier.id)
|
||||||
|
|
||||||
|
# Connect the toggled signal to a callback function
|
||||||
|
button.toggled.connect(_on_modifier_button_toggled.bind(modifier.id))
|
||||||
|
|
||||||
|
%ModifierButtons.add_child(button)
|
||||||
|
|
||||||
|
func _on_modifier_button_toggled(button_pressed: bool, modifier_id: String) -> void:
|
||||||
|
if button_pressed:
|
||||||
|
# Add the modifier if the button is pressed
|
||||||
|
var modifier = ModLib.get_modifier_by_id(modifier_id)
|
||||||
|
if modifier:
|
||||||
|
player.ranged.modifier_manager.add_modifier(modifier)
|
||||||
|
else:
|
||||||
|
# Remove the modifier if the button is unpressed
|
||||||
|
player.ranged.modifier_manager.remove_modifier(modifier_id)
|
||||||
1
UI/_scripts/debug_ui.gd.uid
Normal file
1
UI/_scripts/debug_ui.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bblhlxj8sqtql
|
||||||
16
assets/themes/main_theme.tres
Normal file
16
assets/themes/main_theme.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://ddgipwj5yd0vw"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6ioqn"]
|
||||||
|
bg_color = Color(0.6, 0.6, 0.6, 0.560784)
|
||||||
|
border_width_left = 1
|
||||||
|
border_width_top = 1
|
||||||
|
border_width_right = 1
|
||||||
|
border_width_bottom = 1
|
||||||
|
border_color = Color(0.888331, 0.274714, 0.539223, 1)
|
||||||
|
corner_radius_top_left = 3
|
||||||
|
corner_radius_top_right = 3
|
||||||
|
corner_radius_bottom_right = 3
|
||||||
|
corner_radius_bottom_left = 3
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
Button/styles/pressed = SubResource("StyleBoxFlat_6ioqn")
|
||||||
|
|
@ -18,6 +18,12 @@ func _ready() -> void:
|
||||||
func set_stats(stats: StatsComponent) -> void:
|
func set_stats(stats: StatsComponent) -> void:
|
||||||
self.stats_component = stats
|
self.stats_component = stats
|
||||||
|
|
||||||
|
func has_modifier(modifier_id: String) -> bool:
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.id == modifier_id:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
func add_modifier(modifier: Modifier) -> void:
|
func add_modifier(modifier: Modifier) -> void:
|
||||||
modifiers.append(modifier)
|
modifiers.append(modifier)
|
||||||
modifier.on_equip(get_parent())
|
modifier.on_equip(get_parent())
|
||||||
|
|
@ -63,6 +69,29 @@ func recalculate_stats() -> void:
|
||||||
|
|
||||||
emit_signal("stats_updated")
|
emit_signal("stats_updated")
|
||||||
|
|
||||||
|
func check_callable(func_name: String) -> Callable:
|
||||||
|
# Create a default callable that does nothing and returns null
|
||||||
|
var default_callable = func(): return null
|
||||||
|
|
||||||
|
# Check each modifier in priority order
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.has_method(func_name):
|
||||||
|
# Return the callable from this modifier
|
||||||
|
return Callable(modifier, func_name)
|
||||||
|
|
||||||
|
# Return the default callable if no modifier has the function
|
||||||
|
return default_callable
|
||||||
|
|
||||||
|
# Convenience method to check and call in one step
|
||||||
|
func check_and_call(func_name: String, args := []):
|
||||||
|
var callable = check_callable(func_name)
|
||||||
|
if callable.is_valid():
|
||||||
|
if args.size() > 0:
|
||||||
|
return callable.callv(args)
|
||||||
|
else:
|
||||||
|
return callable.call()
|
||||||
|
return null
|
||||||
|
|
||||||
func _apply_modifier_stats(modifier: Modifier) -> void:
|
func _apply_modifier_stats(modifier: Modifier) -> void:
|
||||||
if modifier.has_method("apply_stats_modification"):
|
if modifier.has_method("apply_stats_modification"):
|
||||||
modifier.apply_stats_modification(stats_component)
|
modifier.apply_stats_modification(stats_component)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ class_name FireRateAdditive extends Modifier
|
||||||
func _init():
|
func _init():
|
||||||
id = "fire_rate_additive"
|
id = "fire_rate_additive"
|
||||||
display_name = "Rapid Fire"
|
display_name = "Rapid Fire"
|
||||||
description = "Increases fire rate by %0.1f shots per second" % fire_rate_bonus
|
description = "Increases fire rate by %0.1f shots per round" % fire_rate_bonus
|
||||||
modifier_type = ModifierType.ADDITIVE
|
modifier_type = ModifierType.ADDITIVE
|
||||||
|
|
||||||
func apply_stats_modification(stats: StatsComponent) -> void:
|
func apply_stats_modification(stats: StatsComponent) -> void:
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,5 @@ func _init():
|
||||||
description = "Increases fire rate by %d%%" % ((fire_rate_multiplier - 1.0) * 100)
|
description = "Increases fire rate by %d%%" % ((fire_rate_multiplier - 1.0) * 100)
|
||||||
modifier_type = ModifierType.MULTIPLICATIVE
|
modifier_type = ModifierType.MULTIPLICATIVE
|
||||||
|
|
||||||
func apply_stats_modification(final_stats: Dictionary, _base_stats: Dictionary) -> void:
|
func apply_stats_modification(stats: StatsComponent) -> void:
|
||||||
if final_stats.has("fire_rate"):
|
stats.update_stat("ranged.attack_rate", stats.get_stat("ranged.attack_rate") * fire_rate_multiplier)
|
||||||
final_stats.fire_rate *= fire_rate_multiplier
|
|
||||||
|
|
@ -9,6 +9,5 @@ func _init():
|
||||||
modifier_type = ModifierType.MULTIPLICATIVE
|
modifier_type = ModifierType.MULTIPLICATIVE
|
||||||
priority = 10 # Higher priority than the additive version
|
priority = 10 # Higher priority than the additive version
|
||||||
|
|
||||||
func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void:
|
func apply_stats_modification(stats: StatsComponent) -> void:
|
||||||
if final_stats.has("projectile_size"):
|
stats.update_stat("ranged.projectile_size", stats.get_stat("ranged.projectile_size") * size_multiplier)
|
||||||
final_stats.projectile_size *= size_multiplier
|
|
||||||
17
combat/modifiers/modifiers/unlimited_power.gd
Normal file
17
combat/modifiers/modifiers/unlimited_power.gd
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
class_name UnlimitedPower extends Modifier
|
||||||
|
|
||||||
|
@export var fire_rate_bonus: float = 1.0 # +1 shot per second
|
||||||
|
@export var ranged_damage: float = 99.5
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
id = "unlimited_power"
|
||||||
|
display_name = "Unlimited Power"
|
||||||
|
description = "Shoot lightning bolts. Fire rate + %0.1f. Ranged damage + %0.1f" % [fire_rate_bonus, ranged_damage]
|
||||||
|
modifier_type = ModifierType.ADDITIVE
|
||||||
|
|
||||||
|
func apply_stats_modification(stats: StatsComponent) -> void:
|
||||||
|
stats.update_stat("ranged.attack_rate", stats.get_stat("ranged.attack_rate") + fire_rate_bonus)
|
||||||
|
stats.update_stat("ranged.damage", stats.get_stat("ranged.damage") + ranged_damage)
|
||||||
|
|
||||||
|
func set_projectile_scene(weapon: RangedWeaponComponent) -> void:
|
||||||
|
weapon.projectile_scene = preload("res://assets/projectiles/projectile_lightning.tscn")
|
||||||
1
combat/modifiers/modifiers/unlimited_power.gd.uid
Normal file
1
combat/modifiers/modifiers/unlimited_power.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b21isiwfqrjyi
|
||||||
|
|
@ -3,15 +3,68 @@ extends Node2D
|
||||||
class_name RangedWeaponComponent
|
class_name RangedWeaponComponent
|
||||||
|
|
||||||
@export var stats: StatsComponent
|
@export var stats: StatsComponent
|
||||||
|
@export var basic_projectile: PackedScene = preload("res://assets/projectiles/basic_projectile.tscn")
|
||||||
|
|
||||||
@onready var modifier_manager = $ModifierManager
|
@onready var modifier_manager = $ModifierManager
|
||||||
|
@onready var projectile_scene: PackedScene = basic_projectile
|
||||||
|
|
||||||
|
var can_fire: bool = true
|
||||||
|
var cooldown: Timer
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
cooldown = Timer.new()
|
||||||
|
add_child(cooldown)
|
||||||
|
cooldown.one_shot = true
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
Log.pr("RangedWeaponComponent initialized")
|
Log.pr("RangedWeaponComponent initialized")
|
||||||
modifier_manager.set_stats(stats)
|
modifier_manager.set_stats(stats)
|
||||||
|
modifier_manager.connect("modifier_added", _on_modifier_added)
|
||||||
|
modifier_manager.connect("modifier_removed", _on_modifier_removed)
|
||||||
|
cooldown.connect("timeout", _on_fire_timer_timeout)
|
||||||
|
|
||||||
func add_modifier(modifier: Modifier) -> void:
|
func add_modifier(modifier: Modifier) -> void:
|
||||||
modifier_manager.add_modifier(modifier)
|
modifier_manager.add_modifier(modifier)
|
||||||
|
|
||||||
func remove_modifier(modifier_id: String) -> void:
|
func remove_modifier(modifier_id: String) -> void:
|
||||||
modifier_manager.remove_modifier(modifier_id)
|
modifier_manager.remove_modifier(modifier_id)
|
||||||
|
|
||||||
|
func fire(direction: Vector2, target_position: Vector2) -> void:
|
||||||
|
if !can_fire:
|
||||||
|
return
|
||||||
|
|
||||||
|
spawn_projectile(global_position, direction, target_position)
|
||||||
|
|
||||||
|
can_fire = false
|
||||||
|
cooldown.start(1 / stats.get_stat("ranged.attack_rate"))
|
||||||
|
|
||||||
|
func set_projectile_scene() -> void:
|
||||||
|
projectile_scene = basic_projectile
|
||||||
|
modifier_manager.check_and_call("set_projectile_scene", [self])
|
||||||
|
|
||||||
|
func spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2, target_position: Vector2) -> void:
|
||||||
|
Log.pr("Spawning projectile")
|
||||||
|
modifier_manager.check_and_call("spawn_projectile", [self])
|
||||||
|
|
||||||
|
## TODO: Handle multiple shots per fire
|
||||||
|
|
||||||
|
var projectile = projectile_scene.instantiate()
|
||||||
|
projectile.global_position = spawn_position
|
||||||
|
projectile.target_position = target_position
|
||||||
|
projectile.speed = 200 # stats.get_stat("projectile_speed")
|
||||||
|
projectile.damage = 10 # stats.get_stat("damage")
|
||||||
|
projectile.lifetime = 2 # stats.get_stat("projectile_lifetime")
|
||||||
|
projectile.source_weapon = self
|
||||||
|
var size = stats.get_stat("ranged.projectile_size")
|
||||||
|
projectile.set_projectile_scale(Vector2(size, size))
|
||||||
|
if get_tree() and get_tree().get_root():
|
||||||
|
get_tree().get_root().add_child(projectile)
|
||||||
|
|
||||||
|
func _on_modifier_added(_modifier: Modifier) -> void:
|
||||||
|
set_projectile_scene()
|
||||||
|
|
||||||
|
func _on_modifier_removed(_modifier: Modifier) -> void:
|
||||||
|
set_projectile_scene()
|
||||||
|
|
||||||
|
func _on_fire_timer_timeout() -> void:
|
||||||
|
can_fire = true
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,14 @@ var base_stats: Dictionary[String, Variant] = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats: Dictionary
|
var stats: Dictionary = {}
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
if not stats:
|
if not stats:
|
||||||
reset_stats()
|
reset_stats()
|
||||||
|
|
||||||
func reset_stats() -> void:
|
func reset_stats() -> void:
|
||||||
stats = base_stats.duplicate()
|
stats = base_stats.duplicate(true)
|
||||||
Log.pr("StatsComponent reset to base stats")
|
Log.pr("StatsComponent reset to base stats")
|
||||||
|
|
||||||
func get_stat(stat_name: String) -> Variant:
|
func get_stat(stat_name: String) -> Variant:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=86 format=3 uid="uid://bo5aw2cad3akl"]
|
[gd_scene load_steps=87 format=3 uid="uid://bo5aw2cad3akl"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://bq038uo4cm6nv" path="res://player/scripts/player.gd" id="1_oul6g"]
|
[ext_resource type="Script" uid="uid://bq038uo4cm6nv" path="res://player/scripts/player.gd" id="1_oul6g"]
|
||||||
[ext_resource type="Texture2D" uid="uid://dqgq2c1h6yk3k" path="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png" id="2_yllr7"]
|
[ext_resource type="Texture2D" uid="uid://dqgq2c1h6yk3k" path="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png" id="2_yllr7"]
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
[ext_resource type="PackedScene" uid="uid://dud7c465danl4" path="res://combat/weapons/RangedWeaponComponent.tscn" id="15_wodsf"]
|
[ext_resource type="PackedScene" uid="uid://dud7c465danl4" path="res://combat/weapons/RangedWeaponComponent.tscn" id="15_wodsf"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dqful6et42ok8" path="res://combat/weapons/MeleeWeaponComponent.tscn" id="16_32hag"]
|
[ext_resource type="PackedScene" uid="uid://dqful6et42ok8" path="res://combat/weapons/MeleeWeaponComponent.tscn" id="16_32hag"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bjybfg0xrowb5" path="res://entities/StatsComponent.tscn" id="17_tqiix"]
|
[ext_resource type="PackedScene" uid="uid://bjybfg0xrowb5" path="res://entities/StatsComponent.tscn" id="17_tqiix"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b0w0oxbtax5si" path="res://UI/DebugUI.tscn" id="18_e7oew"]
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_rkbax"]
|
[sub_resource type="CircleShape2D" id="CircleShape2D_rkbax"]
|
||||||
|
|
||||||
|
|
@ -545,11 +546,12 @@ animations = [{
|
||||||
"speed": 5.0
|
"speed": 5.0
|
||||||
}]
|
}]
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("ranged", "melee") groups=["friendly"]]
|
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("ranged", "melee", "stats") groups=["friendly", "player"]]
|
||||||
collision_mask = 14
|
collision_mask = 14
|
||||||
script = ExtResource("1_oul6g")
|
script = ExtResource("1_oul6g")
|
||||||
ranged = NodePath("RangedWeaponComponent")
|
ranged = NodePath("RangedWeaponComponent")
|
||||||
melee = NodePath("MeleeWeaponComponent")
|
melee = NodePath("MeleeWeaponComponent")
|
||||||
|
stats = NodePath("StatsComponent")
|
||||||
|
|
||||||
[node name="PlayerCollision" type="CollisionShape2D" parent="."]
|
[node name="PlayerCollision" type="CollisionShape2D" parent="."]
|
||||||
position = Vector2(0, 7)
|
position = Vector2(0, 7)
|
||||||
|
|
@ -572,3 +574,15 @@ stats = NodePath("../StatsComponent")
|
||||||
[node name="MeleeWeaponComponent" parent="." instance=ExtResource("16_32hag")]
|
[node name="MeleeWeaponComponent" parent="." instance=ExtResource("16_32hag")]
|
||||||
|
|
||||||
[node name="StatsComponent" parent="." instance=ExtResource("17_tqiix")]
|
[node name="StatsComponent" parent="." instance=ExtResource("17_tqiix")]
|
||||||
|
|
||||||
|
[node name="CanvasLayer" type="CanvasLayer" parent="."]
|
||||||
|
layer = 100
|
||||||
|
|
||||||
|
[node name="DebugUi" parent="CanvasLayer" instance=ExtResource("18_e7oew")]
|
||||||
|
offset_left = -288.0
|
||||||
|
offset_top = -161.0
|
||||||
|
offset_right = 870.0
|
||||||
|
offset_bottom = 492.0
|
||||||
|
scale = Vector2(0.499389, 0.499439)
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
extends CharacterBody2D
|
extends CharacterBody2D
|
||||||
|
|
||||||
|
class_name Player
|
||||||
|
|
||||||
@export var speed = 200
|
@export var speed = 200
|
||||||
@export var special_ability: Ability
|
@export var special_ability: Ability
|
||||||
@export var ranged: RangedWeaponComponent
|
@export var ranged: RangedWeaponComponent
|
||||||
@export var melee: MeleeWeaponComponent
|
@export var melee: MeleeWeaponComponent
|
||||||
|
@export var stats: StatsComponent
|
||||||
|
|
||||||
var movement: PlayerMovement
|
var movement: PlayerMovement
|
||||||
var combat: PlayerCombat
|
var combat: PlayerCombat
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ var explosion_projectile_speed: float = 300.0
|
||||||
var explosion_spread_angle: float = 360.0 # Full circle by default
|
var explosion_spread_angle: float = 360.0 # Full circle by default
|
||||||
|
|
||||||
# References
|
# References
|
||||||
var source_weapon: RangedWeapon # Reference to the weapon that fired this
|
var source_weapon: RangedWeaponComponent # Reference to the weapon that fired this
|
||||||
var lifetime_timer: Timer
|
var lifetime_timer: Timer
|
||||||
# Add a variable to track the entity that triggered the explosion
|
# Add a variable to track the entity that triggered the explosion
|
||||||
var ignore_target = []
|
var ignore_target = []
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,6 @@ func add_modifier(modifier: Modifier):
|
||||||
func remove_modifier(modifier_id: String):
|
func remove_modifier(modifier_id: String):
|
||||||
pass
|
pass
|
||||||
#stats.remove_modifier(modifier_id)
|
#stats.remove_modifier(modifier_id)
|
||||||
|
|
||||||
func _on_stats_updated():
|
func _on_stats_updated():
|
||||||
# Update any visual components based on new stats
|
# Update any visual components based on new stats
|
||||||
# For example, if weapon appearance changes based on damage/fire rate
|
# For example, if weapon appearance changes based on damage/fire rate
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ Global="*res://utility/Globals.gd"
|
||||||
SceneSelector="*res://utility/SceneSelector.gd"
|
SceneSelector="*res://utility/SceneSelector.gd"
|
||||||
MapBuilder="*res://utility/MapBuilder.gd"
|
MapBuilder="*res://utility/MapBuilder.gd"
|
||||||
DebugMenu="*res://addons/debug_menu/debug_menu.tscn"
|
DebugMenu="*res://addons/debug_menu/debug_menu.tscn"
|
||||||
|
ModLib="*res://utility/ModifierLibrary.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
|
@ -36,11 +37,17 @@ enabled=PackedStringArray("res://addons/debug_menu/plugin.cfg", "res://addons/lo
|
||||||
[file_customization]
|
[file_customization]
|
||||||
|
|
||||||
folder_colors={
|
folder_colors={
|
||||||
|
"res://UI/": "green",
|
||||||
"res://combat/": "red",
|
"res://combat/": "red",
|
||||||
"res://combat/modifiers/": "green",
|
"res://combat/modifiers/": "green",
|
||||||
"res://combat/weapons/": "red"
|
"res://combat/weapons/": "red",
|
||||||
|
"res://utility/": "purple"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[gui]
|
||||||
|
|
||||||
|
theme/custom="uid://ddgipwj5yd0vw"
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
move_up={
|
move_up={
|
||||||
|
|
@ -68,6 +75,11 @@ fire={
|
||||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(115, 24),"global_position":Vector2(129, 97),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(115, 24),"global_position":Vector2(129, 97),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
debug_menu={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
[layer_names]
|
[layer_names]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,10 @@ class_name MapBuilderClass
|
||||||
# Function to copy a TileMap layer from one scene to a target TileMap at a specific grid position
|
# Function to copy a TileMap layer from one scene to a target TileMap at a specific grid position
|
||||||
func copy_tilemap_to_target(source_scene, target_tilemap: TileMapLayer, target_layer: String, grid_position: Vector2i):
|
func copy_tilemap_to_target(source_scene, target_tilemap: TileMapLayer, target_layer: String, grid_position: Vector2i):
|
||||||
# First, load and instantiate the source scene if it's a resource path
|
# First, load and instantiate the source scene if it's a resource path
|
||||||
Log.pr("Copying tilemap from source scene to target tilemap at grid position: ", grid_position)
|
|
||||||
Log.pr("Source scene: ", source_scene)
|
|
||||||
var source_instance
|
var source_instance
|
||||||
if source_scene is String:
|
if source_scene is String:
|
||||||
source_instance = load(source_scene).instantiate()
|
source_instance = load(source_scene).instantiate()
|
||||||
elif source_scene is PackedScene:
|
elif source_scene is PackedScene:
|
||||||
Log.pr("Source scene is a PackedScene, instantiating it")
|
|
||||||
source_instance = source_scene.instantiate()
|
source_instance = source_scene.instantiate()
|
||||||
else:
|
else:
|
||||||
source_instance = source_scene
|
source_instance = source_scene
|
||||||
|
|
@ -68,8 +65,6 @@ func find_tilemap_by_name(root_node: Node, tilemap_name: String) -> TileMapLayer
|
||||||
|
|
||||||
|
|
||||||
func redraw_terrain(positions: Array, layer: TileMapLayer, terrain_set: int, terrain: int) -> void:
|
func redraw_terrain(positions: Array, layer: TileMapLayer, terrain_set: int, terrain: int) -> void:
|
||||||
Log.pr("Filtering and redrawing surrounding tiles", positions)
|
|
||||||
|
|
||||||
# Filter positions to only include cells with terrainset 0 and terrain 0
|
# Filter positions to only include cells with terrainset 0 and terrain 0
|
||||||
var filtered_positions: Array = []
|
var filtered_positions: Array = []
|
||||||
for cell in positions:
|
for cell in positions:
|
||||||
|
|
@ -80,7 +75,6 @@ func redraw_terrain(positions: Array, layer: TileMapLayer, terrain_set: int, ter
|
||||||
|
|
||||||
# Only redraw if we have filtered cells
|
# Only redraw if we have filtered cells
|
||||||
if filtered_positions.size() > 0:
|
if filtered_positions.size() > 0:
|
||||||
Log.pr("Redrawing filtered tiles:", filtered_positions)
|
|
||||||
layer.set_cells_terrain_connect(filtered_positions, terrain_set, terrain)
|
layer.set_cells_terrain_connect(filtered_positions, terrain_set, terrain)
|
||||||
else:
|
else:
|
||||||
Log.pr("No tiles to redraw after filtering")
|
Log.pr("No tiles to redraw after filtering")
|
||||||
|
|
|
||||||
62
utility/ModifierLibrary.gd
Normal file
62
utility/ModifierLibrary.gd
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
extends Node
|
||||||
|
class_name ModifierLibrary
|
||||||
|
|
||||||
|
var modifiers: Array[Modifier] = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
## Check the modifier folder for any scripts that extend the Modifier class
|
||||||
|
var modifier_folder = "res://combat/modifiers/modifiers/"
|
||||||
|
|
||||||
|
var dir = DirAccess.open(modifier_folder)
|
||||||
|
|
||||||
|
if dir:
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var file_name = dir.get_next()
|
||||||
|
while file_name != "":
|
||||||
|
if file_name.ends_with(".gd"):
|
||||||
|
var script_path = modifier_folder + file_name
|
||||||
|
var script = load(script_path)
|
||||||
|
|
||||||
|
var temp_instance = script.new() if script else null
|
||||||
|
var is_modifier = temp_instance is Modifier if temp_instance else false
|
||||||
|
|
||||||
|
if is_modifier and file_name != "modifier.gd":
|
||||||
|
modifiers.append(temp_instance)
|
||||||
|
|
||||||
|
file_name = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
|
else:
|
||||||
|
print("Failed to open directory: ", modifier_folder)
|
||||||
|
|
||||||
|
Log.pr("ModifierLibrary initialized with %d modifiers" % modifiers.size())
|
||||||
|
Log.pr("Modifiers: ", modifiers)
|
||||||
|
|
||||||
|
func get_modifier_by_id(modifier_id: String) -> Modifier:
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.id == modifier_id:
|
||||||
|
return modifier.duplicate()
|
||||||
|
return null
|
||||||
|
|
||||||
|
func get_modifiers_by_type(modifier_type: int) -> Array[Modifier]:
|
||||||
|
var result: Array[Modifier] = []
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.modifier_type == modifier_type:
|
||||||
|
result.append(modifier)
|
||||||
|
return result
|
||||||
|
|
||||||
|
func get_modifiers_by_rarity(rarity: int) -> Array[Modifier]:
|
||||||
|
var result: Array[Modifier] = []
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.rarity == rarity:
|
||||||
|
result.append(modifier)
|
||||||
|
return result
|
||||||
|
|
||||||
|
func get_modifiers_by_priority(priority: int) -> Array[Modifier]:
|
||||||
|
var result: Array[Modifier] = []
|
||||||
|
for modifier in modifiers:
|
||||||
|
if modifier.priority == priority:
|
||||||
|
result.append(modifier)
|
||||||
|
return result
|
||||||
|
|
||||||
|
func get_all_modifiers() -> Array[Modifier]:
|
||||||
|
return modifiers
|
||||||
1
utility/ModifierLibrary.gd.uid
Normal file
1
utility/ModifierLibrary.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://di51ocdha4fx7
|
||||||
|
|
@ -29,7 +29,7 @@ func initialize_cache(folder_path: String, preload_scenes: bool = false):
|
||||||
scene_files.append(folder_path + "/" + file_name)
|
scene_files.append(folder_path + "/" + file_name)
|
||||||
file_name = dir.get_next()
|
file_name = dir.get_next()
|
||||||
|
|
||||||
Log.pr("Found %d scene files in %s" % [scene_files.size(), folder_path])
|
#Log.pr("Found %d scene files in %s" % [scene_files.size(), folder_path])
|
||||||
# Store in cache
|
# Store in cache
|
||||||
_scene_paths_cache[folder_path] = scene_files
|
_scene_paths_cache[folder_path] = scene_files
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ func initialize_cache(folder_path: String, preload_scenes: bool = false):
|
||||||
if preload_scenes and scene_files.size() > 0:
|
if preload_scenes and scene_files.size() > 0:
|
||||||
_loaded_scenes_cache[folder_path] = []
|
_loaded_scenes_cache[folder_path] = []
|
||||||
for scene_path in scene_files:
|
for scene_path in scene_files:
|
||||||
Log.pr("Preloading scene: " + scene_path)
|
#Log.pr("Preloading scene: " + scene_path)
|
||||||
# Load the scene and store it in the cache
|
# Load the scene and store it in the cache
|
||||||
#var loaded_scene = preload(scene_path)
|
#var loaded_scene = preload(scene_path)
|
||||||
_loaded_scenes_cache[folder_path].append(load(scene_path))
|
_loaded_scenes_cache[folder_path].append(load(scene_path))
|
||||||
|
|
@ -47,7 +47,7 @@ func initialize_cache(folder_path: String, preload_scenes: bool = false):
|
||||||
func get_random_scene(folder_path: String):
|
func get_random_scene(folder_path: String):
|
||||||
# Make sure the cache is initialized
|
# Make sure the cache is initialized
|
||||||
if not _scene_paths_cache.has(folder_path):
|
if not _scene_paths_cache.has(folder_path):
|
||||||
Log.pr("Initializing cache for folder: " + folder_path)
|
#Log.pr("Initializing cache for folder: " + folder_path)
|
||||||
initialize_cache(folder_path, true)
|
initialize_cache(folder_path, true)
|
||||||
|
|
||||||
var scene_paths = _scene_paths_cache[folder_path]
|
var scene_paths = _scene_paths_cache[folder_path]
|
||||||
|
|
@ -60,10 +60,10 @@ func get_random_scene(folder_path: String):
|
||||||
|
|
||||||
# Return from loaded scenes cache if available
|
# Return from loaded scenes cache if available
|
||||||
if _loaded_scenes_cache.has(folder_path):
|
if _loaded_scenes_cache.has(folder_path):
|
||||||
Log.pr("Returning preloaded scene from cache")
|
#Log.pr("Returning preloaded scene from cache")
|
||||||
return _loaded_scenes_cache[folder_path][random_index]
|
return _loaded_scenes_cache[folder_path][random_index]
|
||||||
|
|
||||||
# Otherwise load the scene
|
# Otherwise load the scene
|
||||||
var scene = load(scene_paths[random_index])
|
var scene = load(scene_paths[random_index])
|
||||||
Log.pr(scene)
|
#Log.pr(scene)
|
||||||
return scene
|
return scene
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue