update
This commit is contained in:
126
scripts/game.gd
126
scripts/game.gd
@@ -10,7 +10,9 @@ const START_MANA := 1 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Re
|
||||
const DRAW_COST := 2 # Ziehen: eine bezahlbare Karte vom Stapel
|
||||
const START_LIFE := 30
|
||||
const ELEMENT_PAIR := 2 # so viele eigene offene Wesen eines Elements: Sondereffekt des Elements
|
||||
const ELEMENT_STACK := 3 # so viele offene Wesen eines Elements auf dem Tisch: +1/+1
|
||||
const ELEMENT_MANA := 10 # je so viel Mana an ausgespielten Karten eines Elements: +1 Angriff für die eigenen Wesen des Elements
|
||||
const OFFER := 3 # offene Karten auf dem Stapel, höchstens so teuer wie das Mana des Zugs; beim Ziehen wählt man eine, eine neue rückt nach
|
||||
const DISCARD_GAIN := 1 # Abwerfen einer Handkarte bringt so viel Mana
|
||||
|
||||
# Merkmale der Kampagnen-Bosse (Player.perks).
|
||||
const PERKS := {
|
||||
@@ -105,8 +107,9 @@ class Creature extends RefCounted:
|
||||
return maxi(0, attacks_per_turn() - attacks_done)
|
||||
|
||||
## Angreifen darf, wer offen liegt, noch Angriffe hat und seit einer Runde liegt (oder flink ist).
|
||||
## Darf dieses Wesen angreifen? Ob es Schaden macht, hängt vom Ziel ab (Elementvorteil, Game.can_attack).
|
||||
func can_attack() -> bool:
|
||||
return not hidden and attacks_left() > 0 and power() > 0 and (ready or has("Flink"))
|
||||
return not hidden and attacks_left() > 0 and (ready or has("Flink"))
|
||||
|
||||
func clone(new_owner) -> Creature:
|
||||
var c := Creature.new(card, new_owner, uid)
|
||||
@@ -134,6 +137,9 @@ class Player extends RefCounted:
|
||||
var perks: Array = [] # Boss-Merkmale (siehe PERKS)
|
||||
var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile)
|
||||
var turns := 0 # begonnene eigene Züge
|
||||
var played := {} # Element -> Mana der bisher ausgespielten Karten (je ELEMENT_MANA: +1 Angriff)
|
||||
var offer: Array = [] # die OFFER offenen Karten auf dem Stapel (nur der Besitzer sieht sie)
|
||||
var drew := false # in diesem Zug schon gezogen? Das erste Ziehen ist frei
|
||||
|
||||
func _init(n: String, h: bool, l: int, t: String = "") -> void:
|
||||
name = n
|
||||
@@ -141,6 +147,8 @@ class Player extends RefCounted:
|
||||
is_human = h
|
||||
life = l
|
||||
max_life = l
|
||||
for e in Cards.ELEMENTS:
|
||||
played[e] = 0
|
||||
|
||||
func alive() -> bool:
|
||||
return life > 0
|
||||
@@ -163,6 +171,9 @@ class Player extends RefCounted:
|
||||
p.perks = perks.duplicate()
|
||||
p.looks = looks
|
||||
p.turns = turns
|
||||
p.played = played.duplicate()
|
||||
p.offer = offer.duplicate()
|
||||
p.drew = drew
|
||||
for c in board:
|
||||
p.board.append(c.clone(p))
|
||||
return p
|
||||
@@ -276,6 +287,7 @@ func start(fixed_order: bool = false) -> void:
|
||||
p.order = i
|
||||
for c in draw_cards(START_HAND, START_MANA):
|
||||
p.hand.append(c.copy())
|
||||
p.offer = draw_cards(OFFER, START_MANA)
|
||||
turn = 1
|
||||
current_idx = 0
|
||||
say("Zugreihenfolge: " + ", ".join(players.map(func(p): return p.name)))
|
||||
@@ -285,9 +297,9 @@ func start(fixed_order: bool = false) -> void:
|
||||
begin_turn(current())
|
||||
|
||||
|
||||
## Nimmt n Karten vom Stapel, die höchstens max_cost kosten (was man sich in diesem Zug leisten
|
||||
## kann); zu teure wandern unter den Stapel. Ist der Stapel leer, kommt ein frischer.
|
||||
func draw_cards(n: int, max_cost: int) -> Array:
|
||||
## Nimmt n Karten vom Stapel, die höchstens max_cost kosten; zu teure wandern unter den Stapel.
|
||||
## Ist der Stapel leer, kommt ein frischer.
|
||||
func draw_cards(n: int, max_cost: int = 999) -> Array:
|
||||
var out := []
|
||||
var skipped := []
|
||||
var looked := 0
|
||||
@@ -306,7 +318,8 @@ func draw_cards(n: int, max_cost: int) -> Array:
|
||||
return out
|
||||
|
||||
|
||||
## Ziehen: kostet DRAW_COST, eine Karte kommt auf die Hand.
|
||||
## Ziehen: eine der offenen Karten kommt auf die Hand, eine neue rückt nach. Das erste Ziehen
|
||||
## im Zug ist frei, jedes weitere kostet DRAW_COST.
|
||||
func can_draw(p: Player) -> Array:
|
||||
if winner != "" or p != current():
|
||||
return [false, "Nicht dein Zug"]
|
||||
@@ -318,16 +331,20 @@ func can_draw(p: Player) -> Array:
|
||||
|
||||
|
||||
func draw_cost(p: Player) -> int:
|
||||
return 0 if "ziehen_gratis" in p.perks else DRAW_COST
|
||||
return 0 if not p.drew or "ziehen_gratis" in p.perks else DRAW_COST
|
||||
|
||||
|
||||
func draw(p: Player) -> String:
|
||||
func draw(p: Player, index: int = 0) -> String:
|
||||
var ok := can_draw(p)
|
||||
if not ok[0]:
|
||||
return ok[1]
|
||||
if index < 0 or index >= p.offer.size():
|
||||
return "Keine Karte gewählt"
|
||||
p.energy -= draw_cost(p)
|
||||
var card: Cards.Card = draw_cards(1, p.max_energy)[0]
|
||||
p.drew = true
|
||||
var card: Cards.Card = p.offer[index]
|
||||
p.hand.append(card.copy())
|
||||
p.offer[index] = draw_cards(1, maxi(p.max_energy, START_MANA))[0] # nie teurer als das Mana dieses Zugs
|
||||
say("%s zieht." % p.name)
|
||||
emit({"type": "draw", "player": players.find(p)})
|
||||
return ""
|
||||
@@ -342,15 +359,9 @@ func begin_turn(p: Player) -> void:
|
||||
c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden
|
||||
c.attacks_done = 0
|
||||
p.turns += 1
|
||||
p.drew = false # das erste Ziehen im Zug ist wieder frei
|
||||
say("--- Zug %d: %s ---" % [turn, p.name])
|
||||
emit({"type": "turn", "player": players.find(p)})
|
||||
# Jeder Zug beginnt mit einer Karte vom Stapel, die man sich in diesem Zug leisten kann.
|
||||
if p.hand.size() < MAX_HAND:
|
||||
p.hand.append(draw_cards(1, p.max_energy)[0].copy())
|
||||
say("%s zieht eine Karte." % p.name)
|
||||
emit({"type": "draw", "player": players.find(p)})
|
||||
else:
|
||||
say("%s hat die Hand voll und zieht nicht." % p.name)
|
||||
|
||||
|
||||
## Zugende: Wesen mit Regeneration heilen ganz, dann ist der andere dran.
|
||||
@@ -405,6 +416,8 @@ func play_card(p: Player, card: Cards.Card, face_down: bool = false) -> String:
|
||||
cr.health += 1
|
||||
cr.max_health += 1
|
||||
p.board.append(cr)
|
||||
for e in card.elements:
|
||||
p.played[e] = int(p.played.get(e, 0)) + card.cost
|
||||
if face_down:
|
||||
say("%s legt ein Wesen verdeckt." % p.name)
|
||||
else:
|
||||
@@ -416,6 +429,26 @@ func play_card(p: Player, card: Cards.Card, face_down: bool = false) -> String:
|
||||
return ""
|
||||
|
||||
|
||||
## Abwerfen: eine Handkarte loswerden und dafür DISCARD_GAIN Mana bekommen.
|
||||
func can_discard(p: Player, card: Cards.Card) -> Array:
|
||||
if winner != "" or p != current():
|
||||
return [false, "Nicht dein Zug"]
|
||||
if not (card in p.hand):
|
||||
return [false, "Nicht auf der Hand"]
|
||||
return [true, ""]
|
||||
|
||||
|
||||
func discard(p: Player, card: Cards.Card) -> String:
|
||||
var ok := can_discard(p, card)
|
||||
if not ok[0]:
|
||||
return ok[1]
|
||||
p.hand.erase(card)
|
||||
p.energy += DISCARD_GAIN
|
||||
say("%s wirft %s ab." % [p.name, card.name])
|
||||
emit({"type": "discard", "player": players.find(p)})
|
||||
return ""
|
||||
|
||||
|
||||
## Ein eigenes verdecktes Wesen aufdecken: kostenlos, im eigenen Zug, frühestens einen Zug
|
||||
## nach dem Legen. Aufgedeckt greift es sofort an.
|
||||
func can_reveal(p: Player, cr: Creature) -> Array:
|
||||
@@ -506,11 +539,11 @@ func fuse(p: Player, a, b) -> String:
|
||||
## 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))
|
||||
Cards.fusion_value(a.max_health, b.max_health))
|
||||
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.health = mini(card.health, Cards.fusion_value(a.health, b.health))
|
||||
cr.ready = a.ready and b.ready
|
||||
cr.attacks_done = maxi(a.attacks_done, b.attacks_done)
|
||||
var at := p.board.find(a)
|
||||
@@ -527,59 +560,38 @@ func _fuse_board(p: Player, a: Creature, b: Creature) -> String:
|
||||
|
||||
# --- Elementboni ---------------------------------------------------------------
|
||||
|
||||
## Offene Wesen je Element auf dem ganzen Tisch.
|
||||
func element_counts() -> Dictionary:
|
||||
var counts := {}
|
||||
for e in Cards.ELEMENTS:
|
||||
counts[e] = 0
|
||||
for p in players:
|
||||
for c in p.board:
|
||||
if not c.hidden:
|
||||
for e in c.card.elements:
|
||||
counts[e] += 1
|
||||
return counts
|
||||
|
||||
|
||||
## +1 je Element mit ELEMENT_STACK offenen Wesen auf dem Tisch; mehrere Elemente zählen alle.
|
||||
static func aura_of(elements: Array, counts: Dictionary) -> int:
|
||||
## +1 Angriff je volle ELEMENT_MANA an ausgespielten Karten eines Elements des Spielers; mehrere Elemente zählen alle.
|
||||
static func aura_of(elements: Array, played: Dictionary) -> int:
|
||||
var a := 0
|
||||
for e in elements:
|
||||
if int(counts[e]) >= ELEMENT_STACK:
|
||||
a += 1
|
||||
a += int(played.get(e, 0)) / ELEMENT_MANA
|
||||
return a
|
||||
|
||||
|
||||
## Bonus eines offenen Wesens; verdeckte zählen nicht und bekommen nichts.
|
||||
func aura_for(c: Creature, counts: Dictionary) -> int:
|
||||
if c.hidden:
|
||||
return 0
|
||||
var a := aura_of(c.card.elements, counts)
|
||||
## Bonus eines offenen Wesens aus den ausgespielten Karten seines Besitzers; verdeckte bekommen nichts.
|
||||
func aura_for(c: Creature) -> int:
|
||||
var o = c.owner()
|
||||
if o != null and "element_doppelt" in o.perks:
|
||||
if c.hidden or o == null:
|
||||
return 0
|
||||
var a := aura_of(c.card.elements, o.played)
|
||||
if "element_doppelt" in o.perks:
|
||||
a *= 2
|
||||
return a
|
||||
|
||||
|
||||
## Rechnet die Elementboni neu. Kommt ein Bonus, steigen Leben und Höchstleben; geht er,
|
||||
## sinkt nur das Höchstleben, das Leben wird höchstens darauf gekappt.
|
||||
## Rechnet die Elementboni neu; sie wirken nur auf den Angriff (Creature.power).
|
||||
func apply_auras() -> void:
|
||||
var counts := element_counts()
|
||||
for p in players:
|
||||
for c in p.board:
|
||||
var a := aura_for(c, counts)
|
||||
if a != c.aura:
|
||||
var delta: int = a - c.aura
|
||||
c.max_health += delta
|
||||
c.health = mini(c.health + maxi(delta, 0), c.max_health)
|
||||
c.aura = a
|
||||
c.aura = aura_for(c)
|
||||
|
||||
|
||||
## Vorschau fürs Ausspielen: der Elementbonus, den die Karte offen gelegt bekäme.
|
||||
## Vorschau fürs Ausspielen: der Elementbonus, den die Karte offen gelegt bekäme (sie zählt selbst mit).
|
||||
func forecast(p: Player, card: Cards.Card) -> int:
|
||||
var counts := element_counts()
|
||||
var played := p.played.duplicate()
|
||||
for e in card.elements:
|
||||
counts[e] += 1
|
||||
return aura_of(card.elements, counts) * (2 if "element_doppelt" in p.perks else 1)
|
||||
played[e] = int(played.get(e, 0)) + card.cost
|
||||
return aura_of(card.elements, played) * (2 if "element_doppelt" in p.perks else 1)
|
||||
|
||||
|
||||
# --- Kampf -------------------------------------------------------------------
|
||||
@@ -591,13 +603,15 @@ func can_attack(cr: Creature, target) -> Array:
|
||||
return [false, "Nicht dein Zug"]
|
||||
if cr.hidden:
|
||||
return [false, "%s liegt verdeckt" % cr.name]
|
||||
if not cr.ready and not cr.has("Flink") and cr.attacks_left() > 0 and cr.power() > 0:
|
||||
if not cr.ready and not cr.has("Flink") and cr.attacks_left() > 0:
|
||||
return [false, "%s greift erst im nächsten Zug an" % cr.name]
|
||||
if not cr.can_attack():
|
||||
return [false, "%s kann nicht angreifen" % cr.name]
|
||||
var tgt_owner = target.owner() if target is Creature else target
|
||||
if not (tgt_owner in enemies_of(cr.owner())):
|
||||
return [false, "Kein gültiges Ziel"]
|
||||
if cr.power() + matchup_bonus(cr, target) <= 0: # 0 Angriff reicht nur mit Elementvorteil
|
||||
return [false, "%s würde keinen Schaden machen" % cr.name]
|
||||
if not (target is Creature) and not tgt_owner.board.is_empty():
|
||||
return [false, "Erst die Wesen"]
|
||||
if target is Creature and not target.has("Spott") and tgt_owner.board.any(func(c): return c.has("Spott")):
|
||||
@@ -616,7 +630,7 @@ func attack(cr: Creature, target) -> String:
|
||||
say("%s greift %s an." % [cr.name, target.name])
|
||||
emit({"type": "attack", "attacker": ref_of(cr), "target": ref_of(target)})
|
||||
damage(target, cr.power() + matchup_bonus(cr, target))
|
||||
if target is Creature and target.power() > 0: # wer nichts austeilt, kontert auch nicht
|
||||
if target is Creature: # Konter mit Elementvorteil; wer so nichts austeilt, kontert nicht (damage ignoriert 0)
|
||||
damage(cr, target.power() + matchup_bonus(target, cr))
|
||||
cleanup()
|
||||
check_end()
|
||||
|
||||
Reference in New Issue
Block a user