First commit
This commit is contained in:
60
.agents/skills/developing-godot-gdscript/SKILL.md
Normal file
60
.agents/skills/developing-godot-gdscript/SKILL.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: developing-godot-gdscript
|
||||
description: Builds and refactors Godot 4 projects with idiomatic GDScript, scene composition, signals, and autoload patterns. Use when implementing gameplay, UI, tools, or architecture changes in Godot/GDScript repositories.
|
||||
---
|
||||
|
||||
# Developing Godot With GDScript
|
||||
|
||||
Implements production-friendly Godot 4 changes using typed GDScript, scene composition, and low-coupling architecture.
|
||||
|
||||
## Use This Skill When
|
||||
|
||||
- Adding or refactoring gameplay systems, UI flows, or content pipelines in Godot.
|
||||
- Debugging node lifecycle, signals, resources, or autoload behavior.
|
||||
- Improving maintainability, style consistency, or data flow in GDScript code.
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. Prefer composition over deep inheritance: split reusable behavior into scenes/resources/scripts with focused responsibilities.
|
||||
2. Choose scenes for reusable node trees and editor-authored structure; choose scripts/resources for logic and data that do not need node hierarchy.
|
||||
3. Use autoloads only for truly global state/services (save systems, global config, cross-scene coordinators), not as default dependency injection.
|
||||
4. Favor signals over direct cross-tree mutation to reduce coupling between gameplay, UI, and services.
|
||||
5. Use typed GDScript where possible: annotate variables, params, and return types for safer refactors.
|
||||
|
||||
## GDScript Implementation Checklist
|
||||
|
||||
1. Keep naming idiomatic: `snake_case` for vars/functions/signals, `PascalCase` for `class_name`, `UPPER_SNAKE_CASE` for constants.
|
||||
2. Use `@export` for editor-facing configuration and `@onready` only for scene-dependent node lookups.
|
||||
3. Respect lifecycle intent: `_init` for construction data, `_enter_tree` for tree attachment concerns, `_ready` for node wiring, `_process`/`_physics_process` for frame loops.
|
||||
4. Distinguish frame loops clearly: use `_physics_process` for deterministic physics movement and `_process` for visual or non-physics updates.
|
||||
5. Use `match` for clear branching on enums/states and keep functions short with early returns.
|
||||
|
||||
## Data, Loading, And Error Handling
|
||||
|
||||
1. Guard all file access (`FileAccess.open`) and JSON parsing before indexing into parsed values.
|
||||
2. Validate dynamic data types (`is Dictionary`, `is Array`) before field access to avoid runtime surprises.
|
||||
3. Use `preload` for stable, frequently used compile-time resources; use `load` for optional or dynamic runtime assets.
|
||||
4. Prefer `Resource`/`RefCounted` for plain data models when node features are unnecessary.
|
||||
5. Use `assert`/`push_error` for invalid states that should fail loudly during development.
|
||||
|
||||
## Delivery Workflow
|
||||
|
||||
1. Read `project.godot` first to identify autoloads, rendering/physics settings, and project conventions.
|
||||
2. Inspect relevant `.tscn` + `.gd` pairs together before editing to preserve scene-script contracts.
|
||||
3. Implement minimal, targeted changes that keep public APIs/signals stable unless change is requested.
|
||||
4. Run headless validation and script checks for touched scripts; run project smoke test when feasible.
|
||||
5. Summarize changed files, behavior impact, and any follow-up risks.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
Use project-provided commands when available. Typical Godot checks:
|
||||
|
||||
```bash
|
||||
"$GODOT_BIN" --headless --path . --quit
|
||||
"$GODOT_BIN" --headless --check-only --script res://path/to/changed_script.gd
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html
|
||||
- https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html
|
||||
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
22
AGENTS.md
Normal file
22
AGENTS.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# AGENTS.md
|
||||
Godot 4.6 idle-game prototype; code is GDScript + `.tscn` scenes + JSON data at repo root.
|
||||
## Build, Lint, And Test Commands
|
||||
Use `GODOT_BIN` for your local editor binary (e.g. `godot`, `godot4`, or full path).
|
||||
Run project: `"$GODOT_BIN" --path .`
|
||||
Headless smoke/lint parse: `"$GODOT_BIN" --headless --path . --quit`
|
||||
Script syntax check (single script): `"$GODOT_BIN" --headless --check-only --script res://big_number.gd`
|
||||
Tests: no test framework is currently committed (`tests/` and `addons/gut` are absent).
|
||||
Single-test command: N/A right now; if GUT is added, use `"$GODOT_BIN" --headless --path . -s res://addons/gut/gut_cmdln.gd -gtest=res://tests/<file>.gd`.
|
||||
## Architecture And Structure
|
||||
`project.godot` defines autoload singletons: `GameState` (runtime currency state + save/load) and `CurrencyGeneratorDatabase` (loads `generator_data.json`).
|
||||
`big_number.gd` (`class_name BigNumber`) is the internal numeric API for huge-value math, comparisons, formatting, and serialization.
|
||||
`big_number_museum.tscn` + `big_number_museum.gd` is the current main playable scene/prototype UI.
|
||||
`currency_generator.gd`, `currency_label.gd`, and `big_number_progress_bar.gd` are UI/gameplay adapters that react to `GameState` signals.
|
||||
Persistence is local JSON only: `user://idle_save.json`; there is no external DB/server/backend.
|
||||
## Code Style And Conventions
|
||||
Prefer typed GDScript (`var x: Type`, `func f() -> void`) and keep shared models/components as `class_name` scripts.
|
||||
Naming: `snake_case` for vars/functions/signals, `PascalCase` for classes, `UPPER_SNAKE_CASE` for constants.
|
||||
Follow existing Godot style: tabs/consistent indentation, early returns, and short functions grouped by purpose.
|
||||
When reading files/JSON, always guard `FileAccess.open(...)` and validate parsed types before indexing.
|
||||
Use signals (already in `GameState`) for UI updates instead of polling or cross-node direct mutation.
|
||||
No Cursor/Claude/Windsurf/Cline/Goose/Copilot rule files were found in this repository, so this file is the canonical agent guidance.
|
||||
227
big_number.gd
Normal file
227
big_number.gd
Normal file
@@ -0,0 +1,227 @@
|
||||
class_name BigNumber
|
||||
extends RefCounted
|
||||
|
||||
## The core value representation.
|
||||
## For example, 1.5e24 is represented as mantissa = 1.5, exponent = 24.
|
||||
var mantissa: float
|
||||
var exponent: int
|
||||
|
||||
## Pre-calculate this to avoid dividing logs constantly
|
||||
const LOG10_E: float = 0.4342944819032518
|
||||
|
||||
func _init(m: float = 0.0, e: int = 0) -> void:
|
||||
mantissa = m
|
||||
exponent = e
|
||||
_normalize()
|
||||
|
||||
## Adjusts the internal representation so the mantissa is always between 1.0 and 9.99...
|
||||
## or exactly 0.0.
|
||||
func _normalize() -> void:
|
||||
if mantissa == 0.0:
|
||||
exponent = 0
|
||||
return
|
||||
|
||||
var is_negative: bool = mantissa < 0.0
|
||||
var abs_m: float = abs(mantissa)
|
||||
|
||||
if abs_m >= 10.0 or abs_m < 1.0:
|
||||
# Calculate how many powers of 10 we need to shift
|
||||
# Godot's log() is base 'e', so we multiply by log10(e) to get log10(x)
|
||||
var exp_diff: int = floori(log(abs_m) * LOG10_E)
|
||||
abs_m /= pow(10.0, float(exp_diff))
|
||||
exponent += exp_diff
|
||||
|
||||
mantissa = abs_m if not is_negative else -abs_m
|
||||
|
||||
# ==========================================
|
||||
# MATH OPERATIONS
|
||||
# ==========================================
|
||||
|
||||
func add(other: BigNumber) -> BigNumber:
|
||||
if mantissa == 0: return BigNumber.new(other.mantissa, other.exponent)
|
||||
if other.mantissa == 0: return BigNumber.new(mantissa, exponent)
|
||||
|
||||
var exp_diff: int = exponent - other.exponent
|
||||
|
||||
# If the difference in magnitude is massive, the smaller number is insignificant
|
||||
# (15 is a safe threshold for 64-bit float precision).
|
||||
if exp_diff >= 15:
|
||||
return BigNumber.new(mantissa, exponent)
|
||||
elif exp_diff <= -15:
|
||||
return BigNumber.new(other.mantissa, other.exponent)
|
||||
|
||||
var new_m: float = mantissa
|
||||
var new_e: int = exponent
|
||||
|
||||
# Scale the smaller number down to match the larger number's exponent
|
||||
if exp_diff > 0:
|
||||
new_m += other.mantissa / pow(10.0, float(exp_diff))
|
||||
elif exp_diff < 0:
|
||||
new_m = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
|
||||
new_e = other.exponent
|
||||
else:
|
||||
new_m += other.mantissa
|
||||
|
||||
return BigNumber.new(new_m, new_e)
|
||||
|
||||
## High-performance addition for the _process() loop.
|
||||
## Modifies THIS instance instead of creating a new RefCounted object.
|
||||
func add_in_place(other: BigNumber) -> void:
|
||||
if other.mantissa == 0.0: return
|
||||
if mantissa == 0.0:
|
||||
mantissa = other.mantissa
|
||||
exponent = other.exponent
|
||||
return
|
||||
|
||||
var exp_diff: int = exponent - other.exponent
|
||||
|
||||
if exp_diff >= 15:
|
||||
return # Other number is too small to matter
|
||||
elif exp_diff <= -15:
|
||||
# This number is effectively replaced by the larger other number
|
||||
mantissa = other.mantissa
|
||||
exponent = other.exponent
|
||||
return
|
||||
|
||||
if exp_diff > 0:
|
||||
mantissa += other.mantissa / pow(10.0, float(exp_diff))
|
||||
elif exp_diff < 0:
|
||||
mantissa = (mantissa / pow(10.0, float(-exp_diff))) + other.mantissa
|
||||
exponent = other.exponent
|
||||
else:
|
||||
mantissa += other.mantissa
|
||||
|
||||
# Prevent floating-point drift near zero
|
||||
if abs(mantissa) < 0.0000000001:
|
||||
mantissa = 0.0
|
||||
exponent = 0
|
||||
else:
|
||||
_normalize()
|
||||
|
||||
func subtract(other: BigNumber) -> BigNumber:
|
||||
# Subtraction is just adding a negative number
|
||||
var negative_other = BigNumber.new(-other.mantissa, other.exponent)
|
||||
return add(negative_other)
|
||||
|
||||
func multiply(other: BigNumber) -> BigNumber:
|
||||
var new_m: float = mantissa * other.mantissa
|
||||
var new_e: int = exponent + other.exponent
|
||||
return BigNumber.new(new_m, new_e)
|
||||
|
||||
func divide(other: BigNumber) -> BigNumber:
|
||||
if other.mantissa == 0.0:
|
||||
push_error("BigNumber: Division by zero!")
|
||||
return BigNumber.new(0.0, 0)
|
||||
|
||||
var new_m: float = mantissa / other.mantissa
|
||||
var new_e: int = exponent - other.exponent
|
||||
return BigNumber.new(new_m, new_e)
|
||||
|
||||
# ==========================================
|
||||
# COMPARISONS
|
||||
# ==========================================
|
||||
|
||||
## Returns 1 if this > other, -1 if this < other, 0 if equal
|
||||
func compare_to(other: BigNumber) -> int:
|
||||
if mantissa == 0.0 and other.mantissa == 0.0: return 0
|
||||
|
||||
# Handle signs
|
||||
if mantissa > 0 and other.mantissa <= 0: return 1
|
||||
if mantissa < 0 and other.mantissa >= 0: return -1
|
||||
|
||||
# Both are same sign. Compare exponents first.
|
||||
var sign_mult: int = 1 if mantissa > 0 else -1
|
||||
|
||||
if exponent > other.exponent: return sign_mult
|
||||
if exponent < other.exponent: return -sign_mult
|
||||
|
||||
# Exponents are equal, compare mantissas
|
||||
if mantissa > other.mantissa: return 1
|
||||
if mantissa < other.mantissa: return -1
|
||||
|
||||
return 0
|
||||
|
||||
func is_greater_than(other: BigNumber) -> bool:
|
||||
return compare_to(other) == 1
|
||||
|
||||
func is_less_than(other: BigNumber) -> bool:
|
||||
return compare_to(other) == -1
|
||||
|
||||
func is_equal_to(other: BigNumber) -> bool:
|
||||
return compare_to(other) == 0
|
||||
|
||||
## Calculates the progress ratio between this number and a target number.
|
||||
## Returns a standard float clamped between 0.0 and 1.0 for UI progress bars.
|
||||
func get_ratio(target: BigNumber) -> float:
|
||||
if target.mantissa == 0.0:
|
||||
return 1.0 # If the goal is 0, you've already beaten it!
|
||||
if mantissa == 0.0:
|
||||
return 0.0
|
||||
|
||||
var exp_diff: int = exponent - target.exponent
|
||||
|
||||
# If the target is massively larger, progress is practically 0%
|
||||
if exp_diff <= -15:
|
||||
return 0.0
|
||||
|
||||
# If current is equal or greater, progress is 100%
|
||||
if exp_diff >= 15 or is_greater_than(target) or is_equal_to(target):
|
||||
return 1.0
|
||||
|
||||
# Calculate the actual float ratio
|
||||
var ratio: float = (mantissa / target.mantissa) * pow(10.0, float(exp_diff))
|
||||
|
||||
# Clamp it just to be perfectly safe for UI elements
|
||||
return clampf(ratio, 0.0, 1.0)
|
||||
|
||||
# ==========================================
|
||||
# UTILITIES
|
||||
# ==========================================
|
||||
|
||||
## Creates a BigNumber from a standard float or int
|
||||
static func from_float(val: float) -> BigNumber:
|
||||
return BigNumber.new(val, 0)
|
||||
|
||||
## Outputs a UI-friendly string (e.g., "1.50e12")
|
||||
func to_string_sci(decimals: int = 2) -> String:
|
||||
if exponent < 3:
|
||||
# For small numbers, just show the regular number
|
||||
var val: float = mantissa * pow(10.0, float(exponent))
|
||||
return ("%." + str(decimals) + "f") % val
|
||||
|
||||
var format_str: String = "%." + str(decimals) + "f"
|
||||
return (format_str % mantissa) + "e" + str(exponent)
|
||||
|
||||
## Optional: Standard idle game suffix formatting (K, M, B, T, Qa, etc.)
|
||||
func to_string_suffix(decimals: int = 2) -> String:
|
||||
if exponent < 3:
|
||||
return to_string_sci(decimals)
|
||||
|
||||
var suffixes = ["", "K", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc"]
|
||||
var suffix_index: int = floori(exponent / 3.0)
|
||||
|
||||
if suffix_index < suffixes.size():
|
||||
var display_mantissa = mantissa * pow(10.0, float(exponent % 3))
|
||||
var format_str: String = "%." + str(decimals) + "f"
|
||||
return (format_str % display_mantissa) + suffixes[suffix_index]
|
||||
else:
|
||||
# Fall back to scientific if we run out of suffixes
|
||||
return to_string_sci(decimals)
|
||||
|
||||
# ==========================================
|
||||
# SAVE & LOAD (SERIALIZATION)
|
||||
# ==========================================
|
||||
|
||||
## Converts the BigNumber into a basic Dictionary for easy JSON saving.
|
||||
func serialize() -> Dictionary:
|
||||
return {
|
||||
"m": mantissa,
|
||||
"e": exponent
|
||||
}
|
||||
|
||||
## A static factory method that creates a new BigNumber from loaded Dictionary data.
|
||||
static func deserialize(data: Dictionary) -> BigNumber:
|
||||
# Provide fallbacks (0.0 and 0) just in case the save file is corrupted
|
||||
var loaded_m: float = data.get("m", 0.0)
|
||||
var loaded_e: int = data.get("e", 0)
|
||||
return BigNumber.new(loaded_m, loaded_e)
|
||||
1
big_number.gd.uid
Normal file
1
big_number.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://be4in86mvsj0q
|
||||
10
big_number_museum.gd
Normal file
10
big_number_museum.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends Control
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
1
big_number_museum.gd.uid
Normal file
1
big_number_museum.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://coasop1lyw5rh
|
||||
43
big_number_museum.tscn
Normal file
43
big_number_museum.tscn
Normal file
@@ -0,0 +1,43 @@
|
||||
[gd_scene format=3 uid="uid://b11eevc8qj18p"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dtbxopw6ulhl8" path="res://generator/currency_generator.gd" id="1_h2bxk"]
|
||||
[ext_resource type="PackedScene" uid="uid://clu4rn1tl1s8t" path="res://big_number_progress_bar.tscn" id="1_lni8d"]
|
||||
[ext_resource type="Script" uid="uid://coasop1lyw5rh" path="res://big_number_museum.gd" id="1_muwei"]
|
||||
[ext_resource type="Script" uid="uid://sp67wcb6s5nw" path="res://currency_label.gd" id="2_a7k0t"]
|
||||
|
||||
[node name="BigNumberMuseum" type="Control" unique_id=1249411984]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_muwei")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1937517070]
|
||||
layout_mode = 0
|
||||
offset_right = 264.0
|
||||
offset_bottom = 111.0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer" unique_id=1357448839]
|
||||
layout_mode = 2
|
||||
text = "Primary currency"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=1592054467]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 126
|
||||
|
||||
[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=2126082317]
|
||||
layout_mode = 2
|
||||
text = "Get Currency"
|
||||
script = ExtResource("1_h2bxk")
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer" unique_id=1036299287]
|
||||
layout_mode = 2
|
||||
text = "000"
|
||||
script = ExtResource("2_a7k0t")
|
||||
|
||||
[node name="HBoxContainerAuto" parent="VBoxContainer" unique_id=1174903091 instance=ExtResource("1_lni8d")]
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Button" to="VBoxContainer/HBoxContainer/Button" method="_on_pressed"]
|
||||
36
big_number_progress_bar.gd
Normal file
36
big_number_progress_bar.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
extends HBoxContainer
|
||||
|
||||
@export var currency: GameState.CurrencyType
|
||||
|
||||
@onready var _label_start = $LabelStart
|
||||
@onready var _label_end = $LabelEnd
|
||||
@onready var _progress_bar = $ProgressBar
|
||||
|
||||
var _start: BigNumber
|
||||
var _end: BigNumber
|
||||
var _current: BigNumber
|
||||
|
||||
func set_limits(start: BigNumber, end: BigNumber) -> void:
|
||||
_start = start
|
||||
_end = end
|
||||
|
||||
_label_start.text = _start.to_string_suffix(2)
|
||||
_label_end.text = _end.to_string_suffix(2)
|
||||
|
||||
_progress_bar.min_value = _start.get_ratio(_end)
|
||||
_progress_bar.max_value = _end.get_ratio(_start)
|
||||
|
||||
func _ready() -> void:
|
||||
match currency:
|
||||
GameState.CurrencyType.gold:
|
||||
GameState.gold_changed.connect(_on_currency_changed)
|
||||
_current = GameState.gold
|
||||
GameState.CurrencyType.gems:
|
||||
GameState.gems_changed.connect(_on_currency_changed)
|
||||
_current = GameState.gems
|
||||
|
||||
set_limits(_current, _current.add(BigNumber.new(1, 1)))
|
||||
|
||||
func _on_currency_changed(currency: BigNumber) -> void:
|
||||
_current = currency
|
||||
_progress_bar.value = currency.get_ratio(_end)
|
||||
1
big_number_progress_bar.gd.uid
Normal file
1
big_number_progress_bar.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bom3jmnv0cvon
|
||||
23
big_number_progress_bar.tscn
Normal file
23
big_number_progress_bar.tscn
Normal file
@@ -0,0 +1,23 @@
|
||||
[gd_scene format=3 uid="uid://clu4rn1tl1s8t"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bom3jmnv0cvon" path="res://big_number_progress_bar.gd" id="1_ssa4w"]
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" unique_id=1174903091]
|
||||
offset_right = 298.0
|
||||
offset_bottom = 49.0
|
||||
alignment = 1
|
||||
script = ExtResource("1_ssa4w")
|
||||
|
||||
[node name="LabelStart" type="Label" parent="." unique_id=1661715778]
|
||||
layout_mode = 2
|
||||
text = "100
|
||||
"
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="." unique_id=577927023]
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LabelEnd" type="Label" parent="." unique_id=440697928]
|
||||
layout_mode = 2
|
||||
text = "100
|
||||
"
|
||||
31
currency_generator_database.gd
Normal file
31
currency_generator_database.gd
Normal file
@@ -0,0 +1,31 @@
|
||||
extends Node
|
||||
|
||||
var items: Dictionary = {}
|
||||
|
||||
func _ready():
|
||||
load_item_data()
|
||||
|
||||
func load_item_data():
|
||||
# 1. Open the JSON file
|
||||
var file = FileAccess.open("res://generator_data.json", FileAccess.READ)
|
||||
var json_text = file.get_as_text()
|
||||
|
||||
# 2. Parse it into a Godot Dictionary
|
||||
var parsed_data = JSON.parse_string(json_text)
|
||||
|
||||
# 3. Loop through and create Custom Resources
|
||||
for key in parsed_data:
|
||||
var data = CurrencyGeneratorData.new()
|
||||
var item_info = parsed_data[key]
|
||||
|
||||
# Populate the resource
|
||||
data.id = key
|
||||
data.name = key
|
||||
data.initial_cost = item_info["initial_cost"]
|
||||
data.coefficient = item_info["coefficient"]
|
||||
data.initial_time = item_info["initial_time"]
|
||||
data.initial_revenue = item_info["initial_revenue"]
|
||||
data.initial_productivity = item_info["initial_productivity"]
|
||||
|
||||
# Store it in our database dictionary
|
||||
items[key] = data
|
||||
1
currency_generator_database.gd.uid
Normal file
1
currency_generator_database.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cjuinqoug01v1
|
||||
16
currency_label.gd
Normal file
16
currency_label.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
class_name CurrencyLabel
|
||||
extends Label
|
||||
|
||||
@export var currency: GameState.CurrencyType
|
||||
|
||||
func _ready() -> void:
|
||||
match currency:
|
||||
GameState.CurrencyType.gold:
|
||||
GameState.gold_changed.connect(_on_currency_changed)
|
||||
GameState.CurrencyType.gems:
|
||||
GameState.gems_changed.connect(_on_currency_changed)
|
||||
|
||||
func _on_currency_changed(currency: BigNumber) -> void:
|
||||
text = currency.to_string_sci(0)
|
||||
|
||||
|
||||
1
currency_label.gd.uid
Normal file
1
currency_label.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://sp67wcb6s5nw
|
||||
76
game_state.gd
Normal file
76
game_state.gd
Normal file
@@ -0,0 +1,76 @@
|
||||
extends Node
|
||||
# This script is added as an Autoload named 'GameState'
|
||||
|
||||
# ==========================================
|
||||
# SIGNALS
|
||||
# ==========================================
|
||||
signal gold_changed(new_amount: BigNumber)
|
||||
signal gems_changed(new_amount: BigNumber)
|
||||
|
||||
enum CurrencyType { gold, gems }
|
||||
|
||||
# ==========================================
|
||||
# STATE VARIABLES
|
||||
# ==========================================
|
||||
var gold: BigNumber = BigNumber.from_float(0.0)
|
||||
var gems: BigNumber = BigNumber.from_float(0.0)
|
||||
|
||||
var last_save_time: int = 0 # Unix timestamp
|
||||
|
||||
static var file_name: String = "user://idle_save.json"
|
||||
|
||||
func _ready() -> void:
|
||||
load_game()
|
||||
|
||||
# ==========================================
|
||||
# STATE MODIFIERS
|
||||
# ==========================================
|
||||
func add_gold(amount: BigNumber) -> void:
|
||||
gold.add_in_place(amount)
|
||||
gold_changed.emit(gold)
|
||||
|
||||
func spend_gold(cost: BigNumber) -> bool:
|
||||
if gold.is_greater_than(cost) or gold.is_equal_to(cost):
|
||||
var negative_cost = BigNumber.new(-cost.mantissa, cost.exponent)
|
||||
gold.add_in_place(negative_cost)
|
||||
gold_changed.emit(gold)
|
||||
return true
|
||||
return false
|
||||
|
||||
func add_gems(amount: BigNumber) -> void:
|
||||
gems.add_in_place(amount)
|
||||
gems_changed.emit(gems)
|
||||
|
||||
func spend_gems(cost: BigNumber) -> bool:
|
||||
if gems.is_greater_than(cost) or gems.is_equal_to(cost):
|
||||
var negative_cost = BigNumber.new(-cost.mantissa, cost.exponent)
|
||||
gems.add_in_place(negative_cost)
|
||||
gems_changed.emit(gems)
|
||||
return true
|
||||
return false
|
||||
|
||||
# ==========================================
|
||||
# PERSISTENCE (Save/Load)
|
||||
# ==========================================
|
||||
func save_game() -> void:
|
||||
var save_data = {
|
||||
"gold": gold.serialize(),
|
||||
"gems": gems.serialize(),
|
||||
"last_save_time": Time.get_unix_time_from_system()
|
||||
}
|
||||
|
||||
var file = FileAccess.open(file_name, FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(JSON.stringify(save_data))
|
||||
|
||||
func load_game() -> void:
|
||||
if not FileAccess.file_exists(file_name):
|
||||
return
|
||||
|
||||
var file = FileAccess.open(file_name, FileAccess.READ)
|
||||
if file:
|
||||
var parsed = JSON.parse_string(file.get_as_text())
|
||||
if parsed is Dictionary:
|
||||
gold = BigNumber.deserialize(parsed.get("gold", {}))
|
||||
gems = BigNumber.deserialize(parsed.get("gems", {}))
|
||||
last_save_time = parsed.get("last_save_time", Time.get_unix_time_from_system())
|
||||
1
game_state.gd.uid
Normal file
1
game_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d2j7tvlgxr2jp
|
||||
20
generator/currency_generator.gd
Normal file
20
generator/currency_generator.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
class_name CurrencyGenerator
|
||||
extends Node
|
||||
|
||||
@export var currency: GameState.CurrencyType
|
||||
@export var data: CurrencyGeneratorData
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
func _on_pressed() -> void:
|
||||
match currency:
|
||||
GameState.CurrencyType.gold: GameState.add_gold(BigNumber.new(1, 0))
|
||||
GameState.CurrencyType.gems: GameState.add_gems(BigNumber.new(1, 0))
|
||||
_: print("c")
|
||||
pass
|
||||
1
generator/currency_generator.gd.uid
Normal file
1
generator/currency_generator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dtbxopw6ulhl8
|
||||
18
generator/currency_generator_data.gd
Normal file
18
generator/currency_generator_data.gd
Normal file
@@ -0,0 +1,18 @@
|
||||
class_name CurrencyGeneratorData
|
||||
extends Resource
|
||||
|
||||
@export var id: GameState.CurrencyType
|
||||
@export var name: String
|
||||
@export var initial_cost: float
|
||||
@export var coefficient: float
|
||||
@export var initial_time: float
|
||||
@export var initial_revenue: float
|
||||
@export var initial_productivity: float
|
||||
|
||||
## Returns cost of next generator
|
||||
func cost_next(owned: int) -> float:
|
||||
return initial_cost * pow(coefficient, owned)
|
||||
|
||||
## Returns value produced
|
||||
func production_total(owned: int, multipliers: float) -> float:
|
||||
return (initial_productivity * owned) * multipliers
|
||||
1
generator/currency_generator_data.gd.uid
Normal file
1
generator/currency_generator_data.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b00tqsuhxdy0d
|
||||
37
generator_data.json
Normal file
37
generator_data.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"generator_1": {
|
||||
"initial_cost": 10,
|
||||
"coefficient": 1.15,
|
||||
"initial_time": 0.6,
|
||||
"initial_revenue": 1.0,
|
||||
"initial_productivity": 1.67
|
||||
},
|
||||
"generator_2": {
|
||||
"initial_cost": 60.0,
|
||||
"coefficient": 1.15,
|
||||
"initial_time": 3.0,
|
||||
"initial_revenue": 60.0,
|
||||
"initial_productivity": 20.0
|
||||
},
|
||||
"generator_3": {
|
||||
"initial_cost": 720.0,
|
||||
"coefficient": 1.14,
|
||||
"initial_time": 6.0,
|
||||
"initial_revenue": 540.0,
|
||||
"initial_productivity": 90.0
|
||||
},
|
||||
"generator_4": {
|
||||
"initial_cost": 8640.0,
|
||||
"coefficient": 1.13,
|
||||
"initial_time": 12.0,
|
||||
"initial_revenue": 4320.0,
|
||||
"initial_productivity": 360.0
|
||||
},
|
||||
"generator_5": {
|
||||
"initial_cost": 103680.0,
|
||||
"coefficient": 1.12,
|
||||
"initial_time": 24.0,
|
||||
"initial_revenue": 51840.0,
|
||||
"initial_productivity": 2160.0
|
||||
}
|
||||
}
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 995 B |
43
icon.svg.import
Normal file
43
icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bgtt3wu43tajh"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
27
project.godot
Normal file
27
project.godot
Normal file
@@ -0,0 +1,27 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Idles"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
GameState="*uid://d2j7tvlgxr2jp"
|
||||
|
||||
[physics]
|
||||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
|
||||
[rendering]
|
||||
|
||||
rendering_device/driver.windows="d3d12"
|
||||
Reference in New Issue
Block a user