rpg-stats
SKILL.md
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 = 50for 50%? Integer division = truncation errors. Usefloat(0.0-1.0 OR 0.0-100.0). - NEVER modify stats without signals — UI showing health bar but
stats.current_health -= 10doesn'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. Usemaxi(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:
combat-system,inventory-system
Weekly Installs
1
Repository
thedivergentai/…c-skillsGitHub Stars
35
First Seen
Feb 9, 2026
Security Audits
Installed on
amp1
opencode1
kimi-cli1
codex1
github-copilot1
gemini-cli1