update
This commit is contained in:
@@ -94,9 +94,9 @@ static func best_element(own: Array, other: Array) -> String:
|
||||
return best
|
||||
|
||||
|
||||
## Kosten einer Karte: zwei Drittel Angriff plus ein Drittel Leben, abgerundet, mindestens 1.
|
||||
## Kosten einer Karte: zwei Drittel Angriff plus ein Drittel Leben, abgerundet (0/2 kostet nichts).
|
||||
static func cost_of(attack: int, health: int) -> int:
|
||||
return maxi(1, (2 * attack + health) / 3)
|
||||
return (2 * attack + health) / 3
|
||||
|
||||
|
||||
## Wert eines verschmolzenen Wesens: der bessere Wert plus ein Drittel des schwächeren,
|
||||
|
||||
@@ -6,8 +6,8 @@ extends RefCounted
|
||||
const MAX_BOARD := 5
|
||||
const MAX_HAND := 5
|
||||
const START_HAND := 5 # Startkarten, alle im ersten Zug bezahlbar
|
||||
const START_MANA := 5 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
|
||||
const DRAW_COST := 1 # Ziehen: eine bezahlbare Karte vom Stapel
|
||||
const START_MANA := 1 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
|
||||
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
|
||||
@@ -131,7 +131,6 @@ class Player extends RefCounted:
|
||||
var board: Array = []
|
||||
var energy := 0 # Mana in diesem Zug; der Rest verfällt
|
||||
var max_energy := 0 # Mana zu Zugbeginn (START_MANA + eigene Züge)
|
||||
var start_bonus := 0 # einmaliges Extra-Mana für den späteren Start
|
||||
var order := 0 # Platz in der Zugreihenfolge
|
||||
var perks: Array = [] # Boss-Merkmale (siehe PERKS)
|
||||
var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile)
|
||||
@@ -161,7 +160,6 @@ class Player extends RefCounted:
|
||||
p.hand = hand.duplicate()
|
||||
p.energy = energy
|
||||
p.max_energy = max_energy
|
||||
p.start_bonus = start_bonus
|
||||
p.order = order
|
||||
p.perks = perks.duplicate()
|
||||
p.looks = looks
|
||||
@@ -277,9 +275,7 @@ func start(fixed_order: bool = false) -> void:
|
||||
for i in players.size():
|
||||
var p: Player = players[i]
|
||||
p.order = i
|
||||
# Wer nicht beginnt, bekommt im ersten Zug einmalig 1 Mana extra.
|
||||
p.start_bonus = 0 if i == 0 else 1
|
||||
for c in draw_cards(START_HAND, START_MANA + p.start_bonus):
|
||||
for c in draw_cards(START_HAND, START_MANA):
|
||||
p.hand.append(c.copy())
|
||||
turn = 1
|
||||
current_idx = 0
|
||||
@@ -349,16 +345,19 @@ func begin_turn(p: Player) -> void:
|
||||
p.energy = p.max_energy + (1 if "extra_mana" in p.perks else 0) # der Rest vom letzten Zug ist verfallen
|
||||
if p.turns == 0 and "start_energie" in p.perks:
|
||||
p.energy += 2
|
||||
if p.start_bonus > 0:
|
||||
p.energy += p.start_bonus
|
||||
say("%s erhält %d Mana extra für den späteren Start." % [p.name, p.start_bonus])
|
||||
p.start_bonus = 0
|
||||
for c in p.board:
|
||||
c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden
|
||||
c.attacks_done = 0
|
||||
p.turns += 1
|
||||
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.
|
||||
@@ -661,15 +660,15 @@ 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; der Konter kennt keinen Elementvorteil
|
||||
damage(cr, target.power())
|
||||
if target is Creature and target.power() > 0: # wer nichts austeilt, kontert auch nicht
|
||||
damage(cr, target.power() + matchup_bonus(target, cr))
|
||||
cleanup()
|
||||
check_end()
|
||||
return ""
|
||||
|
||||
|
||||
## Elementvorteil beim Zuschlagen: +1 im Vorteil, -1 im Nachteil (die Verteidigung des
|
||||
## Ziels), gegen Helden gibt es keinen.
|
||||
## Elementvorteil beim Zuschlagen und beim Konter: +1 im Vorteil, -1 im Nachteil,
|
||||
## gegen Helden gibt es keinen.
|
||||
static func matchup_bonus(source: Creature, target) -> int:
|
||||
if not (target is Creature):
|
||||
return 0
|
||||
|
||||
@@ -26,7 +26,7 @@ static func snapshot(g: Game, viewer: int) -> Dictionary:
|
||||
"max_health": 0 if masked else c.max_health, "attacks_done": c.attacks_done,
|
||||
"ready": c.ready, "hidden": c.hidden, "aura": c.aura})
|
||||
ps.append({"name": p.name, "title": p.title, "is_human": p.is_human, "life": p.life, "max_life": p.max_life,
|
||||
"energy": p.energy, "max_energy": p.max_energy, "start_bonus": p.start_bonus,
|
||||
"energy": p.energy, "max_energy": p.max_energy,
|
||||
"order": p.order, "perks": p.perks, "looks": p.looks, "turns": p.turns,
|
||||
"hand": p.hand.map(card_ref) if mine else p.hand.size(), "board": board})
|
||||
var evs := []
|
||||
@@ -50,7 +50,6 @@ static func from_snapshot(d: Dictionary) -> Game:
|
||||
p.max_life = int(pd.max_life)
|
||||
p.energy = int(pd.energy)
|
||||
p.max_energy = int(pd.max_energy)
|
||||
p.start_bonus = int(pd.start_bonus)
|
||||
p.order = int(pd.order)
|
||||
p.perks = Array(pd.perks)
|
||||
for cid in pd.get("looks", {}):
|
||||
|
||||
@@ -16,6 +16,7 @@ var game: Game
|
||||
var perspective: Game.Player # der Mensch, dessen Hand unten liegt
|
||||
var fx: BattleFx
|
||||
var selected = null # Game.Creature
|
||||
var hover_target = null # fremdes Wesen unter der Maus: Vorschau des Angriffs mit dem gewählten Wesen
|
||||
var fuse_first = null # Karte oder Wesen, für das im Menü „Verschmelzen mit …“ gewählt wurde
|
||||
var hold_time := 1.0 # so lange still über dem Ziel halten, dann verschmelzen die beiden
|
||||
var press_target = null # Karte oder Wesen unter der gedrückten linken Maustaste
|
||||
@@ -928,6 +929,23 @@ func matchup_for(cr: Game.Creature) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
## Vorschau für den Angriff auf das Wesen unter der Maus: der Angreifer zeigt seinen Angriff mit
|
||||
## Elementvorteil, und wer dabei stirbt, bekommt die Marke. Verdeckte Ziele bleiben unbekannt.
|
||||
func update_attack_preview() -> void:
|
||||
var target = hover_target
|
||||
if selected == null or target == null or target.hidden or not game.can_attack(selected, target)[0]:
|
||||
target = null
|
||||
for v in enemy_views:
|
||||
v.doomed = target != null and v.creature == target and selected.power() + Game.matchup_bonus(selected, target) >= target.health
|
||||
v.queue_redraw()
|
||||
for v in own_views:
|
||||
var mine: bool = v.creature == selected
|
||||
v.attack_shift = Game.matchup_bonus(selected, target) if mine and target != null else 0
|
||||
var counter: int = target.power() + Game.matchup_bonus(target, selected) if target != null and target.power() > 0 else 0
|
||||
v.doomed = mine and target != null and counter >= selected.health
|
||||
v.queue_redraw()
|
||||
|
||||
|
||||
class Deck extends Control:
|
||||
signal clicked
|
||||
var enabled := true
|
||||
@@ -1078,6 +1096,8 @@ func refresh() -> void:
|
||||
tile_layer.add_child(tile)
|
||||
var looks: Dictionary = p.looks
|
||||
tile.hover.connect(func(card, entered):
|
||||
hover_target = tile.creature if entered else null
|
||||
update_attack_preview()
|
||||
if tile.secret:
|
||||
show_preview(null, false)
|
||||
else:
|
||||
@@ -1123,6 +1143,7 @@ func refresh() -> void:
|
||||
var t: CreatureView = tiles[uid]
|
||||
tiles.erase(uid)
|
||||
fx.fade_out(t)
|
||||
update_attack_preview()
|
||||
deck.visible = not spectating()
|
||||
deck.enabled = mine and g.can_draw(h)[0]
|
||||
deck.count = g.deck.size()
|
||||
|
||||
@@ -161,7 +161,7 @@ func _draw_full(rect: Rect2, frame: Color, seed: int) -> void:
|
||||
fx, HORIZONTAL_ALIGNMENT_LEFT, box.size.x - 8, tsize, max_lines, Style.TEXT)
|
||||
# Werte springen aus den unteren Ecken, dazwischen der Edelstein
|
||||
Style.stat_circle(self, Vector2(r_stat - 2, size.y - r_stat + 2), r_stat, str(card.attack), Style.AMBER, f_stat)
|
||||
Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.CRIMSON, f_stat)
|
||||
Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.LIFE, f_stat)
|
||||
if bonus > 0:
|
||||
Style.text_outlined(self, "+%d/+%d" % [bonus, bonus], f_text, Style.GREEN, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3)
|
||||
Style.rarity_gem(self, rarity, Vector2(size.x / 2, size.y - r_stat + 1), 8 if big else 6, seed)
|
||||
@@ -188,7 +188,7 @@ func _draw_compact(rect: Rect2, frame: Color, seed: int) -> void:
|
||||
Style.keyword_icon(self, k, Vector2(x + 6, size.y - 13), 6)
|
||||
x += 18
|
||||
Style.stat_circle(self, Vector2(11, size.y - 11), 10, str(card.attack), Style.AMBER, 12)
|
||||
Style.stat_circle(self, Vector2(size.x - 11, size.y - 11), 10, str(card.health), Style.CRIMSON, 12)
|
||||
Style.stat_circle(self, Vector2(size.x - 11, size.y - 11), 10, str(card.health), Style.LIFE, 12)
|
||||
if bonus > 0:
|
||||
Style.text_outlined(self, "+%d" % bonus, 10, Style.GREEN, Vector2(4, size.y - 29), "midleft", 3)
|
||||
Style.rarity_sparkle(self, rect, rarity, seed)
|
||||
|
||||
@@ -11,7 +11,9 @@ var width := 2
|
||||
var flash := false # kurz rot: wurde gerade angegriffen
|
||||
var slot: Vector2 # Zielposition im Raster
|
||||
var busy := false # wird gerade animiert (kein Neupositionieren)
|
||||
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses Wesen (+1/-1)
|
||||
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses fremde Wesen (+1/-1); die Werte zeigen dann, was ihn erwartet
|
||||
var attack_shift := 0 # eigener Angreifer: Elementvorteil gegen das Ziel unter der Maus
|
||||
var doomed := false # stirbt bei dem Angriff, der gerade unter der Maus liegt
|
||||
var secret := false # verdecktes fremdes Wesen: nur der Rücken ist zu sehen
|
||||
var hold := 0.0 # Fortschritt des Verschmelzens, wenn eine Karte darüber gehalten wird (0..1)
|
||||
|
||||
@@ -66,14 +68,24 @@ func _draw() -> void:
|
||||
bcol = Style.RED
|
||||
Style.border(self, rect, bcol, 4 if flash else width, 6)
|
||||
var fs := 11 if small else 13
|
||||
var name_w: float = size.x - (30 if matchup != 0 else 6) # Platz für den Vorteilskreis oben rechts
|
||||
var name_w: float = size.x - 6
|
||||
var nf := Style.fit_size(creature.card.title(), true, fs, name_w)
|
||||
draw_rect(Rect2(2, 2, size.x - 4, Style.fs(fs) + 8), Color(0.05, 0.05, 0.08, 0.7))
|
||||
Style.text(self, Style.fit(creature.card.title(), true, Style.fs(nf), name_w), nf, Style.rarity_name_color(rar, seed), Vector2(3 + name_w / 2, 6), "midtop", true)
|
||||
# Elementbonus als ein Abzeichen rechts unter dem Namen; die Symbole rücken dafür nach links
|
||||
var badge := ""
|
||||
var bw := 0.0
|
||||
if creature.aura != 0:
|
||||
badge = "+%d/+%d" % [creature.aura, creature.aura] if creature.aura > 0 else "−%d/−%d" % [-creature.aura, -creature.aura]
|
||||
bw = Style.FONT_BOLD.get_string_size(badge, HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(10)).x + 8
|
||||
var by := Style.fs(fs) + 13
|
||||
draw_rect(Rect2(size.x - bw - 4, by, bw, 16), Color(0.05, 0.05, 0.08, 0.75))
|
||||
Style.text(self, badge, 10, Style.GREEN if creature.aura > 0 else Style.RED, Vector2(size.x - 4 - bw / 2, by + 8), "center", true)
|
||||
# Eigenschaften als Symbole: gekaufte weiß, die vom Elementpaar in der Elementfarbe
|
||||
var icon := 7.0 if small else 9.0
|
||||
var kws := creature.keywords()
|
||||
var x := size.x / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0
|
||||
var avail := size.x - (bw + 8 if badge != "" else 0.0)
|
||||
var x := avail / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0
|
||||
var y := Style.fs(fs) + 14 + icon
|
||||
for k in kws:
|
||||
var col := Color("f2f2f6")
|
||||
@@ -83,11 +95,14 @@ func _draw() -> void:
|
||||
col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45)
|
||||
Style.keyword_icon(self, k, Vector2(x + icon, y), icon, col)
|
||||
x += icon * 2 + 6
|
||||
_draw_stats(15 if small else 17, 10 if small else 12, 12.0 if small else 14.0)
|
||||
_draw_stats(15 if small else 17, 12.0 if small else 14.0)
|
||||
Style.rarity_gem(self, rar, Vector2(size.x / 2, size.y - 9), 5, seed)
|
||||
if creature.hidden:
|
||||
draw_rect(rect, Color(0.05, 0.05, 0.1, 0.55))
|
||||
Style.text_outlined(self, tr("verdeckt"), 12, Style.TEXT, Vector2(size.x / 2, size.y / 2), "center", 3)
|
||||
if doomed:
|
||||
draw_rect(Rect2(0, size.y * 0.5 - 12, size.x, 24), Color(0.05, 0.02, 0.02, 0.8))
|
||||
Style.text_outlined(self, tr("stirbt"), 14, Style.RED, Vector2(size.x / 2, size.y * 0.5), "center", 3)
|
||||
Style.rarity_sparkle(self, rect, rar, seed)
|
||||
_draw_hold()
|
||||
|
||||
@@ -110,21 +125,26 @@ func _draw_back(rect: Rect2) -> void:
|
||||
draw_style_box(Style.box(Color("3c285a"), 6, bcol, 4 if flash else maxi(width, 2)), rect)
|
||||
Style.border(self, rect.grow(-10), Color("5a3c82"), 2, 6)
|
||||
Style.text(self, "?", 36, Style.GOLD, size / 2, "center", true)
|
||||
if matchup != 0:
|
||||
pass # unbekanntes Element: kein Vorteil anzeigen
|
||||
|
||||
|
||||
## Angriff (grün, wenn der Elementbonus ihn hebt), Leben, Elementvorteil des Angreifers.
|
||||
func _draw_stats(f: int, f_small: int, r: float) -> void:
|
||||
var extra := creature.power() - creature.attack
|
||||
var atk_col := Style.GREEN if extra > 0 else (Style.RED if extra < 0 else Style.AMBER)
|
||||
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(creature.power()), atk_col, f)
|
||||
if extra != 0:
|
||||
Style.text_outlined(self, ("+%d" % extra) if extra > 0 else ("−%d" % -extra), f_small, Style.GREEN if extra > 0 else Style.RED, Vector2(r * 2 + 3, size.y - r + 1), "midleft", 3)
|
||||
var hp_col := Style.CRIMSON if creature.health < creature.max_health else Color("b84a4a")
|
||||
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(creature.health), hp_col, f)
|
||||
## Angriff und Leben für den Angriff, der gerade vorbereitet wird. Fremdes Wesen bei gewähltem
|
||||
## Angreifer: sein Konter gegen ihn (im Nachteil 1 weniger, wer 0 Angriff hat, kontert gar nicht),
|
||||
## das Leben bleibt echt. Eigener Angreifer über einem Ziel: sein Angriff mit Elementvorteil.
|
||||
func shown_stats() -> Array:
|
||||
if matchup != 0:
|
||||
var c := Vector2(size.x - 12, 12)
|
||||
draw_circle(c, 11, Color("14141c"))
|
||||
Style.text(self, ("+%d" % matchup) if matchup > 0 else ("−%d" % -matchup), f_small + 1,
|
||||
Style.GREEN if matchup > 0 else Style.RED, c, "center", true)
|
||||
var atk := 0 if creature.power() <= 0 else maxi(0, creature.power() - matchup)
|
||||
return [atk, creature.health]
|
||||
if attack_shift != 0:
|
||||
return [maxi(0, creature.power() + attack_shift), creature.health]
|
||||
return [creature.power(), creature.health]
|
||||
|
||||
|
||||
## Angriff gelb, Leben blau; die Zahl wird grün, wenn sie für den aktuellen Angriff steigt, rot, wenn sie sinkt.
|
||||
func _draw_stats(f: int, r: float) -> void:
|
||||
var shown := shown_stats()
|
||||
var atk: int = shown[0]
|
||||
var hp: int = shown[1]
|
||||
var atk_text := Style.GREEN if atk > creature.power() else (Style.RED if atk < creature.power() else Color.WHITE)
|
||||
var hp_text := Style.GREEN if hp > creature.health else (Style.RED if hp < creature.health else Color.WHITE)
|
||||
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(atk), Style.AMBER, f, atk_text)
|
||||
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(hp), Style.LIFE, f, hp_text)
|
||||
|
||||
@@ -68,9 +68,6 @@ func _draw() -> void:
|
||||
draw_circle(c, 4.5, Color(0, 0, 0, 0.35))
|
||||
draw_arc(c, 4.5, 0, TAU, 16, Color(col, 0.6), 1.0)
|
||||
px += Game.ELEMENT_STACK * 13 + 2
|
||||
if n > Game.ELEMENT_STACK:
|
||||
Style.text(self, "+%d" % (n - Game.ELEMENT_STACK), 11, Style.MUTED, Vector2(px, cy), "midleft")
|
||||
px += 18
|
||||
if paired(e):
|
||||
Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + 10, cy), 7, Style.lighter(col, 0.45))
|
||||
if strong(e):
|
||||
|
||||
@@ -19,7 +19,7 @@ const YELLOW := Color("ffe65a")
|
||||
const ORANGE := Color("ff963c")
|
||||
const PURPLE := Color("8c78dc")
|
||||
const AMBER := Color("f0b429") # Angriff
|
||||
const CRIMSON := Color("d64545") # Leben
|
||||
const LIFE := Color("4a86d8") # Leben
|
||||
const MANA := Color("3a78d2") # Kostenkristall
|
||||
const PRIMARY := Color("285a3c") # Hauptaktion
|
||||
const DANGER := Color("5a2828")
|
||||
@@ -476,11 +476,11 @@ static func cost_crystal(ci: CanvasItem, c: Vector2, r: float, cost: int, font:
|
||||
|
||||
|
||||
## Wertkreis (Angriff gelb, Leben rot) mit Zahl.
|
||||
static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int) -> void:
|
||||
static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int, text_col: Color = Color.WHITE) -> void:
|
||||
ci.draw_circle(c, r + 2, Color("14141c"))
|
||||
ci.draw_circle(c, r, color)
|
||||
ci.draw_circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.45, Color(1, 1, 1, 0.16))
|
||||
text_outlined(ci, value, font, Color.WHITE, c, "center", 3)
|
||||
text_outlined(ci, value, font, text_col, c, "center", 3)
|
||||
|
||||
|
||||
## Eigenschaft als Symbol auf dunklem Kreis: Spott Ausrufezeichen, Flink Doppelpfeil,
|
||||
|
||||
Reference in New Issue
Block a user