first commit
This commit is contained in:
359
scripts/core/PlayerState.gd
Normal file
359
scripts/core/PlayerState.gd
Normal 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
|
||||
Reference in New Issue
Block a user