first commit

This commit is contained in:
team3
2026-08-29 11:42:30 +02:00
commit 9410d18ea5
47 changed files with 4382 additions and 0 deletions

359
scripts/core/PlayerState.gd Normal file
View File

@@ -0,0 +1,359 @@
class_name PlayerState
extends RefCounted
## Zustand eines Spielers oder Bots: Gold, Leben, Bank, Board, Shop.
var pid: int = 0
var pname: String = "Spieler"
var is_bot: bool = false
var gold: int = Data.START_GOLD
var hp: int = Data.START_HP
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 bench: Array = [] ## Avatar oder null
var board: Dictionary = {} ## Vector2i -> Avatar
var shop: Array = [] ## ShopEntry
## Elemente, die dieser Spieler aufgestellt hat. Nur die zeigt sein Shop.
var unlocked: Array[bool] = []
func _init(p_pid: int = 0, p_name: String = "Spieler", p_bot: bool = false) -> void:
pid = p_pid
pname = p_name
is_bot = p_bot
for i in Data.BENCH_SLOTS:
bench.append(null)
for i in Data.ELEMENT_COUNT:
unlocked.append(false)
func unlock(element: int) -> void:
if element >= 0:
unlocked[element] = true
## Anzahl der Elemente, die dieser Spieler aufgestellt hat.
func unlocked_count() -> int:
var n := 0
for e in Data.ELEMENT_COUNT:
if unlocked[e]:
n += 1
return n
## Elemente, die im eigenen Shop erscheinen können: selbst aufgestellt und
## im geteilten Vorrat noch verfügbar.
func shop_elements(pool: Pool) -> Array[int]:
var out: Array[int] = []
for e in Data.ELEMENT_COUNT:
if unlocked[e] and pool.element_left[e] > 0:
out.append(e)
return out
# --- Shop ------------------------------------------------------------------
func roll_shop(pool: Pool, rng: RandomNumberGenerator) -> void:
shop.clear()
var active := shop_elements(pool)
# Je mehr eigene Elemente aufgestellt sind, desto seltener kommt Neutral.
var neutral := Data.neutral_chance(unlocked_count())
for i in Data.SHOP_SLOTS:
var element := -1
if not active.is_empty() and rng.randf() >= neutral:
element = active[rng.randi_range(0, active.size() - 1)]
shop.append(ShopEntry.new(rng.randi_range(0, 2), element))
func reroll(pool: Pool, rng: RandomNumberGenerator) -> bool:
if gold < Data.REROLL_COST:
return false
gold -= Data.REROLL_COST
roll_shop(pool, rng)
return true
func free_bench_slot() -> int:
for i in bench.size():
if bench[i] == null:
return i
return -1
## Kauft einen Avatar. Neutrale brauchen die beim Kauf gewählte Spezialisierung.
func buy(index: int, pool: Pool, chosen_element: int = -1) -> bool:
if index < 0 or index >= shop.size():
return false
var entry: ShopEntry = shop[index]
if entry.sold or gold < entry.price():
return false
var element := entry.element if entry.element >= 0 else chosen_element
if element < 0 or element >= Data.ELEMENT_COUNT:
return false
var slot := free_bench_slot()
if slot < 0:
return false
if not pool.take(element):
return false
gold -= entry.price()
bench[slot] = entry.to_avatar(element)
entry.sold = true
return true
func sell_bench(index: int, pool: Pool) -> bool:
if index < 0 or index >= bench.size() or bench[index] == null:
return false
var a: Avatar = bench[index]
pool.return_avatar(a)
gold += maxi(1, a.invested)
bench[index] = null
return true
func sell_board(hex: Vector2i, pool: Pool) -> bool:
if not board.has(hex):
return false
var a: Avatar = board[hex]
pool.return_avatar(a)
gold += maxi(1, a.invested)
board.erase(hex)
return true
# --- Aufstellen ------------------------------------------------------------
func team_slots(round_no: int) -> int:
return Data.team_slots(round_no)
func can_place(round_no: int) -> bool:
return board.size() < team_slots(round_no)
## Setzt einen Avatar von der Bank aufs Board.
func place(bench_index: int, hex: Vector2i, round_no: int) -> 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):
return false
var a: Avatar = bench[bench_index]
board[hex] = a
bench[bench_index] = null
unlock(a.elements[0])
return true
func unplace(hex: Vector2i) -> bool:
if not board.has(hex):
return false
var slot := free_bench_slot()
if slot < 0:
return false
bench[slot] = board[hex]
board.erase(hex)
return true
func move_on_board(from_hex: Vector2i, to_hex: Vector2i) -> bool:
if not board.has(from_hex) or not _valid_hex(to_hex):
return false
var a: Avatar = board[from_hex]
if board.has(to_hex):
board[from_hex] = board[to_hex]
else:
board.erase(from_hex)
board[to_hex] = a
return true
## 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:
if src.is_empty() or dst.is_empty() or src == dst:
return false
var sk: String = src["kind"]
var dk: String = dst["kind"]
if sk == "board" and dk == "board":
return move_on_board(src["hex"], dst["hex"])
if sk == "bench" and dk == "bench":
var i: int = src["index"]
var j: int = dst["index"]
if bench[i] == null:
return false
var tmp = bench[j]
bench[j] = bench[i]
bench[i] = tmp
return true
if sk == "board" and dk == "bench":
var hex: Vector2i = src["hex"]
var j2: int = dst["index"]
if not board.has(hex):
return false
var unit: Avatar = board[hex]
if bench[j2] == null:
board.erase(hex)
else:
board[hex] = bench[j2]
bench[j2] = unit
return true
# bench -> board
var i3: int = src["index"]
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):
return false
var unit3: Avatar = bench[i3]
if board.has(hex3):
bench[i3] = board[hex3]
else:
bench[i3] = null
board[hex3] = unit3
unlock(unit3.elements[0])
return true
func _valid_hex(h: Vector2i) -> bool:
return h.x >= 0 and h.x < Data.BOARD_COLS and h.y >= 0 and h.y < Data.BOARD_ROWS
# --- Aufwerten -------------------------------------------------------------
## Wie viel Futter pro Element auf der Bank liegt.
func fodder_counts() -> Dictionary:
var counts := {}
for a in bench:
if a == null or a.is_elementless() or a.star != 1:
continue
var e: int = a.elements[0]
counts[e] = counts.get(e, 0) + 1
return counts
func can_upgrade(hex: Vector2i) -> bool:
if not board.has(hex):
return false
var a: Avatar = board[hex]
var cost := a.upgrade_cost()
if cost.is_empty():
return false
var have := fodder_counts()
for e in cost:
if have.get(e, 0) < cost[e]:
return false
return true
## Fehlende Futter-Avatare pro Element (für die Anzeige).
func upgrade_missing(hex: Vector2i) -> Dictionary:
if not board.has(hex):
return {}
var a: Avatar = board[hex]
var cost := a.upgrade_cost()
var have := fodder_counts()
var miss := {}
for e in cost:
var m: int = cost[e] - have.get(e, 0)
if m > 0:
miss[e] = m
return miss
func available_new_elements(hex: Vector2i) -> Array[int]:
var out: Array[int] = []
if not board.has(hex):
return out
var a: Avatar = board[hex]
for e in Data.ELEMENT_COUNT:
if not a.has_element(e):
out.append(e)
return out
func upgrade(hex: Vector2i, new_element: int, pool: Pool) -> bool:
if not can_upgrade(hex):
return false
var a: Avatar = board[hex]
if a.has_element(new_element) or new_element < 0 or new_element >= Data.ELEMENT_COUNT:
return false
var cost := a.upgrade_cost()
var spent := 0
for e in cost:
var need: int = cost[e]
for i in bench.size():
if need <= 0:
break
var f = bench[i]
if f != null and f.star == 1 and not f.is_elementless() and f.elements[0] == e:
spent += f.invested
bench[i] = null
need -= 1
a.invested += spent
a.star += 1
a.elements.append(new_element)
unlock(new_element)
return true
# --- Traits ----------------------------------------------------------------
## Zählt aufgestellte Einheiten pro Element.
func trait_counts() -> Dictionary:
var counts := {}
for hex in board:
var a: Avatar = board[hex]
for e in a.elements:
counts[e] = counts.get(e, 0) + 1
return counts
func active_combos() -> Array:
var counts := trait_counts()
var out := []
for c in Data.COMBO_TRAITS:
if counts.get(c["a"], 0) >= Data.COMBO_MIN and counts.get(c["b"], 0) >= Data.COMBO_MIN:
out.append(c)
return out
# --- Runde -----------------------------------------------------------------
func round_income() -> int:
var g := Data.BASE_INCOME
if last_won:
g += Data.WIN_GOLD
g += Data.streak_gold(streak)
g += Data.interest(gold)
return g
func register_result(won: bool) -> void:
last_won = won
if won:
streak = maxi(1, streak + 1)
else:
streak = mini(-1, streak - 1)
## Nur Leben abziehen. Das Ausscheiden setzt Game._check_eliminations,
## damit die Platzierung stimmt.
func take_damage(dmg: int) -> void:
hp = maxi(0, hp - dmg)
func board_units() -> Array:
var out := []
for hex in board:
out.append(board[hex])
return out