This commit is contained in:
team3
2026-09-16 12:09:33 +02:00
parent 9410d18ea5
commit 8a188c5d5c
29 changed files with 2016 additions and 564 deletions

View File

@@ -70,13 +70,16 @@ func base_stats() -> Dictionary:
var b: Dictionary = Data.ROLE_STATS[role]
var mult: float = Data.STAR_MULT[star - 1]
var ebonus := 1.0 + Data.ELEMENT_STAT_BONUS * float(maxi(0, elements.size() - 1))
var prof: Dictionary = Data.ELEMENT_PROFILE.get(primary_element(),
{"hp": 1.0, "atk": 1.0, "armor": 0.0, "aspd": 1.0, "mana": 1.0, "crit": 0.0})
return {
"hp": b["hp"] * mult * ebonus,
"atk": b["atk"] * mult * ebonus,
"armor": b["armor"],
"aspd": b["aspd"],
"hp": b["hp"] * mult * ebonus * prof["hp"],
"atk": b["atk"] * mult * ebonus * prof["atk"],
"armor": minf(0.75, b["armor"] + prof["armor"]),
"aspd": b["aspd"] * prof["aspd"],
"range": b["range"],
"mana": b["mana"],
"mana": b["mana"] * prof["mana"],
"crit": prof["crit"],
}

View File

@@ -22,31 +22,111 @@ func setup(rand: RandomNumberGenerator) -> void:
greed = rand.randf_range(0.3, 0.8)
func take_turn(p: PlayerState, pool: Pool, rand: RandomNumberGenerator, round_no: int) -> void:
func take_turn(p: PlayerState, pool: Pool, rand: RandomNumberGenerator) -> void:
_do_upgrades(p, pool)
_do_buys(p, pool, rand, round_no)
_do_placements(p, pool, round_no)
_do_buys(p, pool)
_do_placements(p)
_do_upgrades(p, pool)
var keep := int(lerpf(2.0, 18.0, greed))
if p.gold > keep + Data.REROLL_COST and rand.randf() < 0.6:
p.reroll(pool, rand)
_do_buys(p, pool, rand, round_no)
_do_placements(p, pool, round_no)
_do_upgrades(p, pool)
# Wer kurz vor einem Upgrade steht, würfelt gezielt nach Futter.
var hunting := _closest_upgrade_gap(p) <= 3
var rolls := 3 if hunting else 1
for i in rolls:
if p.gold > keep + Data.REROLL_COST and (hunting or rand.randf() < 0.6):
p.reroll(pool, rand)
_do_buys(p, pool)
_do_placements(p)
_do_upgrades(p, pool)
# Level kaufen, wenn das Brett voll ist und Nachwuchs auf der Bank wartet.
while p.gold > keep + Data.XP_BUY_COST and not p.can_place() \
and _pick_bench_for_board(p) >= 0 and p.buy_xp():
_do_placements(p)
_free_bench(p, pool)
## Stellt das Brett gegen den bekannten Gegner um: Tanks vor seine Schützen,
## eigene Schützen weg von seinen Tanks. Ohne Gegner gilt die Standardaufstellung.
func arrange(p: PlayerState, opp: PlayerState) -> void:
if p.board.is_empty():
return
var units: Array = p.board_units()
p.board.clear()
var threat := [] # Spalten (gespiegelt), in denen gegnerische Schützen stehen
var enemy_tanks := [] # Spalten (gespiegelt) gegnerischer Tanks
if opp != null:
for hex in opp.board:
var col: int = Data.BOARD_COLS - 1 - hex.x
if opp.board[hex].role == Data.Role.TANK:
enemy_tanks.append(col)
else:
threat.append(col)
var center := [3, 2, 4, 1, 5, 0, 6]
var tank_cols := threat.duplicate()
for c in center:
if not tank_cols.has(c):
tank_cols.append(c)
# Schützen möglichst weit weg von gegnerischen Tanks.
var far_cols := center.duplicate()
far_cols.sort_custom(func(a, b):
return _min_dist(a, enemy_tanks) > _min_dist(b, enemy_tanks))
units.sort_custom(func(a, b): return a.role == Data.Role.TANK and b.role != Data.Role.TANK)
for u in units:
var rows: Array = [0, 1, 2, 3] if u.role == Data.Role.TANK else [3, 2, 1, 0]
var cols: Array = tank_cols if u.role == Data.Role.TANK else far_cols
var placed := false
for r in rows:
for c in cols:
var h := Vector2i(c, r)
if not p.board.has(h):
p.board[h] = u
placed = true
break
if placed:
break
func _min_dist(col: int, cols: Array) -> int:
if cols.is_empty():
return 0
var best := 99
for c in cols:
best = mini(best, absi(col - c))
return best
## Kleinste Zahl fehlender Futter-Avatare über alle Board-Einheiten.
func _closest_upgrade_gap(p: PlayerState) -> int:
var best := 99
for hex in p.board:
var miss := p.upgrade_missing(hex)
if p.board[hex].upgrade_cost().is_empty():
continue
var total := 0
for e in miss:
total += miss[e]
best = mini(best, total)
return best
## Elemente, die irgendeine Board-Einheit für ihr nächstes Upgrade braucht.
func _needed_elements(p: PlayerState) -> Dictionary:
var need := {}
for hex in p.board:
for e in p.board[hex].upgrade_cost():
need[e] = true
return need
func _wants(entry: ShopEntry, p: PlayerState) -> bool:
if entry.element < 0:
# Neutral wird beim Kauf spezialisiert, also immer brauchbar.
return true
if pref.has(entry.element):
if _needed_elements(p).has(entry.element):
return true
# Elemente, die schon auf dem Board sind, sind immer Futter.
return p.trait_counts().has(entry.element)
return pref.has(entry.element)
func _do_buys(p: PlayerState, pool: Pool, rand: RandomNumberGenerator, round_no: int) -> void:
func _do_buys(p: PlayerState, pool: Pool) -> void:
for i in p.shop.size():
var e: ShopEntry = p.shop[i]
if e.sold:
@@ -60,8 +140,8 @@ func _do_buys(p: PlayerState, pool: Pool, rand: RandomNumberGenerator, round_no:
p.buy(i, pool, _pick_element(p) if e.element < 0 else e.element)
func _do_placements(p: PlayerState, pool: Pool, round_no: int) -> void:
while p.can_place(round_no):
func _do_placements(p: PlayerState) -> void:
while p.can_place():
var idx := _pick_bench_for_board(p)
if idx < 0:
return
@@ -69,7 +149,7 @@ func _do_placements(p: PlayerState, pool: Pool, round_no: int) -> void:
var hex := _pick_hex(p, a.role)
if hex.x < 0:
return
if not p.place(idx, hex, round_no):
if not p.place(idx, hex):
return
@@ -77,6 +157,7 @@ func _do_placements(p: PlayerState, pool: Pool, round_no: int) -> void:
## Alles Weitere bleibt als Futter auf der Bank.
func _pick_bench_for_board(p: PlayerState) -> int:
var counts := p.trait_counts()
var need := _needed_elements(p)
var best := -1
var best_score := -1
for i in p.bench.size():
@@ -84,7 +165,9 @@ func _pick_bench_for_board(p: PlayerState) -> int:
if a == null:
continue
var score := 0
if pref.has(a.elements[0]):
if need.has(a.elements[0]) and counts.get(a.elements[0], 0) > 0:
score = 0 # Futter für ein laufendes Upgrade bleibt auf der Bank
elif pref.has(a.elements[0]):
score = 3 if counts.get(a.elements[0], 0) == 0 else 1
else:
score = 2 if counts.get(a.elements[0], 0) == 0 else 0
@@ -144,12 +227,16 @@ func _do_upgrades(p: PlayerState, pool: Pool) -> void:
func _free_bench(p: PlayerState, pool: Pool) -> void:
if p.free_bench_slot() >= 0:
return
var counts := p.trait_counts()
var need := _needed_elements(p)
# Erst verkaufen, was weder Futter noch Lieblingselement ist.
for i in p.bench.size():
var a = p.bench[i]
if a == null:
continue
if counts.get(a.elements[0], 0) == 0 and not pref.has(a.elements[0]):
if a != null and not need.has(a.elements[0]) and not pref.has(a.elements[0]):
p.sell_bench(i, pool)
return
for i in p.bench.size():
var a = p.bench[i]
if a != null and not need.has(a.elements[0]):
p.sell_bench(i, pool)
return
p.sell_bench(0, pool)

View File

@@ -47,6 +47,8 @@ class CUnit extends RefCounted:
var target = null
var cast_flash: float = 0.0
var hit_flash: float = 0.0
var dealt: float = 0.0 ## ausgeteilter Schaden, für den Rückblick
var healed: float = 0.0
func effective_aspd() -> float:
return maxf(0.1, aspd * (1.0 + haste) * (1.0 - slow))
@@ -57,7 +59,12 @@ var time: float = 0.0
var finished: bool = false
var winner: int = -1 ## 0 = Team A, 1 = Team B, -1 = unentschieden
var survivors: int = 0
var survivor_stars: int = 0 ## Sternsumme der Überlebenden, bestimmt den Schaden
var events: Array = [] ## kurze Textmeldungen für das Log
## Treffer, Heilungen und Zauber für Anzeige und Sound. Nur im Spielerkampf
## aufgezeichnet, Bot-Kämpfe laufen still durch.
var record_hits: bool = false
var hits: Array = []
var _mods := [{}, {}]
var _occ := {}
@@ -116,7 +123,7 @@ func _spawn(p: PlayerState, team: int) -> void:
u.max_mana = s["mana"]
u.mana = 0.0
u.mana_mult = m["mana"]
u.crit = m["crit"]
u.crit = m["crit"] + s.get("crit", 0.0)
if _occ.has(u.pos):
u.pos = _free_near(u.pos)
_occ[u.pos] = u
@@ -266,6 +273,9 @@ func _basic_attack(u: CUnit, t: CUnit) -> void:
var dmg := u.atk
if u.crit > 0.0 and _rand.randf() < u.crit:
dmg *= CRIT_MULT
if record_hits:
hits.append({"kind": "attack", "pos": t.pos, "from": u.pos, "team": u.team,
"ranged": u.atk_range > 1, "element": u.avatar.primary_element(), "amount": 0, "t": time})
_apply_damage(u, t, dmg, false)
var ice: float = _mods[u.team]["ice"]
if ice > 0.0:
@@ -291,6 +301,10 @@ func _apply_damage(src, tgt: CUnit, amount: float, is_true: bool) -> void:
if dmg > 0.0:
tgt.hp -= dmg
tgt.hit_flash = 0.12
if src != null:
src.dealt += dmg
if record_hits and src != null:
_record("crit" if amount > src.atk * 1.4 and not is_true else "dmg", tgt, dmg)
tgt.mana += MANA_PER_HIT * tgt.mana_mult
if tgt.mana >= tgt.max_mana and tgt.alive and tgt.hp > 0.0:
_cast(tgt)
@@ -319,8 +333,17 @@ func _kill(u: CUnit) -> void:
func _heal(u: CUnit, amount: float) -> void:
if u.alive:
u.hp = minf(u.max_hp, u.hp + amount)
if not u.alive:
return
var real := minf(u.max_hp, u.hp + amount) - u.hp
u.hp += real
u.healed += real
if record_hits and real >= 1.0:
_record("heal", u, real)
func _record(kind: String, u: CUnit, amount: float) -> void:
hits.append({"kind": kind, "pos": u.pos, "team": u.team, "amount": int(round(amount)), "t": time})
# --- Fähigkeiten ----------------------------------------------------------
@@ -328,6 +351,8 @@ func _heal(u: CUnit, amount: float) -> void:
func _cast(u: CUnit) -> void:
u.mana = 0.0
u.cast_flash = 0.35
if record_hits:
_record("cast", u, 0.0)
var elems: Array = u.avatar.elements
var heal_bonus := 0.0
for i in elems.size():
@@ -373,7 +398,7 @@ func _cast(u: CUnit) -> void:
hit_enemies.append(o)
Data.Role.HEILER:
var amount := u.atk * (3.0 - 0.25 * float(u.avatar.star - 1)) * (1.0 + heal_bonus)
var allies := _allies_by_missing_hp(u.team)
var allies := _allies_by_missing_hp(u.team, u)
var count := 1
if u.avatar.star == 2:
count = 3
@@ -428,11 +453,14 @@ func _apply_element_effects(u: CUnit, main_target, hit_enemies: Array) -> void:
main_target.slow_timer = maxf(main_target.slow_timer, 3.0)
func _allies_by_missing_hp(team: int) -> Array:
## Verbündete in Heilreichweite, schwächste zuerst. Mindestens der Heiler selbst.
func _allies_by_missing_hp(team: int, healer: CUnit) -> Array:
var list := []
for o in units:
if o.alive and o.team == team:
if o.alive and o.team == team and Hex.distance(o.pos, healer.pos) <= Data.HEAL_RANGE:
list.append(o)
if list.is_empty():
list.append(healer)
list.sort_custom(func(a, b): return (a.hp / a.max_hp) < (b.hp / b.max_hp))
return list
@@ -447,6 +475,40 @@ func alive_count(team: int) -> int:
return n
## Kurzer Rückblick für ein Team: Überlebende, stärkste Einheit, Gegnerspitze.
func recap(team: int) -> Dictionary:
var total := 0
var alive := 0
var best = null
var best_enemy = null
for u in units:
if u.team == team:
total += 1
if u.alive:
alive += 1
if best == null or u.dealt + u.healed > best.dealt + best.healed:
best = u
elif best_enemy == null or u.dealt > best_enemy.dealt:
best_enemy = u
return {
"total": total, "alive": alive,
"best_name": best.avatar.display_name() if best else "-",
"best_dealt": int(best.dealt) if best else 0,
"best_healed": int(best.healed) if best else 0,
"enemy_name": best_enemy.avatar.display_name() if best_enemy else "-",
"enemy_dealt": int(best_enemy.dealt) if best_enemy else 0,
"duration": time,
}
func _star_sum(team: int) -> int:
var n := 0
for u in units:
if u.alive and u.team == team:
n += u.avatar.star
return n
func _check_end() -> void:
var a := alive_count(0)
var b := alive_count(1)
@@ -455,9 +517,11 @@ func _check_end() -> void:
if a == b:
winner = -1
survivors = 0
survivor_stars = 0
else:
winner = 0 if a > b else 1
survivors = maxi(a, b)
survivor_stars = _star_sum(winner)
return
if time >= TIME_LIMIT:
_resolve_by_hp()
@@ -472,6 +536,8 @@ func _resolve_by_hp() -> void:
if absf(frac[0] - frac[1]) < 0.25:
winner = -1
survivors = 0
survivor_stars = 0
else:
winner = 0 if frac[0] > frac[1] else 1
survivors = alive_count(winner)
survivor_stars = _star_sum(winner)

View File

@@ -28,6 +28,21 @@ const ROLE_STATS := {
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",
}
# Multiplikator pro Sternstufe (Index = star - 1).
const STAR_MULT := [1.0, 1.8, 3.24]
const MAX_STAR := 3
@@ -55,13 +70,29 @@ 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 180 hält der Vorrat eine ganze Partie und kann im Endspiel trotzdem knapp werden.
const POOL_PER_ELEMENT := 180
## 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
const MAX_TEAM_SLOTS := 9
# --- 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
# --- Kampf -----------------------------------------------------------------
## Heiler heilen nur Verbündete in dieser Hex-Reichweite.
const HEAL_RANGE := 2
# --- Element-Traits --------------------------------------------------------
# steps = Anzahl aufgestellter Einheiten mit diesem Element.
@@ -133,9 +164,14 @@ static func trait_tier(element: int, count: int) -> int:
return tier
## Teamgröße wächst mit der Runde.
static func team_slots(round_no: int) -> int:
return mini(3 + int(float(round_no - 1) / 2.0), MAX_TEAM_SLOTS)
## 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.

View File

@@ -20,6 +20,11 @@ var pairings: Array = [] ## [{a, b, ghost}]
var player_combat: Combat = null
var pending: Array = [] ## Ergebnisse der Bot-Kämpfe
var last_round_log: Array = []
var plan_left: float = Data.PLAN_SECONDS
var timer_enabled: bool = true
var human_recap: Dictionary = {} ## Rückblick auf den letzten eigenen Kampf
## Zählt, wie oft der Mensch gewonnen bzw. verloren hat (für Sound und Anzeige).
var human_last_result: int = 0 ## 1 Sieg, -1 Niederlage, 0 unentschieden/keine
func start(seed_value: int = 0) -> void:
@@ -37,7 +42,9 @@ func start(seed_value: int = 0) -> void:
round_no = 1
for p in players:
p.roll_shop(pool, rand)
pairings = _make_pairings()
_run_bot_turns()
plan_left = Data.PLAN_SECONDS
phase = Phase.PLANNING
@@ -54,7 +61,21 @@ func alive_players() -> Array:
func _run_bot_turns() -> void:
for p in players:
if p.is_bot and p.alive:
bots[p.pid].take_turn(p, pool, rand, round_no)
bots[p.pid].take_turn(p, pool, rand)
bots[p.pid].arrange(p, opponent_of(p))
## Planungsuhr. Läuft sie ab, startet der Kampf von selbst.
## Gibt true zurück, wenn dadurch die Phase gewechselt hat.
func tick_planning(dt: float) -> bool:
if phase != Phase.PLANNING or not timer_enabled:
return false
plan_left -= dt
if plan_left <= 0.0:
plan_left = 0.0
start_combat()
return true
return false
# --- Kampfphase ------------------------------------------------------------
@@ -62,12 +83,16 @@ func _run_bot_turns() -> void:
func start_combat() -> void:
if phase != Phase.PLANNING:
return
pairings = _make_pairings()
# Abbilder erst jetzt kopieren, damit sie den aktuellen Stand zeigen.
for pr in pairings:
if pr["ghost"]:
pr["b"] = _make_ghost(pr["ghost_src"])
pending.clear()
player_combat = null
for pr in pairings:
if pr["a"] == human or pr["b"] == human:
player_combat = Combat.new(pr["a"], pr["b"], rand.randi())
player_combat.record_hits = true
pending.append({"pair": pr, "combat": player_combat})
else:
var c := Combat.new(pr["a"], pr["b"], rand.randi())
@@ -107,7 +132,7 @@ func _make_pairings() -> Array:
if others.is_empty():
return out
var src: PlayerState = others[rand.randi_range(0, others.size() - 1)]
out.append({"a": lone, "b": _make_ghost(src), "ghost": true})
out.append({"a": lone, "b": _make_ghost(src), "ghost": true, "ghost_src": src})
return out
@@ -129,18 +154,25 @@ func resolve_round() -> void:
c.run_to_end()
var a: PlayerState = pr["a"]
var b: PlayerState = pr["b"]
var dmg := 3 + c.survivors
var base := Data.round_base_damage(round_no)
var dmg := base + c.survivor_stars
if a == human or b == human:
human_last_result = 0 if c.winner < 0 else (1 if pr["a" if c.winner == 0 else "b"] == human else -1)
human_recap = c.recap(0 if a == human else 1)
human_recap["opponent"] = (b if a == human else a).pname
human_recap["result"] = human_last_result
human_recap["damage"] = dmg if c.winner >= 0 else base
if c.winner == 0:
_apply_result(a, b, dmg, pr["ghost"])
elif c.winner == 1:
_apply_result(b, a, dmg, pr["ghost"])
else:
a.register_result(false)
a.take_damage(3)
a.take_damage(base)
if not pr["ghost"]:
b.register_result(false)
b.take_damage(3)
last_round_log.append("%s vs %s: unentschieden" % [a.pname, b.pname])
b.take_damage(base)
last_round_log.append("%s vs %s: unentschieden (-%d)" % [a.pname, b.pname, base])
pending.clear()
player_combat = null
_check_eliminations()
@@ -176,13 +208,26 @@ func _next_round() -> void:
if not p.alive:
continue
p.gold += p.round_income()
p.roll_shop(pool, rand)
p.gain_xp(Data.XP_PER_ROUND)
p.new_round_shop(pool, rand)
pairings = _make_pairings()
_run_bot_turns()
plan_left = Data.PLAN_SECONDS
phase = Phase.PLANNING
# --- Hilfen für die UI ----------------------------------------------------
## Gegner eines Spielers in der aktuellen Paarung (Abbilder liefern ihre Quelle).
func opponent_of(p: PlayerState) -> PlayerState:
for pr in pairings:
if pr["a"] == p:
return pr["ghost_src"] if pr["ghost"] else pr["b"]
if pr["b"] == p:
return pr["a"]
return null
func human_opponent() -> PlayerState:
for pr in pairings:
if pr["a"] == human:

View File

@@ -12,6 +12,10 @@ var streak: int = 0 ## >0 Siegserie, <0 Niederlagenserie
var alive: bool = true
var last_won: bool = false
var placement: int = 0 ## Endplatzierung, 0 = noch im Spiel
var level: int = Data.START_LEVEL
var xp: int = 0
var shop_locked: bool = false ## eingefrorener Shop bleibt über die Runde
var last_element: int = -1 ## zuletzt gewählte Spezialisierung für Schnellkauf
var bench: Array = [] ## Avatar oder null
var board: Dictionary = {} ## Vector2i -> Avatar
@@ -72,10 +76,18 @@ func reroll(pool: Pool, rng: RandomNumberGenerator) -> bool:
if gold < Data.REROLL_COST:
return false
gold -= Data.REROLL_COST
shop_locked = false
roll_shop(pool, rng)
return true
## Neue Runde: der Shop wird nur neu gefüllt, wenn er nicht eingefroren ist.
func new_round_shop(pool: Pool, rng: RandomNumberGenerator) -> void:
if shop_locked:
return
roll_shop(pool, rng)
func free_bench_slot() -> int:
for i in bench.size():
if bench[i] == null:
@@ -101,6 +113,8 @@ func buy(index: int, pool: Pool, chosen_element: int = -1) -> bool:
gold -= entry.price()
bench[slot] = entry.to_avatar(element)
entry.sold = true
if entry.element < 0:
last_element = element
return true
@@ -126,21 +140,42 @@ func sell_board(hex: Vector2i, pool: Pool) -> bool:
# --- Aufstellen ------------------------------------------------------------
func team_slots(round_no: int) -> int:
return Data.team_slots(round_no)
func team_slots() -> int:
return level
func can_place(round_no: int) -> bool:
return board.size() < team_slots(round_no)
func can_place() -> bool:
return board.size() < level
func gain_xp(amount: int) -> void:
xp += amount
while level < Data.MAX_LEVEL and xp >= Data.xp_needed(level):
xp -= Data.xp_needed(level)
level += 1
if level >= Data.MAX_LEVEL:
xp = 0
func can_buy_xp() -> bool:
return level < Data.MAX_LEVEL and gold >= Data.XP_BUY_COST
func buy_xp() -> bool:
if not can_buy_xp():
return false
gold -= Data.XP_BUY_COST
gain_xp(Data.XP_BUY_AMOUNT)
return true
## Setzt einen Avatar von der Bank aufs Board.
func place(bench_index: int, hex: Vector2i, round_no: int) -> bool:
func place(bench_index: int, hex: Vector2i) -> bool:
if bench_index < 0 or bench_index >= bench.size() or bench[bench_index] == null:
return false
if board.has(hex) or not _valid_hex(hex):
return false
if not can_place(round_no):
if not can_place():
return false
var a: Avatar = bench[bench_index]
board[hex] = a
@@ -175,7 +210,7 @@ func move_on_board(from_hex: Vector2i, to_hex: Vector2i) -> bool:
## Verschiebt einen Avatar zwischen Bank und Board, in beide Richtungen.
## Ist das Ziel belegt, tauschen die beiden Avatare die Plätze.
## src und dst sind {"kind": "board"/"bench", "hex": Vector2i, "index": int}.
func move_slot(src: Dictionary, dst: Dictionary, round_no: int) -> bool:
func move_slot(src: Dictionary, dst: Dictionary) -> bool:
if src.is_empty() or dst.is_empty() or src == dst:
return false
var sk: String = src["kind"]
@@ -212,7 +247,7 @@ func move_slot(src: Dictionary, dst: Dictionary, round_no: int) -> bool:
var hex3: Vector2i = dst["hex"]
if bench[i3] == null or not _valid_hex(hex3):
return false
if not board.has(hex3) and not can_place(round_no):
if not board.has(hex3) and not can_place():
return false
var unit3: Avatar = bench[i3]
if board.has(hex3):