Restructure into core/ and content/official/
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
extends Node
|
||||
|
||||
## Loads modifier definitions and serves them by id.
|
||||
##
|
||||
## ⚠️ PROTOTYPE. This is scaffolding, not the real mod loader.
|
||||
##
|
||||
## What's wrong with it, to be fixed in Phase 3:
|
||||
## - DirAccess cannot walk into a PCK. This works in the editor and silently
|
||||
## returns NOTHING in an exported build. Needs a build-time manifest.
|
||||
## - No namespacing. Ids are bare strings, so two mods can collide.
|
||||
## - No mod.json, no api_version, no dependency resolution.
|
||||
## - Only reads content/official/. Doesn't touch user://mods/.
|
||||
##
|
||||
## Autoload name is ModRegistry (PascalCase), so call sites read as
|
||||
## ModRegistry.get_by_id(...) rather than looking like a local variable.
|
||||
|
||||
const MODIFIER_DIR := "res://content/official/modifiers/"
|
||||
|
||||
var modifiers: Array[Modifier] = []
|
||||
var _by_id: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_load_from_folder(MODIFIER_DIR)
|
||||
_reindex()
|
||||
print("ModRegistry: loaded %d modifiers." % modifiers.size())
|
||||
|
||||
|
||||
## Recursive, because each modifier now lives in its own folder:
|
||||
## modifiers/mjolnir/mjolnir.tres
|
||||
func _load_from_folder(path: String) -> void:
|
||||
var dir := DirAccess.open(path)
|
||||
if dir == null:
|
||||
push_warning("ModRegistry: can't open %s" % path)
|
||||
return
|
||||
|
||||
dir.list_dir_begin()
|
||||
var fname := dir.get_next()
|
||||
while fname != "":
|
||||
var full := path.path_join(fname)
|
||||
if dir.current_is_dir():
|
||||
if not fname.begins_with("."):
|
||||
_load_from_folder(full)
|
||||
elif fname.ends_with(".tres"):
|
||||
var res := load(full)
|
||||
if res is Modifier:
|
||||
modifiers.append(res)
|
||||
else:
|
||||
push_warning("ModRegistry: %s is not a Modifier" % full)
|
||||
fname = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
|
||||
|
||||
func _reindex() -> void:
|
||||
_by_id.clear()
|
||||
for m in modifiers:
|
||||
if m.id.is_empty():
|
||||
push_warning("ModRegistry: modifier with empty id, skipping.")
|
||||
continue
|
||||
if _by_id.has(m.id):
|
||||
push_warning("ModRegistry: duplicate id '%s'." % m.id)
|
||||
_by_id[m.id] = m
|
||||
|
||||
|
||||
func get_by_id(id: String) -> Modifier:
|
||||
return _by_id.get(id)
|
||||
|
||||
|
||||
## Pick `count` distinct modifiers, weighted by rarity.
|
||||
func pick_random(count: int) -> Array[Modifier]:
|
||||
var pool := modifiers.duplicate()
|
||||
var chosen: Array[Modifier] = []
|
||||
count = mini(count, pool.size())
|
||||
|
||||
for _i in count:
|
||||
var total := 0.0
|
||||
for m in pool:
|
||||
total += maxf(m.weight, 0.0001)
|
||||
|
||||
var r := randf() * total
|
||||
var acc := 0.0
|
||||
var picked := 0
|
||||
for j in pool.size():
|
||||
acc += maxf(pool[j].weight, 0.0001)
|
||||
if r <= acc:
|
||||
picked = j
|
||||
break
|
||||
|
||||
chosen.append(pool[picked])
|
||||
pool.remove_at(picked) # distinct — don't offer the same one twice
|
||||
|
||||
return chosen
|
||||
@@ -0,0 +1 @@
|
||||
uid://btm4dwci50xmu
|
||||
@@ -0,0 +1,40 @@
|
||||
extends Resource
|
||||
class_name Modifier
|
||||
# One modifier definition: title + description + texture + scoring rule.
|
||||
# Create instances of this as .tres files in the editor (right-click in
|
||||
# FileSystem -> New Resource -> Modifier), OR build them in code in the catalog.
|
||||
#
|
||||
# Because a texture always maps to the same modifier, the texture lives HERE.
|
||||
|
||||
# A stable unique id (e.g. "stars_x5"). Useful for saving which modifiers are
|
||||
# applied to which segments, and for lookups. Keep it unique across the catalog.
|
||||
@export var id: String = ""
|
||||
|
||||
# Shown to the player when offered the piece.
|
||||
@export var title: String = ""
|
||||
@export_multiline var description: String = ""
|
||||
|
||||
# The texture painted onto the segment when this modifier is applied.
|
||||
@export var texture: Texture2D
|
||||
|
||||
# --- Scoring rule ---
|
||||
# Simple, data-only rules cover most cases without writing code per modifier.
|
||||
# value = (base_value * mult + add) for the affected segment.
|
||||
@export var score_mult: float = 1.0
|
||||
@export var score_add: int = 0
|
||||
|
||||
# Optional: rarity weight for random selection (higher = more common).
|
||||
@export var weight: float = 1.0
|
||||
|
||||
# Apply this modifier's rule to a segment's base value.
|
||||
func apply_score(base_value: int) -> int:
|
||||
return int(round(base_value * score_mult)) + score_add
|
||||
|
||||
# A short human-readable summary of the effect, for tooltips/UI.
|
||||
func effect_text() -> String:
|
||||
var parts: Array[String] = []
|
||||
if score_mult != 1.0:
|
||||
parts.append("x%s" % str(score_mult))
|
||||
if score_add != 0:
|
||||
parts.append(("+%d" % score_add) if score_add > 0 else str(score_add))
|
||||
return " ".join(parts) if parts.size() > 0 else "no change"
|
||||
@@ -0,0 +1 @@
|
||||
uid://duh0lxb7gdimr
|
||||
Reference in New Issue
Block a user