systems rework: buffs, prestige graph, research, modular architecture

Decouples core systems from centralized globals in favor of catalogue-based architecture and data-driven content.

   Key changes:
   - Prestige: node-based buff tech tree, graph panel, progress bar
   - Research: multi-worker system, per-generator research, xp generation
   - Ascension: meta-currency layer via multi-currency prestige resets
   - Buffs: refactored as generator-independent via catalogues
   - UI: currency panel, edge-scrolling camera + zoom, current-goal panel
   - Alchemy Tower: crafting building with recipe/cost system
   - Testing: 7 test suites (ascension, prestige, research, goals)
   - Content: migrated from hardcoded idles/ to gym format (tiny_sword)
This commit was merged in pull request #1.
This commit is contained in:
2026-05-07 20:36:21 +00:00
parent 2743fd314a
commit 81a4058b04
242 changed files with 11226 additions and 2242 deletions

118
core/edge_scroll_camera.gd Normal file
View File

@@ -0,0 +1,118 @@
## Scrolls the camera when the mouse cursor reaches the screen edge.
## Attach to a Camera2D node. Requires an OrthographicCamera2D or ViewportContainer
## to determine screen bounds at runtime.
@tool
extends Camera2D
## Speed at which the camera scrolls (pixels per second).
@export var scroll_speed: float = 400.0
## Distance from the edge (in pixels) that triggers scrolling.
@export var edge_margin: float = 40.0
## Whether to clamp the camera position to a specific region.
@export var clamp_enabled: bool = false
## Minimum bounds when clamping (in world space).
@export var clamp_min: Vector2 = Vector2.ZERO
## Maximum bounds when clamping (in world space).
@export var clamp_max: Vector2 = Vector2.ZERO
## Zoom sensitivity per mouse wheel tick.
@export var zoom_sensitivity: float = 0.1
## Minimum zoom level.
@export var zoom_min: float = 0.25
## Maximum zoom level.
@export var zoom_max: float = 3.0
var _screen_rect: Rect2
func _ready() -> void:
_update_screen_rect()
var viewport: Viewport = get_viewport()
if viewport != null:
viewport.size_changed.connect(_update_screen_rect)
func _process(delta: float) -> void:
if Engine.is_editor_hint():
queue_redraw()
return
if is_zero_approx(scroll_speed):
return
var mouse_pos: Vector2 = get_viewport().get_mouse_position()
var scroll_dir: Vector2 = _get_scroll_direction(mouse_pos)
if scroll_dir != Vector2.ZERO:
position += scroll_dir * scroll_speed * delta
if clamp_enabled:
_clamp_position()
func _get_scroll_direction(mouse_pos: Vector2) -> Vector2:
var direction: Vector2 = Vector2.ZERO
if mouse_pos.x < _screen_rect.position.x + edge_margin:
direction.x -= 1.0
elif mouse_pos.x > _screen_rect.position.x + _screen_rect.size.x - edge_margin:
direction.x += 1.0
if mouse_pos.y < _screen_rect.position.y + edge_margin:
direction.y -= 1.0
elif mouse_pos.y > _screen_rect.position.y + _screen_rect.size.y - edge_margin:
direction.y += 1.0
return direction.normalized()
func _draw() -> void:
if not Engine.is_editor_hint():
return
if not clamp_enabled:
return
var rect_size: Vector2 = clamp_max - clamp_min
draw_rect(Rect2(clamp_min, rect_size), Color(1, 0.4, 0, 0.35))
draw_rect(Rect2(clamp_min, rect_size), Color(1, 0.6, 0.2, 0.8), false, 2.0)
func _clamp_position() -> void:
position.x = clampf(position.x, clamp_min.x, clamp_max.x)
position.y = clampf(position.y, clamp_min.y, clamp_max.y)
func _unhandled_input(event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if event is InputEventMouseButton:
var mouse_event: InputEventMouseButton = event
if mouse_event.button_index == MOUSE_BUTTON_WHEEL_UP:
_zoom(zoom_sensitivity, mouse_event.position)
elif mouse_event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_zoom(-zoom_sensitivity, mouse_event.position)
func _zoom(change: float, mouse_pos: Vector2) -> void:
var new_zoom_value: float = clampf(zoom.x + change, zoom_min, zoom_max)
if is_equal_approx(new_zoom_value, zoom.x):
return
var viewport_center: Vector2 = get_viewport_rect().size / 2.0
var offset: Vector2 = mouse_pos - viewport_center
var world_point: Vector2 = position + offset / zoom
zoom = Vector2(new_zoom_value, new_zoom_value)
position = world_point - offset / zoom
func _update_screen_rect() -> void:
var viewport: Viewport = get_viewport()
if viewport != null:
_screen_rect = viewport.get_visible_rect()