update
Some checks are pending
Tests / headless (push) Waiting to run
Tests / fenster-schnell (push) Waiting to run
Tests / fenster-voll (push) Waiting to run
Tests / balance (push) Waiting to run

This commit is contained in:
team3
2026-10-13 15:39:55 +02:00
parent b91f475a6e
commit f2c609a08b
25 changed files with 1338 additions and 776 deletions

View File

@@ -4,14 +4,13 @@ extends RefCounted
## Elementboni und Verschmelzen.
const MAX_BOARD := 5
const MAX_HAND := 5
const START_HAND := 5 # Startkarten, alle im ersten Zug bezahlbar
const START_HAND := 5 # Startkarten: die obersten des eigenen Stapels
const START_MANA := 1 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
const DECK_MIN := 40 # Karten je Stapel: mindestens so viele (Gegner und Startdecks genau so viele)
const DECK_MAX := 60 # und höchstens so viele
const START_LIFE := 30
const ELEMENT_PAIR := 2 # so viele eigene offene Wesen eines Elements: Sondereffekt des Elements
const ELEMENT_COUNT := 5 # je so viele ausgespielte Karten eines Elements: +1/+1 für die eigenen Wesen des Elements
const ELEMENT_COUNT := 3 # je so viele ausgespielte Karten eines Elements: +1/+1 für die eigenen Wesen des Elements
const DISCARD_GAIN := 1 # Abwerfen einer Handkarte bringt so viel Mana
# Merkmale der Kampagnen-Bosse (Player.perks).
@@ -154,23 +153,11 @@ class Player extends RefCounted:
func alive() -> bool:
return life > 0
## Eine Karte vom eigenen Stapel als Exemplar in der eigenen Stufe; null, wenn er leer ist.
## Mit max_cost kommt die oberste bezahlbare, zu teure wandern nach unten (Starthand).
func draw_from_deck(max_cost: int = 99) -> Cards.Card:
var skipped := []
var id := ""
while not deck.is_empty():
var top: String = deck.pop_back()
if Cards.by_id(top).cost <= max_cost:
id = top
break
skipped.append(top)
if id == "" and not skipped.is_empty(): # nichts Bezahlbares: dann eben die oberste
id = skipped.pop_front()
for sk in skipped:
deck.insert(0, sk)
if id == "":
## Die oberste Karte des eigenen Stapels als Exemplar in der eigenen Stufe; null, wenn er leer ist.
func draw_from_deck() -> Cards.Card:
if deck.is_empty():
return null
var id: String = deck.pop_back()
return Cards.by_id(id).leveled(int(looks.get(id, 0)))
## Offene eigene Wesen mit diesem Element.
@@ -295,8 +282,8 @@ func masked_clone(viewer: Player) -> Game:
# --- Zugablauf ---------------------------------------------------------------
## Jeder spielt mit seinem eigenen Stapel (leer: ein zufälliger); die Starthand sind fünf im
## ersten Zug bezahlbare Karten daraus.
## Jeder spielt mit seinem eigenen Stapel (leer: ein zufälliger); die Starthand sind die
## obersten fünf Karten daraus, wie gemischt.
func start(fixed_order: bool = false) -> void:
if not fixed_order:
shuffle(players, rng) # zufällige Zugreihenfolge
@@ -307,7 +294,7 @@ func start(fixed_order: bool = false) -> void:
p.deck = DeckGen.random_deck(rng)
shuffle(p.deck, rng)
for k in START_HAND:
var c := p.draw_from_deck(START_MANA)
var c := p.draw_from_deck()
if c != null:
p.hand.append(c)
turn = 1
@@ -324,8 +311,6 @@ func start(fixed_order: bool = false) -> void:
func can_draw(p: Player) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if p.hand.size() >= MAX_HAND:
return [false, "Deine Hand ist voll"]
if p.deck.is_empty():
return [false, "Dein Stapel ist leer"]
if p.energy < draw_cost(p):
@@ -497,75 +482,51 @@ static func look_of(p: Player, c: Cards.Card) -> int:
return c.look if c.look >= 0 else int(p.looks.get(c.id, 0))
## Kosten: die Hälfte der Manasumme beider Karten, aufgerundet.
static func fusion_cost(a, b) -> int:
return ceili((card_of(a).cost + card_of(b).cost) / 2.0)
## a und b: zwei eigene Wesen auf dem Brett oder zwei eigene Handkarten.
## a und b: zwei verschiedene eigene Karten, auf der Hand oder als Wesen auf dem Brett, auch
## gemischt. Verschmelzen kostet kein Mana.
func can_fuse(p: Player, a, b) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if a == null or b == null or a == b:
return [false, "Zwei verschiedene Karten wählen"]
if a is Creature and b is Creature:
if not (a in p.board) or not (b in p.board):
return [false, "Nur eigene Wesen"]
elif a is Creature or b is Creature:
return [false, "Nur Hand mit Hand oder Brett mit Brett"]
elif not (a in p.hand) or not (b in p.hand):
return [false, "Nicht auf der Hand"]
if p.energy < fusion_cost(a, b):
return [false, "Nicht genug Mana"]
for x in [a, b]:
if not ((x in p.board) if x is Creature else (x in p.hand)):
return [false, "Nur eigene Karten"]
return [true, ""]
## Verschmilzt zwei Handkarten zu einer oder zwei eigene Wesen zu einem offenen: Elemente und
## Eigenschaften beider, Werte nach Cards.fusion_value, Kosten folgen den Werten.
## Die Karte, die aus a und b entsteht (siehe Cards.fused), in der höheren Stufe der Eltern.
static func fusion_result(p: Player, a, b) -> Cards.Card:
return Cards.fused(card_of(a), card_of(b), maxi(look_of(p, card_of(a)), look_of(p, card_of(b))))
## Verschmilzt zwei eigene Karten zu einer neuen Handkarte: Wesen verlassen das Brett, die neue
## Karte ist unverletzt und wird wie jede andere ausgespielt. Sie landet am Platz der vorderen
## Handkarte, sonst hinten.
func fuse(p: Player, a, b) -> String:
var ok := can_fuse(p, a, b)
if not ok[0]:
return ok[1]
p.energy -= fusion_cost(a, b)
if a is Creature:
return _fuse_board(p, a, b)
var card := Cards.fused(a, b)
card.look = maxi(look_of(p, a), look_of(p, b))
var at := p.hand.find(a)
p.hand.erase(a)
p.hand.erase(b)
var card := fusion_result(p, a, b)
var at := p.hand.size()
var gone := []
for x in [a, b]:
if x is Creature:
p.board.erase(x)
gone.append(x.uid)
else:
at = mini(at, p.hand.find(x))
p.hand.erase(x)
p.hand.insert(mini(at, p.hand.size()), card)
say("%s und %s verschmelzen auf der Hand zu %s (%d/%d)." % [a.name, b.name, card.name, card.attack, card.health])
emit({"type": "fuse", "player": players.find(p), "uid": -1, "gone": []})
return ""
## Auf dem Brett zählen die Werte der Wesen (ohne Elementbonus, den bekommt das neue selbst);
## das Ergebnis ist nur angriffsbereit, wenn beide es waren.
func _fuse_board(p: Player, a: Creature, b: Creature) -> String:
var card := Cards.fused(a.card, b.card, Cards.fusion_value(a.attack, b.attack),
Cards.fusion_value(a.max_health - a.aura, b.max_health - b.aura))
card.look = maxi(look_of(p, a.card), look_of(p, b.card))
var cr := Creature.new(card, p, next_uid)
next_uid += 1
cr.health = mini(card.health, Cards.fusion_value(mini(a.health, a.max_health - a.aura), mini(b.health, b.max_health - b.aura)))
cr.ready = a.ready and b.ready
cr.attacks_done = maxi(a.attacks_done, b.attacks_done)
var at := p.board.find(a)
p.board.erase(a)
p.board.erase(b)
p.board.insert(mini(at, p.board.size()), cr)
say("%s und %s verschmelzen zu %s (%d/%d)." % [a.name, b.name, cr.name, cr.attack, cr.health])
emit({"type": "fuse", "player": players.find(p), "uid": cr.uid, "gone": [a.uid, b.uid]})
apply_auras()
cleanup()
check_end()
say("%s und %s verschmelzen zu %s (%d/%d) auf der Hand." % [a.name, b.name, card.name, card.attack, card.health])
emit({"type": "fuse", "player": players.find(p), "uid": -1, "gone": gone})
return ""
# --- Elementboni ---------------------------------------------------------------
## +1/+1 je volle ELEMENT_COUNT ausgespielte Karten eines Elements des Spielers; mehrere Elemente zählen alle.
## +1/+1 je volle ELEMENT_COUNT ausgespielte Karten eines Elements des Spielers; mehrere Elemente
## zählen alle, auch Neutral (eigener Zähler, aber kein Paar).
static func aura_of(elements: Array, played: Dictionary) -> int:
var a := 0
for e in elements:
@@ -648,12 +609,18 @@ func attack(cr: Creature, target) -> String:
return ""
## Elementvorteil beim Zuschlagen und beim Konter: +1 im Vorteil, -1 im Nachteil,
## gegen Helden gibt es keinen.
## Elementvorteil beim Zuschlagen und beim Konter: im Vorteil zählt der Stapelbonus des Ziels
## nicht (so viel Schaden mehr), im Nachteil der eigene nicht (so viel weniger). Gegen Helden
## gibt es keinen.
static func matchup_bonus(source: Creature, target) -> int:
if not (target is Creature):
return 0
return Cards.matchup(source.card.elements, target.card.elements)
var m := Cards.matchup(source.card.elements, target.card.elements)
if m > 0:
return target.aura
if m < 0:
return -source.aura
return 0
func damage(target, amount: int) -> void: