RPG Stats Resource-based stats, modifier stacks, and derived calculations define flexible character progression. Available Scripts stat_resource.gd Robust Resource-based stat system with caching, dirty flags, and modifier stacks. modifier_stack_stats.gd Expert stat system with additive/multiplicative modifier stacks and priority ordering. NEVER Do in RPG Stats NEVER use int for percentages — var critical_chance: int = 50 for 50%? Integer division = truncation errors. Use float (0.0-1.0 OR 0.0-100.0). NEVER modify stats without signals — UI showing health bar but stats.current_health -= 10 doesn't update? MUST emit signals on stat changes. NEVER use additive-only modifiers — Buff adds +10 strength on level 1 (10 base) = 100% increase. Same buff on level 50 (100 base) = 10% increase. Use multiplicative OR hybrid. NEVER skip modifier IDs — add_modifier("strength", 5) without ID? Can't remove specific buffs later. MUST use unique IDs (e.g., "sword_buff", "potion_123"). NEVER use exponential XP formulas without cap — xp_to_next = level * 1000 ? Level 100 = 100k XP, level 1000 = 1M. Use sqrt/log OR flat scaling. NEVER forget to clamp derived stats — max_health = vitality * 10 ? Negative vitality from debuff = negative health = crash. Use maxi(value, 1) .
stats.gd
class_name Stats extends Resource signal stat_changed ( stat_name : String , old_value : float , new_value : float ) signal level_up ( new_level : int ) @ export var level : int = 1 @ export var experience : int = 0 @ export var experience_to_next_level : int = 100
Base stats
@ export var strength : int = 10 @ export var dexterity : int = 10 @ export var intelligence : int = 10 @ export var vitality : int = 10
Derived stats (calculated from base)
var max_health : int : get : return vitality * 10 var attack_power : int : get : return strength * 2 var defense : int : get : return strength + ( vitality / 2 ) var magic_power : int : get : return intelligence * 3 var critical_chance : float : get : return dexterity * 0.01
Modifiers
var modifiers : Dictionary = { } func add_experience ( amount : int ) -> void : experience += amount while experience
= experience_to_next_level : level_up_character ( ) func level_up_character ( ) -> void : level += 1 experience -= experience_to_next_level experience_to_next_level = int ( experience_to_next_level * 1.5 )
Increase base stats
strength += 2 dexterity += 2 intelligence += 2 vitality += 2 level_up . emit ( level ) func get_stat ( stat_name : String ) -> float : var base_value : float = get ( stat_name ) var modifier_bonus := get_modifier_total ( stat_name ) return base_value + modifier_bonus func add_modifier ( stat_name : String , modifier_id : String , value : float ) -> void : if not modifiers . has ( stat_name ) : modifiers [ stat_name ] = { } modifiers [ stat_name ] [ modifier_id ] = value func remove_modifier ( stat_name : String , modifier_id : String ) -> void : if modifiers . has ( stat_name ) : modifiers [ stat_name ] . erase ( modifier_id ) func get_modifier_total ( stat_name : String ) -> float : if not modifiers . has ( stat_name ) : return 0.0 var total := 0.0 for value in modifiers [ stat_name ] . values ( ) : total += value return total Equipment Stats
equipment_item.gd
extends Item class_name EquipmentItem @ export var stat_bonuses : Dictionary = { "strength" : 5 , "dexterity" : 3 } func on_equip ( stats : Stats ) -> void : for stat_name in stat_bonuses : stats . add_modifier ( stat_name , "equipment_" + id , stat_bonuses [ stat_name ] ) func on_unequip ( stats : Stats ) -> void : for stat_name in stat_bonuses : stats . remove_modifier ( stat_name , "equipment_" + id ) Status Effects
status_effect.gd
class_name StatusEffect extends Resource @ export var effect_id : String @ export var duration : float @ export var stat_modifiers : Dictionary = { } func apply ( stats : Stats ) -> void : for stat_name in stat_modifiers : stats . add_modifier ( stat_name , "status_" + effect_id , stat_modifiers [ stat_name ] ) func remove ( stats : Stats ) -> void : for stat_name in stat_modifiers : stats . remove_modifier ( stat_name , "status_" + effect_id ) Damage Calculation func calculate_damage ( attacker_stats : Stats , defender_stats : Stats ) -> float : var base_damage := float ( attacker_stats . attack_power ) var defense := float ( defender_stats . defense )
Damage reduction formula
var damage := base_damage * ( 100.0 / ( 100.0 + defense ) )
Critical hit
if randf ( ) < attacker_stats . critical_chance : damage *= 2.0 return maxf ( damage , 1.0 )
Minimum 1 damage
Skill Requirements
skill.gd
class_name Skill extends Resource @ export var required_level : int = 1 @ export var required_stats : Dictionary = { "strength" : 15 , "intelligence" : 10 } func can_use ( stats : Stats ) -> bool : if stats . level < required_level : return false for stat_name in required_stats : if stats . get_stat ( stat_name ) < required_stats [ stat_name ] : return false return true Best Practices Derived Stats - Calculate from base stats Modifiers - Temporary/permanent bonuses Formula Balance - Avoid exponential power creep Reference Related: godot-combat-system , godot-inventory-system Related Master Skill: godot-master