
Godot Economy System
- 221 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-economy-system for development tasks
About
godot-economy-system: A skill for development. This provides functionality for development workflows.
- godot-economy-system
Godot Economy System by the numbers
- 221 all-time installs (skills.sh)
- +13 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,763 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-economy-systemAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 221 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-economy-system for development tasks
Files
Economy System
Expert guidance for designing balanced game economies with currency, shops, and loot.
Available Scripts
currency_resource.gd
Specialized data container for defining distinct denominations (Gold, Gems, XP) with UI metadata.
wallet_manager_singleton.gd
Centralized AutoLoad orchestrator for managing balances and processing secure transactions.
shop_item_data.gd
Resource-based definition for purchasables, including pricing, currency types, and stock limits.
shop_system_logic.gd
Decoupled logic for handling buy/sell exchanges between the Wallet and Inventory systems.
dynamic_price_modifier.gd
Injection pattern for applying temporary discounts or markups based on world state (e.g. Sales).
currency_label_sync.gd
Reactive UI hook for automatically updating currency displays when balances change.
loot_drop_economy_bridge.gd
Bridge node for capturing loot events and adding funds to the player's wallet.
economy_persistence_handler.gd
Expert logic for serializing financial states into secure, loadable dictionaries.
currency_pickup_effect.gd
Visual feedback controller that triggers particles or animations upon financial gain.
trade_contract_resource.gd
Advanced barter system definition for multi-item "Quid Pro Quo" transactions.
NEVER Do in Economy Systems
- NEVER use `int` for large-scale premium economies — Standard 32-bit integers cap at 2.1 billion. For massive quantities, use
floator a customBigIntstructure [12]. - NEVER forget to implement a Buy/Sell price spread — Allowing players to sell items for the same price they bought them creates infinite money exploits [13].
- NEVER skip "Currency Sinks" — Without mandatory costs (repairs, taxes, consumables), the game economy will suffer from hyper-inflation [14].
- NEVER perform currency validation only on the client — In multiplayer or persistent games, the server MUST be the source of truth for all financial transactions [15].
- NEVER hardcode loot drop percentages inside scripts — Changing drop rates should not require a recompile. Use Resources or outside data files for easy balancing [16].
- NEVER allow negative balances via underflow — Always check
if current >= amountBEFORE subtracting. Negative gold can break logic and save files. - NEVER modify the wallet balance directly from the UI — The UI should only request a transaction. The
WalletManagershould decide if it's valid and update the state. - NEVER use floating point math for exact currency counts —
0.1 + 0.2might equal0.30000000000000004, leading to discrepancies. Useintfor cents/smallest units. - NEVER ignore "Transaction Logs" in serious RPGs — If money disappears, you need a history of events to debug whether it was a bug or a legitimate game event.
- NEVER give rewards without checking "Max Limit" — If a player is capped at 999,999 gold, adding 1,000 should result in 999,999, not a wrapped negative number.
---
Currency Manager
# economy_manager.gd (AutoLoad)
extends Node
signal currency_changed(old_amount: int, new_amount: int)
var gold: int = 0
func add_currency(amount: int) -> void:
var old := gold
gold += amount
currency_changed.emit(old, gold)
func spend_currency(amount: int) -> bool:
if gold < amount:
return false
var old := gold
gold -= amount
currency_changed.emit(old, gold)
return true
func has_currency(amount: int) -> bool:
return gold >= amountShop System
# shop_item.gd
class_name ShopItem
extends Resource
@export var item: Item
@export var buy_price: int
@export var sell_price: int
@export var stock: int = -1 # -1 = infinite
func can_buy() -> bool:
return stock != 0# shop.gd
class_name Shop
extends Resource
@export var shop_name: String
@export var items: Array[ShopItem] = []
func buy_item(shop_item: ShopItem, inventory: Inventory) -> bool:
if not shop_item.can_buy():
return false
if not EconomyManager.has_currency(shop_item.buy_price):
return false
if not EconomyManager.spend_currency(shop_item.buy_price):
return false
inventory.add_item(shop_item.item, 1)
if shop_item.stock > 0:
shop_item.stock -= 1
return true
func sell_item(item: Item, inventory: Inventory) -> bool:
# Find matching shop item for sell price
var shop_item := get_shop_item_for(item)
if not shop_item:
return false
if not inventory.has_item(item, 1):
return false
inventory.remove_item(item, 1)
EconomyManager.add_currency(shop_item.sell_price)
return true
func get_shop_item_for(item: Item) -> ShopItem:
for shop_item in items:
if shop_item.item == item:
return shop_item
return nullPricing Formula
func calculate_sell_price(buy_price: int, markup: float = 0.5) -> int:
# Sell for 50% of buy price
return int(buy_price * markup)
func calculate_dynamic_price(base_price: int, demand: float) -> int:
# Price increases with demand
return int(base_price * (1.0 + demand))Loot Tables
# loot_table.gd
class_name LootTable
extends Resource
@export var drops: Array[LootDrop] = []
func roll_loot() -> Array[Item]:
var items: Array[Item] = []
for drop in drops:
if randf() < drop.chance:
items.append(drop.item)
return items# loot_drop.gd
class_name LootDrop
extends Resource
@export var item: Item
@export var chance: float = 0.5
@export var min_amount: int = 1
@export var max_amount: int = 1Best Practices
1. Balance - Test economy carefully 2. Sinks - Provide money sinks (repairs, etc.) 3. Inflation - Control money generation
---
Elite Godot 4.x Patterns
1. Multi-Item Barter System
Use Resource arrays to define complex trade offers. This allows designers to visually configure "Quid Pro Quo" transactions without script modifications.
# trade_offer.gd
class_name TradeOffer extends Resource
@export var items_required: Array[Item] = []
@export var items_provided: Array[Item] = []
func can_afford(inventory: Inventory) -> bool:
for item in items_required:
if not inventory.has_item(item): return false
return true2. Economic Analytics (GPM Tracking)
Implement a custom Logger to intercept economy events and calculate metrics like Gold-Per-Minute (GPM) using the Time singleton.
# economy_logger.gd
class_name EconomyLogger extends Logger
var _gold_earned := 0
var _start_time := Time.get_ticks_msec()
func _log_message(msg: String, is_error: bool) -> void:
if not is_error and msg.begins_with("[ECON]"):
_gold_earned += msg.split(":")[1].to_int()
_log_gpm()
func _log_gpm() -> void:
var mins := (Time.get_ticks_msec() - _start_time) / 60000.0
var gpm := _gold_earned / max(mins, 0.01)
# Output to analytics dashboard or file3. Dynamic Item Value Estimator
Encapsulate valuation logic within the Item resource. Use enum rarity tiers and power level properties to dynamically calculate merchant prices.
# item_data_economy.gd
enum Rarity { COMMON, RARE, EPIC, LEGENDARY }
@export var rarity: Rarity = Rarity.COMMON
@export var base_value: int = 100
func get_value() -> int:
var mult := 1.0
match rarity:
Rarity.RARE: mult = 2.0
Rarity.EPIC: mult = 5.0
Rarity.LEGENDARY: mult = 20.0
return int(base_value * mult)Reference
- Master Skill: godot-master
# currency_label_sync.gd
# Reactive UI for balance display
extends Label
@export var currency_id: String = "gold"
func _ready():
WalletManager.balance_changed.connect(_on_balance_changed)
text = str(WalletManager.balances.get(currency_id, 0))
func _on_balance_changed(id: String, amount: int):
if id == currency_id:
text = str(amount)
# currency_pickup_effect.gd
# Visual feedback for financial gains
extends GPUParticles2D
# EXPERT NOTE: Trigger visual effects via balance signals
# to ensure the world "feels" the impact of economic changes.
func _ready():
WalletManager.balance_changed.connect(_on_balance_changed)
func _on_balance_changed(id: String, _amount: int):
if id == "gold":
restart()
# currency_resource.gd
# Specialized data container for denominations
class_name Currency extends Resource
# EXPERT NOTE: Defining Gold, Gems, and XP as Resources
# allow for modular wallet logic and distinct UI icons.
@export var id: String = "gold"
@export var display_name: String = "Gold"
@export var icon: Texture2D
@export var is_premium: bool = false
@export var max_limit: int = 999999
# dynamic_price_modifier.gd
# Adjusting costs based on world state
extends Resource
# EXPERT NOTE: Injecting price modifiers allows for "Sale" events
# or "Charisma" discounts without touching core item data.
@export var multiplier: float = 1.0
func calculate_price(base_price: int) -> int:
return roundi(base_price * multiplier)
# economy_persistence_handler.gd
# Saving financial state securely
extends Node
# EXPERT NOTE: Save balances as a simple JSON-compatible dictionary.
# Consider basic encryption for premium currency counts.
func save_economy() -> Dictionary:
return WalletManager.balances.duplicate()
func load_economy(data: Dictionary):
WalletManager.balances = data
for id in data:
WalletManager.balance_changed.emit(id, data[id])
# loot_drop_economy_bridge.gd
# Hooking combat drops to wallet addition
extends Node
# EXPERT NOTE: Use a generic node to listen for "Gold Drops" from enemies
# to keep the combat system focused only on health/damage.
func _on_enemy_looted(gold_amount: int):
WalletManager.add_funds("gold", gold_amount)
print("Looted ", gold_amount, " gold.")
# skills/economy-system/code/loot_table_weighted.gd
extends Resource
## Weighted Loot Table Expert Pattern
## Uses cumulative probability for high-precision drop distribution.
class_name LootTable
@export var items: Array[LootItem] = []
func get_random_item() -> Resource:
var total_weight = 0.0
for item in items:
total_weight += item.weight
var roll = randf() * total_weight
var cumulative_weight = 0.0
for item in items:
cumulative_weight += item.weight
if roll <= cumulative_weight:
return item.item_resource
return null
## Supporting Resource Type
class LootItem extends Resource:
@export var item_resource: Resource
@export var weight: float = 1.0 # Higher = more common
@export var tier: int = 1 # Optional metadata for filtering
## EXPERT NOTE:
## Storing loot tables as Resources allows designers to swap tables
## (e.g., 'Normal Chest' vs 'Boss Chest') in the inspector without code changes.
# shop_item_data.gd
# Pricing and availability for purchasables
class_name ShopItem extends Resource
# EXPERT NOTE: Shop items wrap InventoryItems with
# pricing and stock metadata.
@export var item: InventoryItem
@export var cost: int = 100
@export var currency_id: String = "gold"
@export var initial_stock: int = -1 # -1 for infinite
var current_stock: int = 0
func _init():
current_stock = initial_stock
# shop_system_logic.gd
# Orchestrating buys and sells
extends Node
# EXPERT NOTE: Logic should be decoupled from UI.
# This node handles the actual exchange of resources.
func buy_item(shop_item: ShopItem) -> bool:
if shop_item.current_stock == 0:
return false
if WalletManager.spend_funds(shop_item.currency_id, shop_item.cost):
if shop_item.current_stock > 0:
shop_item.current_stock -= 1
# Assumes InventoryManager exists
InventoryManager.add_item(shop_item.item)
return true
return false
func sell_item(item: InventoryItem, price: int):
if InventoryManager.remove_item(item):
WalletManager.add_funds("gold", price)
# trade_contract_resource.gd
# Advanced multi-item bartering logic
class_name TradeContract extends Resource
# EXPERT NOTE: Contracts allow for "Quid Pro Quo" transactions
# where specific items are traded for others without currency.
@export var take_items: Array[InventoryItem]
@export var give_items: Array[InventoryItem]
func execute_trade():
# Validate inventory has all 'take_items' first...
pass
# skills/economy-system/scripts/transaction_manager.gd
extends Node
## Transaction Manager Expert Pattern
## Safe atomic transactions for in-game economy (shops, trades).
class_name TransactionManager
signal transaction_completed(type: String, amount: int, item: String)
signal transaction_failed(reason: String)
enum CurrencyType { GOLD, GEMS, TOKENS }
var _wallets: Dictionary = {
CurrencyType.GOLD: 0,
CurrencyType.GEMS: 0
}
func get_balance(currency: CurrencyType) -> int:
return _wallets.get(currency, 0)
func add_currency(currency: CurrencyType, amount: int) -> void:
_wallets[currency] = get_balance(currency) + amount
transaction_completed.emit("earn", amount, "")
func attempt_purchase(cost: int, currency: CurrencyType, item_id: String, inventory_target: InventoryData = null) -> bool:
# 1. Validation
if cost < 0:
transaction_failed.emit("Invalid cost")
return false
if get_balance(currency) < cost:
transaction_failed.emit("Insufficient funds")
return false
# 2. Inventory Check (Atomic Step 1)
if inventory_target:
if not inventory_target.can_add_item(item_id):
transaction_failed.emit("Inventory full")
return false
# 3. Execution (Atomic Step 2)
_wallets[currency] -= cost
if inventory_target:
inventory_target.add_item_by_id(item_id)
transaction_completed.emit("spend", cost, item_id)
return true
func attempt_sell(item_id: String, price: int, currency: CurrencyType, inventory_source: InventoryData) -> bool:
# 1. Validation
if not inventory_source.has_item(item_id):
transaction_failed.emit("Item not found")
return false
# 2. Execution
inventory_source.remove_item_by_id(item_id)
add_currency(currency, price)
transaction_completed.emit("sell", price, item_id)
return true
## EXPERT USAGE:
## if TransactionManager.attempt_purchase(50, CurrencyType.GOLD, "sword_01", player_inv):
## play_sound("kaching")
# wallet_manager_singleton.gd
# Centralized economy state and transactions
extends Node
# EXPERT NOTE: The Wallet should be an Autoload to manage
# balances across different scenes (Shop vs World).
signal balance_changed(currency_id: String, new_amount: int)
signal transaction_failed(reason: String)
var balances: Dictionary = {} # currency_id -> int
func add_funds(currency_id: String, amount: int):
var current = balances.get(currency_id, 0)
balances[currency_id] = current + amount
balance_changed.emit(currency_id, balances[currency_id])
func spend_funds(currency_id: String, amount: int) -> bool:
var current = balances.get(currency_id, 0)
if current >= amount:
balances[currency_id] = current - amount
balance_changed.emit(currency_id, balances[currency_id])
return true
transaction_failed.emit("Insufficient funds")
return false