296 lines
11 KiB
GDScript
296 lines
11 KiB
GDScript
class_name Data
|
|
extends RefCounted
|
|
|
|
## Statische Spieldaten: Elemente, Rollen, Basiswerte, Traits, Preise.
|
|
|
|
enum Element { FEUER, WASSER, WIND, BLITZ, ERDE, EIS }
|
|
enum Role { TANK, DPS, HEILER }
|
|
|
|
const ELEMENT_COUNT := 6
|
|
const ELEMENT_NAMES := ["Feuer", "Wasser", "Wind", "Blitz", "Erde", "Eis"]
|
|
const ELEMENT_COLORS := [
|
|
Color(0.92, 0.31, 0.16),
|
|
Color(0.18, 0.52, 0.90),
|
|
Color(0.48, 0.85, 0.60),
|
|
Color(0.96, 0.84, 0.22),
|
|
Color(0.62, 0.45, 0.24),
|
|
Color(0.66, 0.92, 0.98),
|
|
]
|
|
const NONE_COLOR := Color(0.62, 0.62, 0.66)
|
|
|
|
const ROLE_NAMES := ["Tank", "DPS", "Heiler"]
|
|
const ROLE_SHORT := ["T", "D", "H"]
|
|
|
|
# Basiswerte auf 1-Star.
|
|
const ROLE_STATS := {
|
|
Role.TANK: {"hp": 700.0, "atk": 45.0, "armor": 0.40, "aspd": 0.60, "range": 1, "mana": 80.0},
|
|
Role.DPS: {"hp": 450.0, "atk": 65.0, "armor": 0.18, "aspd": 0.80, "range": 3, "mana": 100.0},
|
|
Role.HEILER: {"hp": 520.0, "atk": 38.0, "armor": 0.22, "aspd": 0.70, "range": 3, "mana": 90.0},
|
|
}
|
|
|
|
# Das Hauptelement prägt die Werte: Feuer schlägt hart, Erde hält aus, Wind ist
|
|
# schnell, Blitz trifft kritisch, Wasser zaubert öfter, Eis ist zäh und langsam.
|
|
const ELEMENT_PROFILE := {
|
|
Element.FEUER: {"hp": 0.90, "atk": 1.18, "armor": 0.0, "aspd": 1.0, "mana": 1.0, "crit": 0.0},
|
|
Element.WASSER: {"hp": 1.0, "atk": 0.95, "armor": 0.0, "aspd": 1.0, "mana": 0.75, "crit": 0.0},
|
|
Element.WIND: {"hp": 0.92, "atk": 0.95, "armor": 0.0, "aspd": 1.20, "mana": 1.0, "crit": 0.0},
|
|
Element.BLITZ: {"hp": 0.95, "atk": 1.0, "armor": 0.0, "aspd": 1.05, "mana": 1.0, "crit": 0.15},
|
|
Element.ERDE: {"hp": 1.22, "atk": 0.92, "armor": 0.08, "aspd": 0.90, "mana": 1.0, "crit": 0.0},
|
|
Element.EIS: {"hp": 1.08, "atk": 0.95, "armor": 0.05, "aspd": 0.95, "mana": 0.9, "crit": 0.0},
|
|
}
|
|
const PROFILE_HINT := {
|
|
Element.FEUER: "hart, aber weich", Element.WASSER: "zaubert oft", Element.WIND: "schnell",
|
|
Element.BLITZ: "kritisch", Element.ERDE: "zäh und langsam", Element.EIS: "robust",
|
|
}
|
|
|
|
# Das Hauptelement gibt der Fähigkeit ihre Form. Gilt für jede Rolle:
|
|
# ein Feuer-Heiler brennt, ein Erde-Tank erschüttert, ein Eis-Schütze friert.
|
|
const ELEMENT_FORM := {
|
|
Element.FEUER: {"name": "Flächenbrand", "desc": "trifft auch Nachbarn des Ziels (60%) und entzündet sie"},
|
|
Element.WASSER: {"name": "Welle", "desc": "gibt Verbündeten im Umkreis 2 je 20 Mana"},
|
|
Element.WIND: {"name": "Sturmschritt", "desc": "Verbündete im Umkreis 2 werden 3 s um 25% schneller"},
|
|
Element.BLITZ: {"name": "Kette", "desc": "springt auf 2 weitere Gegner (60%)"},
|
|
Element.ERDE: {"name": "Erschütterung","desc": "betäubt Gegner neben dem Ziel 0,6 s"},
|
|
Element.EIS: {"name": "Frostfeld", "desc": "verlangsamt Gegner um das Ziel 3 s um 30%"},
|
|
}
|
|
|
|
# Bots: wie gierig sie horten und wie gezielt sie jagen.
|
|
enum Difficulty { LEICHT, NORMAL, SCHWER }
|
|
const DIFFICULTY_NAMES := ["Leicht", "Normal", "Schwer"]
|
|
const BOT_GREED := {
|
|
Difficulty.LEICHT: [0.6, 0.9], Difficulty.NORMAL: [0.3, 0.8], Difficulty.SCHWER: [0.15, 0.4],
|
|
}
|
|
|
|
# Schere-Stein-Papier. Elemente im Ring: jedes schlägt genau eines und verliert
|
|
# gegen genau eines. Rollen als Dreieck. Nur das Hauptelement zählt.
|
|
const ELEMENT_BEATS := {
|
|
Element.WASSER: Element.FEUER, # Wasser löscht Feuer
|
|
Element.FEUER: Element.EIS, # Feuer schmilzt Eis
|
|
Element.EIS: Element.WIND, # Eis lähmt Wind
|
|
Element.WIND: Element.ERDE, # Wind trägt Erde ab
|
|
Element.ERDE: Element.BLITZ, # Erde erdet Blitz
|
|
Element.BLITZ: Element.WASSER, # Blitz leitet durch Wasser
|
|
}
|
|
const ROLE_BEATS := {
|
|
Role.TANK: Role.DPS, # Tank hält Schützen aus
|
|
Role.DPS: Role.HEILER, # Schütze bricht Heiler
|
|
Role.HEILER: Role.TANK, # Heiler überdauert Tank
|
|
}
|
|
const ELEMENT_COUNTER := 0.20
|
|
const ROLE_COUNTER := 0.15
|
|
|
|
# Multiplikator pro Sternstufe (Index = star - 1).
|
|
const STAR_MULT := [1.0, 1.8, 3.24]
|
|
const MAX_STAR := 3
|
|
|
|
# Jedes zusätzliche Element gibt einen kleinen Grundwert-Bonus.
|
|
const ELEMENT_STAT_BONUS := 0.06
|
|
|
|
# --- Oekonomie -------------------------------------------------------------
|
|
const START_HP := 100
|
|
const BASE_INCOME := 5
|
|
const WIN_GOLD := 1
|
|
const REROLL_COST := 2
|
|
const PRICE_NONE := 1
|
|
const PRICE_ELEMENT := 2
|
|
## Zinsen: je 10 Gold ein Gold dazu, gedeckelt bei 50 Gold.
|
|
const INTEREST_STEP := 10
|
|
const INTEREST_MAX := 5
|
|
const SHOP_SLOTS := 5
|
|
## Je aufgestelltem Element wird ein Shop-Platz zu 25% seltener neutral.
|
|
## Multiplikativ, damit Neutral nie ganz verschwindet.
|
|
const NEUTRAL_FALLOFF := 0.75
|
|
const BENCH_SLOTS := 9
|
|
const START_GOLD := 4
|
|
|
|
# --- Geteilter Pool --------------------------------------------------------
|
|
## Alle 8 Spieler teilen sich diesen Vorrat pro Element. Ein 3-Star bindet
|
|
## allein 9 Stück, dazu kommt was auf Bänken und Brettern liegt.
|
|
## Bei 100 hält der Vorrat eine ganze Partie, geht im Endspiel aber auf rund ein Fünftel.
|
|
const POOL_PER_ELEMENT := 100
|
|
|
|
# --- Board -----------------------------------------------------------------
|
|
const BOARD_COLS := 7
|
|
const BOARD_ROWS := 4
|
|
|
|
# --- Level und Erfahrung ---------------------------------------------------
|
|
## Das Spielerlevel ist die Anzahl der Kaderplätze. Jede Runde gibt XP,
|
|
## weitere XP kauft man. Level gegen Würfeln ist die TFT-Kernentscheidung.
|
|
const START_LEVEL := 3
|
|
const MAX_LEVEL := 9
|
|
const XP_PER_ROUND := 2
|
|
const XP_BUY_COST := 4
|
|
const XP_BUY_AMOUNT := 4
|
|
const XP_TABLE := {3: 4, 4: 8, 5: 12, 6: 20, 7: 32, 8: 48}
|
|
|
|
# --- Planung ---------------------------------------------------------------
|
|
const PLAN_SECONDS := 30.0
|
|
## Die ersten Runden gehen gegen die Wildnis, damit jeder in Ruhe aufbaut.
|
|
const PVE_ROUNDS := 2
|
|
const SAVE_PATH := "user://spielstand.json"
|
|
|
|
# --- Kampf -----------------------------------------------------------------
|
|
## Heiler heilen nur Verbündete in dieser Hex-Reichweite.
|
|
const HEAL_RANGE := 2
|
|
|
|
# --- Element-Traits --------------------------------------------------------
|
|
# steps = Anzahl aufgestellter Einheiten mit diesem Element.
|
|
const ELEMENT_TRAITS := {
|
|
Element.FEUER: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["+12% Angriff", "+26% Angriff", "+48% Angriff"],
|
|
"values": [0.12, 0.26, 0.48],
|
|
},
|
|
Element.WASSER: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["+20% Mana", "+40% Mana", "+70% Mana"],
|
|
"values": [0.20, 0.40, 0.70],
|
|
},
|
|
Element.WIND: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["+10% Tempo", "+22% Tempo", "+40% Tempo"],
|
|
"values": [0.10, 0.22, 0.40],
|
|
},
|
|
Element.BLITZ: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["+15% Krit", "+30% Krit", "+55% Krit"],
|
|
"values": [0.15, 0.30, 0.55],
|
|
},
|
|
Element.ERDE: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["+12% Leben", "+26% Leben", "+48% Leben"],
|
|
"values": [0.12, 0.26, 0.48],
|
|
},
|
|
Element.EIS: {
|
|
"steps": [2, 4, 6],
|
|
"desc": ["Treffer -12% Tempo", "Treffer -22% Tempo", "Treffer -35% Tempo"],
|
|
"values": [0.12, 0.22, 0.35],
|
|
},
|
|
}
|
|
|
|
# --- Kombi-Traits ----------------------------------------------------------
|
|
# Aktiv, wenn beide Elemente mindestens COMBO_MIN Einheiten haben.
|
|
const COMBO_MIN := 2
|
|
const COMBO_TRAITS := [
|
|
{"a": Element.FEUER, "b": Element.WIND, "key": "feuersturm", "name": "Feuersturm", "desc": "Fähigkeiten setzen in Brand"},
|
|
{"a": Element.WASSER, "b": Element.EIS, "key": "frost", "name": "Frost", "desc": "Gegner starten 15% langsamer"},
|
|
{"a": Element.BLITZ, "b": Element.ERDE, "key": "magnetsturm","name": "Magnetsturm", "desc": "Fähigkeiten betäuben 0.6s"},
|
|
{"a": Element.FEUER, "b": Element.ERDE, "key": "magma", "name": "Magma", "desc": "20% Schaden reflektiert"},
|
|
{"a": Element.WIND, "b": Element.BLITZ, "key": "sturmfront", "name": "Sturmfront", "desc": "Angriffe springen auf ein 2. Ziel"},
|
|
{"a": Element.WASSER, "b": Element.FEUER, "key": "dampf", "name": "Dampf", "desc": "18% Lebensraub"},
|
|
]
|
|
|
|
|
|
static func element_name(e: int) -> String:
|
|
return ELEMENT_NAMES[e]
|
|
|
|
|
|
static func element_color(e: int) -> Color:
|
|
return ELEMENT_COLORS[e]
|
|
|
|
|
|
static func role_name(r: int) -> String:
|
|
return ROLE_NAMES[r]
|
|
|
|
|
|
## Aktive Stufe eines Element-Traits (0 = inaktiv, sonst 1..3).
|
|
static func trait_tier(element: int, count: int) -> int:
|
|
var steps: Array = ELEMENT_TRAITS[element]["steps"]
|
|
var tier := 0
|
|
for i in steps.size():
|
|
if count >= steps[i]:
|
|
tier = i + 1
|
|
return tier
|
|
|
|
|
|
static func beats_element(attacker: int, defender: int) -> bool:
|
|
return attacker >= 0 and defender >= 0 and ELEMENT_BEATS.get(attacker, -1) == defender
|
|
|
|
|
|
static func beats_role(attacker: int, defender: int) -> bool:
|
|
return ROLE_BEATS.get(attacker, -1) == defender
|
|
|
|
|
|
## Schadensfaktor eines Angreifers gegen einen Verteidiger (Hauptelement und Rolle).
|
|
static func counter_multiplier(att_el: int, att_role: int, def_el: int, def_role: int) -> float:
|
|
var m := 1.0
|
|
if beats_element(att_el, def_el):
|
|
m += ELEMENT_COUNTER
|
|
elif beats_element(def_el, att_el):
|
|
m -= ELEMENT_COUNTER
|
|
if beats_role(att_role, def_role):
|
|
m += ROLE_COUNTER
|
|
elif beats_role(def_role, att_role):
|
|
m -= ROLE_COUNTER
|
|
return m
|
|
|
|
|
|
## Element, das dieses Element schlägt (das Gegenstück zu ELEMENT_BEATS).
|
|
static func beaten_by(element: int) -> int:
|
|
for e in ELEMENT_BEATS:
|
|
if ELEMENT_BEATS[e] == element:
|
|
return e
|
|
return -1
|
|
|
|
|
|
static func role_beaten_by(role: int) -> int:
|
|
for r in ROLE_BEATS:
|
|
if ROLE_BEATS[r] == role:
|
|
return r
|
|
return -1
|
|
|
|
|
|
## Was die Fähigkeit einer Rolle auf einer Sternstufe tut.
|
|
static func star_effect(role: int, star: int) -> String:
|
|
match role:
|
|
Role.TANK:
|
|
return "Schild und spottet 3 Gegner" if star == 2 else "Schild, Spott und 25% Reflexion"
|
|
Role.DPS:
|
|
return "Ziel plus Splash auf Nachbarn" if star == 2 else "Ziel plus Fläche im Radius 2"
|
|
_:
|
|
return "heilt 3 Verbündete" if star == 2 else "heilt alle und schildet sie"
|
|
|
|
|
|
## Zusatzwirkung eines Nebenelements.
|
|
static func element_effect(element: int) -> String:
|
|
match element:
|
|
Element.FEUER: return "Brand"
|
|
Element.WASSER: return "Mana und stärkere Heilung"
|
|
Element.WIND: return "Tempo-Schub"
|
|
Element.BLITZ: return "Kettenblitz"
|
|
Element.ERDE: return "Betäubung"
|
|
_: return "Verlangsamung"
|
|
|
|
|
|
## XP bis zum nächsten Level, 0 auf Maximalstufe.
|
|
static func xp_needed(level: int) -> int:
|
|
return XP_TABLE.get(level, 0)
|
|
|
|
|
|
## Grundschaden einer verlorenen Runde, steigt alle vier Runden.
|
|
static func round_base_damage(round_no: int) -> int:
|
|
return 2 + int(float(round_no - 1) / 4.0)
|
|
|
|
|
|
## Wahrscheinlichkeit, dass ein Shop-Platz einen neutralen Avatar zeigt.
|
|
## Ohne eigene Elemente gibt es nur Neutral.
|
|
static func neutral_chance(unlocked_count: int) -> float:
|
|
return pow(NEUTRAL_FALLOFF, float(unlocked_count))
|
|
|
|
|
|
## Zinsen auf das gehaltene Gold.
|
|
static func interest(gold: int) -> int:
|
|
return mini(INTEREST_MAX, int(float(gold) / float(INTEREST_STEP)))
|
|
|
|
|
|
## Streak-Bonusgold (Sieg- oder Niederlagenserie).
|
|
static func streak_gold(streak: int) -> int:
|
|
var s := absi(streak)
|
|
if s >= 5:
|
|
return 3
|
|
if s == 4:
|
|
return 2
|
|
if s == 3:
|
|
return 1
|
|
return 0
|