Add generator buffs (additional test required)

This commit is contained in:
2026-03-15 10:06:43 +01:00
parent 7b58c36414
commit 278b073501
19 changed files with 691 additions and 21 deletions

View File

@@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://b4ne724qqo4d8"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_4w6kt"]
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://currency/knowledge.tres" id="2_hk5d2"]
[resource]
script = ExtResource("1_4w6kt")
id = &"knowledge_auto_treatise"
text = "Ancient Treatise"
max_level = 35
effect_increment = 0.2
cost_currency = ExtResource("2_hk5d2")
base_cost_mantissa = 40.0
cost_multiplier = 1.75

View File

@@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://l5a300wieavc"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_l4lhm"]
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://currency/knowledge.tres" id="2_9xja7"]
[resource]
script = ExtResource("1_l4lhm")
id = &"knowledge_click_notes"
kind = 1
text = "Quick Notes"
max_level = 25
effect_increment = 0.25
cost_currency = ExtResource("2_9xja7")
base_cost_mantissa = 15.0
cost_multiplier = 1.6

View File

@@ -0,0 +1,19 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://dxljqpqhdfqn6"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_8ee3d"]
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://currency/magic.tres" id="2_j7l8a"]
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://currency/knowledge.tres" id="3_ynr4x"]
[resource]
script = ExtResource("1_8ee3d")
id = &"knowledge_grant_program"
kind = 2
text = "Research Grant"
max_level = 20
effect_increment = 0.0
cost_currency = ExtResource("2_j7l8a")
base_cost_mantissa = 90.0
cost_multiplier = 1.8
resource_target_currency = ExtResource("3_ynr4x")
resource_purchase_base_mantissa = 8.0
resource_purchase_increment_multiplier = 1.35

View File

@@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://ceugcxmassmpk"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_r7ak1"]
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://currency/magic.tres" id="2_h3we5"]
[resource]
script = ExtResource("1_r7ak1")
id = &"magic_auto_flux"
text = "Arcane Dynamo"
max_level = 40
effect_increment = 0.15
cost_currency = ExtResource("2_h3we5")
base_cost_mantissa = 25.0
cost_multiplier = 1.7

View File

@@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://6i3fcygusuqf"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_1wyaq"]
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://currency/magic.tres" id="2_0j56j"]
[resource]
script = ExtResource("1_1wyaq")
id = &"magic_click_focus"
kind = 1
text = "Apprentice Gloves"
max_level = 30
effect_increment = 0.35
cost_currency = ExtResource("2_0j56j")
base_cost_mantissa = 8.0
cost_multiplier = 1.55

View File

@@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://cluds3cw1d65q"]
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://generator/generator_buff_data.gd" id="1_fvte4"]
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://currency/magic.tres" id="2_ea8w5"]
[resource]
script = ExtResource("1_fvte4")
id = &"magic_supply_cache"
kind = 2
text = "Mana Cache"
max_level = 25
effect_increment = 0.0
cost_currency = ExtResource("2_ea8w5")
base_cost_mantissa = 12.0
cost_multiplier = 1.6
resource_target_currency = ExtResource("2_ea8w5")
resource_purchase_base_mantissa = 12.0
resource_purchase_increment_multiplier = 1.45

View File

@@ -13,6 +13,10 @@ signal purchase_completed(amount: int, total_owned: int, total_purchased: int, t
signal purchase_failed(requested_amount: int, required_cost: BigNumber, available_currency: BigNumber)
## Emitted when one or more automatic production cycles complete.
signal production_tick(amount: BigNumber, cycle_count: int, total_owned: int)
## Emitted after a successful buff purchase.
signal buff_purchased(buff_id: StringName, new_level: int, cost: BigNumber, cost_currency_id: StringName)
## Emitted when a buff purchase cannot be paid.
signal buff_purchase_failed(buff_id: StringName, required_cost: BigNumber, cost_currency_id: StringName)
## Sentinel exponent used when cost math overflows float range.
const HUGE_COST_EXPONENT: int = 1000000
@@ -119,7 +123,8 @@ func _grant_cycle_income(cycle_count: int) -> void:
if data == null or cycle_count <= 0:
return
var per_cycle: float = data.production_per_cycle(owned, run_multiplier, purchased_count)
var effective_run_multiplier: float = run_multiplier * get_automatic_production_multiplier()
var per_cycle: float = data.production_per_cycle(owned, effective_run_multiplier, purchased_count)
if per_cycle <= 0.0:
return
@@ -195,6 +200,89 @@ func buy_max() -> int:
return 0
return best if buy(best) else 0
func get_buffs() -> Array[GeneratorBuffData]:
if data == null:
return []
return data.buffs
func get_buff_level(buff_id: StringName) -> int:
_ensure_registered()
return GameState.get_generator_buff_level(_generator_id, buff_id)
func get_buff_cost(buff_id: StringName) -> BigNumber:
var buff: GeneratorBuffData = _find_buff(buff_id)
if buff == null:
return BigNumber.from_float(0.0)
var level: int = get_buff_level(buff.id)
return buff.get_cost_for_level(level)
func get_buff_cost_currency_id(buff_id: StringName) -> StringName:
var buff: GeneratorBuffData = _find_buff(buff_id)
if buff == null:
return &""
return _resolve_buff_cost_currency_id(buff)
func can_buy_buff(buff_id: StringName) -> bool:
if not is_available_to_player():
return false
var buff: GeneratorBuffData = _find_buff(buff_id)
if buff == null:
return false
var current_level: int = get_buff_level(buff.id)
if not buff.can_purchase_next_level(current_level):
return false
var cost_currency_id: StringName = _resolve_buff_cost_currency_id(buff)
if cost_currency_id == &"":
return false
var cost: BigNumber = buff.get_cost_for_level(current_level)
var current_amount: BigNumber = GameState.get_currency_amount_by_id(cost_currency_id)
return current_amount.is_greater_than(cost) or current_amount.is_equal_to(cost)
func buy_buff(buff_id: StringName) -> bool:
if not is_available_to_player():
return false
var buff: GeneratorBuffData = _find_buff(buff_id)
if buff == null:
return false
var current_level: int = get_buff_level(buff.id)
if not buff.can_purchase_next_level(current_level):
return false
var cost: BigNumber = buff.get_cost_for_level(current_level)
var cost_currency_id: StringName = _resolve_buff_cost_currency_id(buff)
if cost_currency_id == &"":
return false
if not _spend_currency_by_id(cost_currency_id, cost):
buff_purchase_failed.emit(buff.id, cost, cost_currency_id)
return false
var next_level: int = current_level + 1
GameState.set_generator_buff_level(_generator_id, buff.id, next_level)
_apply_resource_purchase_buff(buff, next_level)
buff_purchased.emit(buff.id, next_level, cost, cost_currency_id)
return true
func is_buff_maxed(buff_id: StringName) -> bool:
var buff: GeneratorBuffData = _find_buff(buff_id)
if buff == null:
return true
return not buff.can_purchase_next_level(get_buff_level(buff.id))
func get_automatic_production_multiplier() -> float:
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.AUTO_PRODUCTION_MULTIPLIER)
func get_manual_click_multiplier() -> float:
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.MANUAL_CLICK_MULTIPLIER)
## Handles external "pressed" interaction: buy if configured, otherwise click-grant.
func _on_pressed() -> void:
if not is_available_to_player():
@@ -235,7 +323,13 @@ func _try_grant_click_currency() -> void:
func _get_click_value() -> BigNumber:
if data == null:
return BigNumber.from_float(0.0)
return BigNumber.new(data.click_mantissa, data.click_exponent)
var click_multiplier: float = get_manual_click_multiplier()
if click_multiplier <= 0.0:
return BigNumber.from_float(0.0)
var base_click: BigNumber = BigNumber.new(data.click_mantissa, data.click_exponent)
return base_click.multiply(BigNumber.from_float(click_multiplier))
## Resolves click cooldown from data.
func _get_click_cooldown_seconds() -> float:
@@ -268,6 +362,12 @@ func _add_currency(amount: BigNumber) -> void:
GameState.add_currency(currency, amount)
func _add_currency_by_id(currency_id: StringName, amount: BigNumber) -> void:
if currency_id == &"":
return
GameState.add_currency_by_id(currency_id, amount)
## Attempts to spend target currency; returns false when insufficient.
func _spend_currency(cost: BigNumber) -> bool:
if currency == null:
@@ -276,6 +376,12 @@ func _spend_currency(cost: BigNumber) -> bool:
return GameState.spend_currency(currency, cost)
func _spend_currency_by_id(currency_id: StringName, cost: BigNumber) -> bool:
if currency_id == &"":
return false
return GameState.spend_currency_by_id(currency_id, cost)
## Returns the current amount for the configured currency type.
func _get_currency_amount() -> BigNumber:
if currency == null:
@@ -300,6 +406,59 @@ func _big_number_to_float(value: BigNumber) -> float:
return 0.0
return value.mantissa * pow(10.0, float(value.exponent))
func _find_buff(buff_id: StringName) -> GeneratorBuffData:
if data == null:
return null
return data.find_buff(buff_id)
func _resolve_buff_cost_currency_id(buff: GeneratorBuffData) -> StringName:
if buff == null:
return &""
var cost_currency: Resource = buff.cost_currency if buff.cost_currency != null else currency
if cost_currency == null:
return &""
return GameState.get_currency_id(cost_currency)
func _resolve_buff_target_currency_id(buff: GeneratorBuffData) -> StringName:
if buff == null:
return &""
var target_currency: Resource = buff.resource_target_currency if buff.resource_target_currency != null else currency
if target_currency == null:
return &""
return GameState.get_currency_id(target_currency)
func _get_multiplier_for_buff_kind(kind: GeneratorBuffData.BuffKind) -> float:
var multiplier: float = 1.0
for buff in get_buffs():
if buff == null:
continue
if buff.kind != kind:
continue
multiplier *= buff.get_effect_multiplier(get_buff_level(buff.id))
return maxf(multiplier, 0.0)
func _apply_resource_purchase_buff(buff: GeneratorBuffData, level: int) -> void:
if buff == null:
return
if buff.kind != GeneratorBuffData.BuffKind.RESOURCE_PURCHASE:
return
var target_currency_id: StringName = _resolve_buff_target_currency_id(buff)
if target_currency_id == &"":
return
var purchased_amount: BigNumber = buff.get_resource_purchase_amount_for_level(level)
if purchased_amount.mantissa <= 0.0:
return
_add_currency_by_id(target_currency_id, purchased_amount)
## Builds a stable state id from data id, node name, or fallback string.
func _resolve_generator_id() -> StringName:
if data != null:
@@ -323,8 +482,20 @@ func _ensure_registered() -> void:
return
GameState.register_generator(_generator_id, _default_unlocked_state(), _default_available_state())
_register_buffs()
_is_registered = true
func _register_buffs() -> void:
for buff in get_buffs():
if buff == null:
continue
var buff_id_text: String = String(buff.id).strip_edges()
if buff_id_text.is_empty():
continue
GameState.register_generator_buff(_generator_id, StringName(buff_id_text), 0)
## Default unlocked state loaded from generator data.
func _default_unlocked_state() -> bool:
if data == null:
@@ -345,9 +516,6 @@ func _on_area_2d_mouse_entered() -> void:
func _on_area_2d_mouse_exited() -> void:
_mouse_entered = false
func _on_generated_state_changed(generator_id: StringName, state: Dictionary) -> void:
func _on_generated_state_changed(generator_id: StringName, _state: Dictionary) -> void:
if generator_id == _generator_id:
print(_generator_id)
print(state)
visible = true

View File

@@ -28,6 +28,22 @@ extends Resource
@export var click_exponent: int = 0
@export var click_cooldown_seconds: float = 0.2
## Data-driven buffs purchasable for this generator.
@export var buffs: Array[GeneratorBuffData] = []
func find_buff(buff_id: StringName) -> GeneratorBuffData:
var expected_id: String = String(buff_id).strip_edges()
if expected_id.is_empty():
return null
for buff in buffs:
if buff == null:
continue
if String(buff.id) == expected_id:
return buff
return null
## Returns cost of next generator
func cost_next(owned: int) -> float:
return cost_for_amount(owned, 1)

View File

@@ -0,0 +1,95 @@
class_name GeneratorBuffData
extends Resource
enum BuffKind {
AUTO_PRODUCTION_MULTIPLIER,
MANUAL_CLICK_MULTIPLIER,
RESOURCE_PURCHASE,
}
const HUGE_EXPONENT: int = 1000000
@export var id: StringName = &""
@export var kind: BuffKind = BuffKind.AUTO_PRODUCTION_MULTIPLIER
@export_multiline var text: String = ""
@export var icon: Texture2D
@export var max_level: int = -1
@export_group("Effect")
@export var base_effect: float = 1.0
@export var effect_increment: float = 0.1
@export_group("Cost")
@export var cost_currency: Resource
@export var base_cost_mantissa: float = 10.0
@export var base_cost_exponent: int = 0
@export var cost_multiplier: float = 1.5
@export_group("Resource Purchase")
@export var resource_target_currency: Resource
@export var resource_purchase_base_mantissa: float = 10.0
@export var resource_purchase_base_exponent: int = 0
@export var resource_purchase_increment_multiplier: float = 1.2
func can_purchase_next_level(current_level: int) -> bool:
if max_level < 0:
return true
return maxi(current_level, 0) < max_level
func get_effect_multiplier(level: int) -> float:
var safe_level: int = maxi(level, 0)
return maxf(base_effect + (effect_increment * float(safe_level)), 0.0)
func get_cost_for_level(level: int) -> BigNumber:
var safe_level: int = maxi(level, 0)
var base_cost: BigNumber = BigNumber.new(base_cost_mantissa, base_cost_exponent)
if base_cost.mantissa <= 0.0:
return BigNumber.from_float(0.0)
if cost_multiplier <= 0.0:
return BigNumber.new(1.0, HUGE_EXPONENT)
var growth: float = 1.0
if not is_equal_approx(cost_multiplier, 1.0):
growth = pow(cost_multiplier, float(safe_level))
if not is_finite(growth):
return BigNumber.new(1.0, HUGE_EXPONENT)
return _scale_big_number(base_cost, growth)
func get_resource_purchase_amount_for_level(level: int) -> BigNumber:
var safe_level: int = maxi(level, 0)
var base_amount: BigNumber = BigNumber.new(resource_purchase_base_mantissa, resource_purchase_base_exponent)
if base_amount.mantissa <= 0.0:
return BigNumber.from_float(0.0)
if resource_purchase_increment_multiplier <= 0.0:
return BigNumber.from_float(0.0)
var growth: float = 1.0
if not is_equal_approx(resource_purchase_increment_multiplier, 1.0):
growth = pow(resource_purchase_increment_multiplier, float(safe_level))
if not is_finite(growth):
return BigNumber.new(1.0, HUGE_EXPONENT)
return _scale_big_number(base_amount, growth)
func get_effect_description(level: int) -> String:
var safe_level: int = maxi(level, 0)
match kind:
BuffKind.AUTO_PRODUCTION_MULTIPLIER, BuffKind.MANUAL_CLICK_MULTIPLIER:
return "x%.2f" % get_effect_multiplier(safe_level)
BuffKind.RESOURCE_PURCHASE:
return "+%s" % get_resource_purchase_amount_for_level(safe_level).to_string_suffix(2)
_:
return "-"
func _scale_big_number(base_amount: BigNumber, scale: float) -> BigNumber:
if base_amount.mantissa <= 0.0:
return BigNumber.from_float(0.0)
if scale <= 0.0:
return BigNumber.from_float(0.0)
if not is_finite(scale):
return BigNumber.new(1.0, HUGE_EXPONENT)
return base_amount.multiply(BigNumber.from_float(scale))

View File

@@ -0,0 +1 @@
uid://dnhocfsuvmeyb

View File

@@ -1,6 +1,9 @@
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://co0mcc2kvcpo5"]
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://generator/currency_generator_data.gd" id="1_c6y77"]
[ext_resource type="Resource" path="res://generator/buffs/magic_auto_flux.tres" id="2_x505b"]
[ext_resource type="Resource" path="res://generator/buffs/magic_click_focus.tres" id="3_fsxdm"]
[ext_resource type="Resource" path="res://generator/buffs/magic_supply_cache.tres" id="4_x2byu"]
[resource]
script = ExtResource("1_c6y77")
@@ -8,4 +11,5 @@ id = &"Magic"
name = "Magic Orb"
initial_time = 0.6
initial_productivity = 1.67
buffs = [ExtResource("2_x505b"), ExtResource("3_fsxdm"), ExtResource("4_x2byu")]
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"

View File

@@ -1,6 +1,9 @@
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://04pmc034qupd"]
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://generator/currency_generator_data.gd" id="1_s3g28"]
[ext_resource type="Resource" path="res://generator/buffs/knowledge_auto_treatise.tres" id="2_kxvfc"]
[ext_resource type="Resource" path="res://generator/buffs/knowledge_click_notes.tres" id="3_q64x8"]
[ext_resource type="Resource" path="res://generator/buffs/knowledge_grant_program.tres" id="4_l77sg"]
[resource]
script = ExtResource("1_s3g28")
@@ -12,4 +15,5 @@ initial_cost = 60.0
initial_time = 3.0
initial_revenue = 60.0
initial_productivity = 20.0
buffs = [ExtResource("2_kxvfc"), ExtResource("3_q64x8"), ExtResource("4_l77sg")]
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"