2043 lines
73 KiB
C#
2043 lines
73 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using UnityEngine.Profiling;
|
||
|
||
namespace TcgEngine.Gameplay
|
||
{
|
||
/// <summary>
|
||
/// Execute and resolves game rules and logic
|
||
/// </summary>
|
||
|
||
public class GameLogic
|
||
{
|
||
public UnityAction onGameStart;
|
||
public UnityAction<Player> onGameEnd; //Winner
|
||
|
||
public UnityAction onTurnStart;
|
||
public UnityAction onTurnPlay;
|
||
public UnityAction onTurnEnd;
|
||
|
||
public UnityAction<Card, Slot> onCardPlayed;
|
||
public UnityAction<Card, Slot> onCardSummoned;
|
||
public UnityAction<Card, Slot> onCardMoved;
|
||
public UnityAction<Card> onCardTransformed;
|
||
public UnityAction<Card> onCardDiscarded;
|
||
public UnityAction<int> onCardDrawn;
|
||
public UnityAction<int> onRollValue;
|
||
|
||
public UnityAction<AbilityData, Card> onAbilityStart;
|
||
public UnityAction<AbilityData, Card, Card> onAbilityTargetCard; //Ability, Caster, Target
|
||
public UnityAction<AbilityData, Card, Player> onAbilityTargetPlayer;
|
||
public UnityAction<AbilityData, Card, Slot> onAbilityTargetSlot;
|
||
public UnityAction<AbilityData, Card> onAbilityEnd;
|
||
|
||
public UnityAction<Card, Card> onAttackStart; //Attacker, Defender
|
||
public UnityAction<Card, Card> onAttackEnd; //Attacker, Defender
|
||
public UnityAction<Card, Player> onAttackPlayerStart;
|
||
public UnityAction<Card, Player> onAttackPlayerEnd;
|
||
|
||
public UnityAction<Card, int> onCardDamaged;
|
||
public UnityAction<Card, int> onCardHealed;
|
||
public UnityAction<Player, int> onPlayerDamaged;
|
||
public UnityAction<Player, int> onPlayerHealed;
|
||
|
||
public UnityAction<Card, Card> onSecretTrigger; //Secret, Triggerer
|
||
public UnityAction<Card, Card> onSecretResolve; //Secret, Triggerer
|
||
|
||
public UnityAction onRefresh;
|
||
|
||
private Game game_data;
|
||
|
||
private ResolveQueue resolve_queue;
|
||
private bool is_ai_predict = false;
|
||
|
||
private System.Random random = new System.Random();
|
||
|
||
private ListSwap<Card> card_array = new ListSwap<Card>();
|
||
private ListSwap<Player> player_array = new ListSwap<Player>();
|
||
private ListSwap<Slot> slot_array = new ListSwap<Slot>();
|
||
private ListSwap<CardData> card_data_array = new ListSwap<CardData>();
|
||
private List<Card> cards_to_clear = new List<Card>();
|
||
|
||
public GameLogic(bool is_ai)
|
||
{
|
||
//is_instant ignores all gameplay delays and process everything immediately, needed for AI prediction
|
||
resolve_queue = new ResolveQueue(null, is_ai);
|
||
is_ai_predict = is_ai;
|
||
}
|
||
|
||
public GameLogic(Game game)
|
||
{
|
||
game_data = game;
|
||
resolve_queue = new ResolveQueue(game, false);
|
||
}
|
||
|
||
public virtual void SetData(Game game)
|
||
{
|
||
game_data = game;
|
||
resolve_queue.SetData(game);
|
||
}
|
||
|
||
public virtual void Update(float delta)
|
||
{
|
||
resolve_queue.Update(delta);
|
||
}
|
||
|
||
//----- Turn Phases ----------
|
||
|
||
public virtual void StartGame()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
|
||
//Choose first player
|
||
game_data.state = GameState.Play;
|
||
game_data.current_player = game_data.first_player;
|
||
game_data.turn_count = 1;
|
||
|
||
//Adventure settings
|
||
bool should_mulligan = GameplayData.Get().mulligan;
|
||
LevelData level = game_data.settings.GetLevel();
|
||
if (level != null)
|
||
{
|
||
if (level != null && level.first_player == LevelFirst.Player)
|
||
game_data.first_player = 0;
|
||
if (level != null && level.first_player == LevelFirst.AI)
|
||
game_data.first_player = 1;
|
||
game_data.current_player = game_data.first_player;
|
||
should_mulligan = level.mulligan;
|
||
}
|
||
|
||
//Init each players
|
||
foreach (Player player in game_data.players)
|
||
{
|
||
//Puzzle level deck
|
||
DeckPuzzleData pdeck = DeckPuzzleData.Get(player.deck);
|
||
|
||
//Hp / mana
|
||
player.hp_max = pdeck != null ? pdeck.start_hp : GameplayData.Get().hp_start;
|
||
player.hp = player.hp_max;
|
||
player.mana_max = pdeck != null ? pdeck.start_mana : GameplayData.Get().mana_start;
|
||
player.mana = player.mana_max;
|
||
|
||
//Draw starting cards 开始抽卡
|
||
int dcards = pdeck != null ? pdeck.start_cards : GameplayData.Get().cards_start;
|
||
DrawCard(player, dcards);
|
||
|
||
//Add coin second player
|
||
bool is_random = level == null || level.first_player == LevelFirst.Random;
|
||
if (is_random && player.player_id != game_data.first_player && GameplayData.Get().second_bonus != null)
|
||
{
|
||
Card card = Card.Create(GameplayData.Get().second_bonus, VariantData.GetDefault(), player);
|
||
player.cards_hand.Add(card);
|
||
}
|
||
}
|
||
|
||
//Start state
|
||
RefreshData();
|
||
onGameStart?.Invoke();
|
||
|
||
if (should_mulligan)
|
||
{
|
||
GoToMulligan();
|
||
}
|
||
else
|
||
{
|
||
CalculateFirstPlayer();
|
||
StartTurn();
|
||
}
|
||
}
|
||
|
||
//开始回合
|
||
public virtual void StartTurn()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
|
||
ClearTurnData();
|
||
game_data.phase = GamePhase.StartTurn;
|
||
RefreshData();
|
||
onTurnStart?.Invoke();
|
||
|
||
Player player = game_data.GetActivePlayer();
|
||
|
||
//Cards draw
|
||
if (game_data.turn_count > 1 || player.player_id != game_data.first_player)
|
||
{
|
||
DrawCard(player, GameplayData.Get().cards_per_turn);
|
||
}
|
||
|
||
|
||
//Mana
|
||
player.mana_max += GameplayData.Get().mana_per_turn;
|
||
player.mana_max = Mathf.Min(player.mana_max, GameplayData.Get().mana_max);
|
||
player.mana = player.mana_max;
|
||
|
||
// 回合开始时检查mana,如果超过5则设置为5
|
||
if (player.mana > player.mana_max)
|
||
player.mana = 5;
|
||
|
||
//Turn timer and history
|
||
game_data.turn_timer = GameplayData.Get().turn_duration;
|
||
player.history_list.Clear();
|
||
|
||
//清除本回合已上场的卡牌数量
|
||
player.cards_played_this_turn = 0;
|
||
|
||
//Player poison
|
||
if (player.HasStatus(StatusType.Poisoned))
|
||
player.hp -= player.GetStatusValue(StatusType.Poisoned);
|
||
|
||
if (player.hero != null)
|
||
player.hero.Refresh();
|
||
|
||
//Refresh Cards and Status Effects
|
||
for (int i = player.cards_board.Count - 1; i >= 0; i--)
|
||
{
|
||
Card card = player.cards_board[i];
|
||
|
||
if (!card.HasStatus(StatusType.Sleep))
|
||
card.Refresh();
|
||
|
||
if (card.HasStatus(StatusType.Poisoned))
|
||
DamageCard(card, card.GetStatusValue(StatusType.Poisoned));
|
||
}
|
||
|
||
//Ongoing Abilities
|
||
UpdateOngoing();
|
||
|
||
//StartTurn Abilities
|
||
TriggerPlayerCardsAbilityType(player, AbilityTrigger.StartOfTurn);
|
||
TriggerPlayerSecrets(player, AbilityTrigger.StartOfTurn);
|
||
|
||
resolve_queue.AddCallback(StartMainPhase);
|
||
resolve_queue.ResolveAll(0.2f);
|
||
}
|
||
|
||
public virtual void StartNextTurn()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
|
||
game_data.current_player = (game_data.current_player + 1) % game_data.settings.nb_players;
|
||
|
||
if (game_data.current_player == game_data.first_player)
|
||
game_data.turn_count++;
|
||
|
||
CheckForWinner();
|
||
StartTurn();
|
||
}
|
||
|
||
public virtual void StartMainPhase()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
|
||
game_data.phase = GamePhase.Main;
|
||
onTurnPlay?.Invoke();
|
||
RefreshData();
|
||
}
|
||
|
||
public virtual void EndTurn()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
if (game_data.phase != GamePhase.Main)
|
||
return;
|
||
|
||
game_data.selector = SelectorType.None;
|
||
game_data.phase = GamePhase.EndTurn;
|
||
|
||
//Reduce status effects with duration
|
||
foreach (Player aplayer in game_data.players)
|
||
{
|
||
aplayer.ReduceStatusDurations();
|
||
foreach (Card card in aplayer.cards_board)
|
||
card.ReduceStatusDurations();
|
||
foreach (Card card in aplayer.cards_equip)
|
||
card.ReduceStatusDurations();
|
||
}
|
||
|
||
//End of turn abilities
|
||
Player player = game_data.GetActivePlayer();
|
||
TriggerPlayerCardsAbilityType(player, AbilityTrigger.EndOfTurn);
|
||
|
||
onTurnEnd?.Invoke();
|
||
RefreshData();
|
||
|
||
resolve_queue.AddCallback(StartNextTurn);
|
||
resolve_queue.ResolveAll(0.2f);
|
||
}
|
||
|
||
//End game with winner
|
||
public virtual void EndGame(int winner)
|
||
{
|
||
if (game_data.state != GameState.GameEnded)
|
||
{
|
||
game_data.state = GameState.GameEnded;
|
||
game_data.phase = GamePhase.None;
|
||
game_data.selector = SelectorType.None;
|
||
game_data.current_player = winner; //Winner player
|
||
resolve_queue.Clear();
|
||
Player player = game_data.GetPlayer(winner);
|
||
onGameEnd?.Invoke(player);
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
//Progress to the next step/phase
|
||
public virtual void NextStep()
|
||
{
|
||
if (game_data.state == GameState.GameEnded)
|
||
return;
|
||
|
||
if (game_data.phase == GamePhase.Mulligan)
|
||
{
|
||
StartTurn();
|
||
return;
|
||
}
|
||
|
||
CancelSelection();
|
||
|
||
//Add to resolve queue in case its still resolving
|
||
resolve_queue.AddCallback(EndTurn);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
|
||
//Check if a player is winning the game, if so end the game
|
||
//Change or edit this function for a new win condition
|
||
protected virtual void CheckForWinner()
|
||
{
|
||
int count_alive = 0;
|
||
Player alive = null;
|
||
foreach (Player player in game_data.players)
|
||
{
|
||
if (!player.IsDead())
|
||
{
|
||
alive = player;
|
||
count_alive++;
|
||
}
|
||
}
|
||
|
||
if (count_alive == 0)
|
||
{
|
||
EndGame(-1); //Everyone is dead, Draw
|
||
}
|
||
else if (count_alive == 1)
|
||
{
|
||
EndGame(alive.player_id); //Player win
|
||
}
|
||
}
|
||
|
||
protected virtual void ClearTurnData()
|
||
{
|
||
game_data.selector = SelectorType.None;
|
||
resolve_queue.Clear();
|
||
card_array.Clear();
|
||
player_array.Clear();
|
||
slot_array.Clear();
|
||
card_data_array.Clear();
|
||
game_data.last_played = null;
|
||
game_data.last_destroyed = null;
|
||
game_data.last_target = null;
|
||
game_data.last_summoned = null;
|
||
game_data.ability_triggerer = null;
|
||
game_data.selected_value = 0;
|
||
game_data.ability_played.Clear();
|
||
game_data.cards_attacked.Clear();
|
||
}
|
||
|
||
//--- Setup ------
|
||
|
||
//Set deck using a Deck in Resources 使用玩家卡组作为卡组
|
||
public virtual void SetPlayerDeck(Player player, DeckData deck)
|
||
{
|
||
player.cards_all.Clear();
|
||
player.cards_deck.Clear();
|
||
player.deck = deck.id;
|
||
player.hero = null;
|
||
|
||
VariantData variant = VariantData.GetDefault();
|
||
if (deck.hero != null)
|
||
{
|
||
player.hero = Card.Create(deck.hero, variant, player);
|
||
}
|
||
|
||
foreach (CardData card in deck.cards)
|
||
{
|
||
if (card != null)
|
||
{
|
||
Card acard = Card.Create(card, variant, player);
|
||
player.cards_deck.Add(acard);
|
||
}
|
||
}
|
||
|
||
DeckPuzzleData puzzle = deck as DeckPuzzleData;
|
||
|
||
//Board cards 公共牌
|
||
if (puzzle != null)
|
||
{
|
||
foreach (DeckCardSlot card in puzzle.board_cards)
|
||
{
|
||
Card acard = Card.Create(card.card, variant, player);
|
||
acard.slot = new Slot(card.slot, Slot.GetP(player.player_id));
|
||
player.cards_board.Add(acard);
|
||
}
|
||
}
|
||
|
||
//Shuffle deck
|
||
if (puzzle == null || !puzzle.dont_shuffle_deck)
|
||
ShuffleDeck(player.cards_deck);
|
||
}
|
||
|
||
//Set deck using custom deck in save file or database
|
||
public virtual void SetPlayerDeck(Player player, UserDeckData deck)
|
||
{
|
||
player.cards_all.Clear();
|
||
player.cards_deck.Clear();
|
||
player.deck = deck.tid;
|
||
player.hero = null;
|
||
|
||
if (deck.hero != null)
|
||
{
|
||
CardData hdata = CardData.Get(deck.hero.tid);
|
||
VariantData hvariant = VariantData.Get(deck.hero.variant);
|
||
if (hdata != null && hvariant != null)
|
||
player.hero = Card.Create(hdata, hvariant, player);
|
||
}
|
||
|
||
foreach (UserCardData card in deck.cards)
|
||
{
|
||
CardData icard = CardData.Get(card.tid);
|
||
VariantData variant = VariantData.Get(card.variant);
|
||
if (icard != null && variant != null)
|
||
{
|
||
for (int i = 0; i < card.quantity; i++)
|
||
{
|
||
Card acard = Card.Create(icard, variant, player);
|
||
player.cards_deck.Add(acard);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Shuffle deck
|
||
ShuffleDeck(player.cards_deck);
|
||
}
|
||
|
||
//---- Gameplay Actions --------------
|
||
|
||
public virtual void PlayCard(Card card, Slot slot, bool skip_cost = false)
|
||
{
|
||
if (game_data.CanPlayCard(card, slot, skip_cost))
|
||
{
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
|
||
//Cost - 使用mana支付
|
||
if (!skip_cost)
|
||
player.PayMana(card);
|
||
|
||
//Play card
|
||
player.RemoveCardFromAllGroups(card);
|
||
|
||
//Add to board
|
||
CardData icard = card.CardData;
|
||
if (icard.IsBoardCard())
|
||
{
|
||
player.cards_board.Add(card);
|
||
card.slot = slot;
|
||
card.exhausted = true; //Cant attack first turn
|
||
}
|
||
else if (icard.IsEquipment())
|
||
{
|
||
Card bearer = game_data.GetSlotCard(slot);
|
||
EquipCard(bearer, card);
|
||
card.exhausted = true;
|
||
}
|
||
else if (icard.IsSecret())
|
||
{
|
||
player.cards_secret.Add(card);
|
||
}
|
||
else
|
||
{
|
||
player.cards_discard.Add(card);
|
||
card.slot = slot; //Save slot in case spell has PlayTarget
|
||
}
|
||
|
||
//History
|
||
if (!is_ai_predict && !icard.IsSecret())
|
||
player.AddHistory(GameAction.PlayCard, card);
|
||
|
||
//Update ongoing effects
|
||
game_data.last_played = card.uid;
|
||
UpdateOngoing();
|
||
|
||
//Trigger abilities
|
||
if (card.CardData.IsDynamicManaCost())
|
||
{
|
||
GoToSelectorCost(card);
|
||
}
|
||
else
|
||
{
|
||
TriggerSecrets(AbilityTrigger.OnPlayOther, card); //After playing card
|
||
TriggerCardAbilityType(AbilityTrigger.OnPlay, card);
|
||
TriggerOtherCardsAbilityType(AbilityTrigger.OnPlayOther, card);
|
||
}
|
||
|
||
RefreshData();
|
||
|
||
onCardPlayed?.Invoke(card, slot);
|
||
resolve_queue.ResolveAll(0.3f);
|
||
}
|
||
}
|
||
|
||
public virtual void MoveCard(Card card, Slot slot, bool skip_cost = false)
|
||
{
|
||
if (game_data.CanMoveCard(card, slot, skip_cost))
|
||
{
|
||
card.slot = slot;
|
||
|
||
//Moving doesn't really have any effect in demo so can be done indefinitely
|
||
//if(!skip_cost)
|
||
//card.exhausted = true;
|
||
//card.RemoveStatus(StatusEffect.Stealth);
|
||
//player.AddHistory(GameAction.Move, card);
|
||
|
||
//Also move the equipment
|
||
Card equip = game_data.GetEquipCard(card.equipped_uid);
|
||
if (equip != null)
|
||
equip.slot = slot;
|
||
|
||
UpdateOngoing();
|
||
RefreshData();
|
||
|
||
onCardMoved?.Invoke(card, slot);
|
||
resolve_queue.ResolveAll(0.2f);
|
||
}
|
||
}
|
||
|
||
public virtual void CastAbility(Card card, AbilityData iability)
|
||
{
|
||
if (game_data.CanCastAbility(card, iability))
|
||
{
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
if (!is_ai_predict && iability.target != AbilityTarget.SelectTarget)
|
||
player.AddHistory(GameAction.CastAbility, card, iability);
|
||
card.RemoveStatus(StatusType.Stealth);
|
||
TriggerCardAbility(iability, card);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
|
||
public virtual void AttackTarget(Card attacker, Card target, bool skip_cost = false)
|
||
{
|
||
if (game_data.CanAttackTarget(attacker, target, skip_cost))
|
||
{
|
||
Player player = game_data.GetPlayer(attacker.player_id);
|
||
if (!is_ai_predict)
|
||
player.AddHistory(GameAction.Attack, attacker, target);
|
||
|
||
game_data.last_target = target.uid;
|
||
|
||
//Trigger before attack abilities
|
||
TriggerCardAbilityType(AbilityTrigger.OnBeforeAttack, attacker, target);
|
||
TriggerCardAbilityType(AbilityTrigger.OnBeforeDefend, target, attacker);
|
||
TriggerSecrets(AbilityTrigger.OnBeforeAttack, attacker);
|
||
TriggerSecrets(AbilityTrigger.OnBeforeDefend, target);
|
||
|
||
//Resolve attack
|
||
resolve_queue.AddAttack(attacker, target, ResolveAttack, skip_cost);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveAttack(Card attacker, Card target, bool skip_cost)
|
||
{
|
||
if (!game_data.IsOnBoard(attacker) || !game_data.IsOnBoard(target))
|
||
return;
|
||
|
||
onAttackStart?.Invoke(attacker, target);
|
||
|
||
attacker.RemoveStatus(StatusType.Stealth);
|
||
UpdateOngoing();
|
||
|
||
resolve_queue.AddAttack(attacker, target, ResolveAttackHit, skip_cost);
|
||
resolve_queue.ResolveAll(0.3f);
|
||
}
|
||
|
||
protected virtual void ResolveAttackHit(Card attacker, Card target, bool skip_cost)
|
||
{
|
||
//Count attack damage
|
||
int datt1 = attacker.GetAttack();
|
||
int datt2 = target.GetAttack();
|
||
|
||
//Damage Cards
|
||
DamageCard(attacker, target, datt1);
|
||
|
||
//Counter Damage
|
||
if (!attacker.HasStatus(StatusType.Intimidate))
|
||
DamageCard(target, attacker, datt2);
|
||
|
||
//Save attack and exhaust
|
||
if (!skip_cost)
|
||
ExhaustBattle(attacker);
|
||
|
||
//Recalculate bonus
|
||
UpdateOngoing();
|
||
|
||
//Abilities
|
||
bool att_board = game_data.IsOnBoard(attacker);
|
||
bool def_board = game_data.IsOnBoard(target);
|
||
if (att_board)
|
||
TriggerCardAbilityType(AbilityTrigger.OnAfterAttack, attacker, target);
|
||
if (def_board)
|
||
TriggerCardAbilityType(AbilityTrigger.OnAfterDefend, target, attacker);
|
||
if (att_board)
|
||
TriggerSecrets(AbilityTrigger.OnAfterAttack, attacker);
|
||
if (def_board)
|
||
TriggerSecrets(AbilityTrigger.OnAfterDefend, target);
|
||
|
||
onAttackEnd?.Invoke(attacker, target);
|
||
RefreshData();
|
||
CheckForWinner();
|
||
|
||
resolve_queue.ResolveAll(0.2f);
|
||
}
|
||
|
||
public virtual void AttackPlayer(Card attacker, Player target, bool skip_cost = false)
|
||
{
|
||
if (attacker == null || target == null)
|
||
return;
|
||
|
||
if (!game_data.CanAttackTarget(attacker, target, skip_cost))
|
||
return;
|
||
|
||
Player player = game_data.GetPlayer(attacker.player_id);
|
||
if (!is_ai_predict)
|
||
player.AddHistory(GameAction.AttackPlayer, attacker, target);
|
||
|
||
//Resolve abilities
|
||
TriggerSecrets(AbilityTrigger.OnBeforeAttack, attacker);
|
||
TriggerCardAbilityType(AbilityTrigger.OnBeforeAttack, attacker, target);
|
||
|
||
//Resolve attack
|
||
resolve_queue.AddAttack(attacker, target, ResolveAttackPlayer, skip_cost);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
|
||
protected virtual void ResolveAttackPlayer(Card attacker, Player target, bool skip_cost)
|
||
{
|
||
if (!game_data.IsOnBoard(attacker))
|
||
return;
|
||
|
||
onAttackPlayerStart?.Invoke(attacker, target);
|
||
|
||
attacker.RemoveStatus(StatusType.Stealth);
|
||
UpdateOngoing();
|
||
|
||
resolve_queue.AddAttack(attacker, target, ResolveAttackPlayerHit, skip_cost);
|
||
resolve_queue.ResolveAll(0.3f);
|
||
}
|
||
|
||
protected virtual void ResolveAttackPlayerHit(Card attacker, Player target, bool skip_cost)
|
||
{
|
||
DamagePlayer(attacker, target, attacker.GetAttack());
|
||
|
||
//Save attack and exhaust
|
||
if (!skip_cost)
|
||
ExhaustBattle(attacker);
|
||
|
||
//Recalculate bonus
|
||
UpdateOngoing();
|
||
|
||
if (game_data.IsOnBoard(attacker))
|
||
TriggerCardAbilityType(AbilityTrigger.OnAfterAttack, attacker, target);
|
||
|
||
TriggerSecrets(AbilityTrigger.OnAfterAttack, attacker);
|
||
|
||
onAttackPlayerEnd?.Invoke(attacker, target);
|
||
RefreshData();
|
||
CheckForWinner();
|
||
|
||
resolve_queue.ResolveAll(0.2f);
|
||
}
|
||
|
||
//Exhaust after battle
|
||
public virtual void ExhaustBattle(Card attacker)
|
||
{
|
||
bool attacked_before = game_data.cards_attacked.Contains(attacker.uid);
|
||
game_data.cards_attacked.Add(attacker.uid);
|
||
bool attack_again = attacker.HasStatus(StatusType.Fury) && !attacked_before;
|
||
attacker.exhausted = !attack_again;
|
||
}
|
||
|
||
//Redirect attack to a new target
|
||
public virtual void RedirectAttack(Card attacker, Card new_target)
|
||
{
|
||
foreach (AttackQueueElement att in resolve_queue.GetAttackQueue())
|
||
{
|
||
if (att.attacker.uid == attacker.uid)
|
||
{
|
||
att.target = new_target;
|
||
att.ptarget = null;
|
||
att.callback = ResolveAttack;
|
||
att.pcallback = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void RedirectAttack(Card attacker, Player new_target)
|
||
{
|
||
foreach (AttackQueueElement att in resolve_queue.GetAttackQueue())
|
||
{
|
||
if (att.attacker.uid == attacker.uid)
|
||
{
|
||
att.ptarget = new_target;
|
||
att.target = null;
|
||
att.pcallback = ResolveAttackPlayer;
|
||
att.callback = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void ShuffleDeck(List<Card> cards)
|
||
{
|
||
for (int i = 0; i < cards.Count; i++)
|
||
{
|
||
Card temp = cards[i];
|
||
int randomIndex = random.Next(i, cards.Count);
|
||
cards[i] = cards[randomIndex];
|
||
cards[randomIndex] = temp;
|
||
}
|
||
}
|
||
|
||
//Draw card 抽卡
|
||
public virtual void DrawCard(Player player, int nb = 1)
|
||
{
|
||
for (int i = 0; i < nb; i++)
|
||
{
|
||
if (player.cards_deck.Count > 0 && player.cards_hand.Count < GameplayData.Get().cards_max)
|
||
{
|
||
Card card = player.cards_deck[0];
|
||
player.cards_deck.RemoveAt(0);
|
||
player.cards_hand.Add(card);
|
||
}
|
||
}
|
||
|
||
onCardDrawn?.Invoke(nb);
|
||
}
|
||
|
||
//Put a card from deck into discard
|
||
public virtual void DrawDiscardCard(Player player, int nb = 1)
|
||
{
|
||
for (int i = 0; i < nb; i++)
|
||
{
|
||
if (player.cards_deck.Count > 0)
|
||
{
|
||
Card card = player.cards_deck[0];
|
||
player.cards_deck.RemoveAt(0);
|
||
player.cards_discard.Add(card);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Summon copy of an exiting card
|
||
public virtual Card SummonCopy(Player player, Card copy, Slot slot)
|
||
{
|
||
CardData icard = copy.CardData;
|
||
return SummonCard(player, icard, copy.VariantData, slot);
|
||
}
|
||
|
||
//Summon copy of an exiting card into hand
|
||
public virtual Card SummonCopyHand(Player player, Card copy)
|
||
{
|
||
CardData icard = copy.CardData;
|
||
return SummonCardHand(player, icard, copy.VariantData);
|
||
}
|
||
|
||
//Create a new card and send it to the board
|
||
public virtual Card SummonCard(Player player, CardData card, VariantData variant, Slot slot)
|
||
{
|
||
if (!slot.IsValid())
|
||
return null;
|
||
|
||
if (game_data.GetSlotCard(slot) != null)
|
||
return null;
|
||
|
||
Card acard = SummonCardHand(player, card, variant);
|
||
PlayCard(acard, slot, true);
|
||
|
||
onCardSummoned?.Invoke(acard, slot);
|
||
|
||
return acard;
|
||
}
|
||
|
||
//Create a new card and send it to your hand
|
||
public virtual Card SummonCardHand(Player player, CardData card, VariantData variant)
|
||
{
|
||
Card acard = Card.Create(card, variant, player);
|
||
player.cards_hand.Add(acard);
|
||
game_data.last_summoned = acard.uid;
|
||
return acard;
|
||
}
|
||
|
||
//Transform card into another one
|
||
public virtual Card TransformCard(Card card, CardData transform_to)
|
||
{
|
||
card.SetCard(transform_to, card.VariantData);
|
||
|
||
onCardTransformed?.Invoke(card);
|
||
|
||
return card;
|
||
}
|
||
|
||
public virtual void EquipCard(Card card, Card equipment)
|
||
{
|
||
if (card != null && equipment != null && card.player_id == equipment.player_id)
|
||
{
|
||
if (!card.CardData.IsEquipment() && equipment.CardData.IsEquipment())
|
||
{
|
||
UnequipAll(card); //Unequip previous cards, only 1 equip at a time
|
||
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
player.RemoveCardFromAllGroups(equipment);
|
||
player.cards_equip.Add(equipment);
|
||
card.equipped_uid = equipment.uid;
|
||
equipment.slot = card.slot;
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void UnequipAll(Card card)
|
||
{
|
||
if (card != null && card.equipped_uid != null)
|
||
{
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
Card equip = player.GetEquipCard(card.equipped_uid);
|
||
if (equip != null)
|
||
{
|
||
card.equipped_uid = null;
|
||
DiscardCard(equip);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Change owner of a card
|
||
public virtual void ChangeOwner(Card card, Player owner)
|
||
{
|
||
if (card.player_id != owner.player_id)
|
||
{
|
||
Player powner = game_data.GetPlayer(card.player_id);
|
||
powner.RemoveCardFromAllGroups(card);
|
||
powner.cards_all.Remove(card.uid);
|
||
owner.cards_all[card.uid] = card;
|
||
card.player_id = owner.player_id;
|
||
}
|
||
}
|
||
|
||
//Damage a player
|
||
public virtual void DamagePlayer(Card attacker, Player target, int value)
|
||
{
|
||
//Damage player
|
||
target.hp -= value;
|
||
target.hp = Mathf.Clamp(target.hp, 0, target.hp_max);
|
||
|
||
//Lifesteal
|
||
Player aplayer = game_data.GetPlayer(attacker.player_id);
|
||
if (attacker.HasStatus(StatusType.LifeSteal))
|
||
aplayer.hp += value;
|
||
|
||
onPlayerDamaged?.Invoke(target, value);
|
||
}
|
||
|
||
//Heal a card
|
||
public virtual void HealCard(Card target, int value)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
if (target.HasStatus(StatusType.Invincibility))
|
||
return;
|
||
|
||
target.damage -= value;
|
||
target.damage = Mathf.Max(target.damage, 0);
|
||
|
||
onCardHealed?.Invoke(target, value);
|
||
}
|
||
|
||
public virtual void HealPlayer(Player target, int value)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
target.hp += value;
|
||
target.hp = Mathf.Clamp(target.hp, 0, target.hp_max);
|
||
|
||
onPlayerHealed?.Invoke(target, value);
|
||
}
|
||
|
||
//Generic damage that doesnt come from another card
|
||
public virtual void DamageCard(Card target, int value)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
if (target.HasStatus(StatusType.Invincibility))
|
||
return; //Invincible
|
||
|
||
if (target.HasStatus(StatusType.SpellImmunity))
|
||
return; //Spell immunity
|
||
|
||
target.damage += value;
|
||
|
||
onCardDamaged?.Invoke(target, value);
|
||
|
||
if (target.GetHP() <= 0)
|
||
DiscardCard(target);
|
||
}
|
||
|
||
//Damage a card with attacker/caster
|
||
public virtual void DamageCard(Card attacker, Card target, int value, bool spell_damage = false)
|
||
{
|
||
if (attacker == null || target == null)
|
||
return;
|
||
|
||
if (target.HasStatus(StatusType.Invincibility))
|
||
return; //Invincible
|
||
|
||
if (target.HasStatus(StatusType.SpellImmunity) && attacker.CardData.type != CardType.Character)
|
||
return; //Spell immunity
|
||
|
||
//Shell
|
||
bool doublelife = target.HasStatus(StatusType.Shell);
|
||
if (doublelife && value > 0)
|
||
{
|
||
target.RemoveStatus(StatusType.Shell);
|
||
return;
|
||
}
|
||
|
||
//Armor
|
||
if (!spell_damage && target.HasStatus(StatusType.Armor))
|
||
value = Mathf.Max(value - target.GetStatusValue(StatusType.Armor), 0);
|
||
|
||
//Damage
|
||
int damage_max = Mathf.Min(value, target.GetHP());
|
||
int extra = value - target.GetHP();
|
||
target.damage += value;
|
||
|
||
//Trample
|
||
Player tplayer = game_data.GetPlayer(target.player_id);
|
||
if (!spell_damage && extra > 0 && attacker.player_id == game_data.current_player && attacker.HasStatus(StatusType.Trample))
|
||
tplayer.hp -= extra;
|
||
|
||
//Lifesteal
|
||
Player player = game_data.GetPlayer(attacker.player_id);
|
||
if (!spell_damage && attacker.HasStatus(StatusType.LifeSteal))
|
||
player.hp += damage_max;
|
||
|
||
//Remove sleep on damage
|
||
target.RemoveStatus(StatusType.Sleep);
|
||
|
||
//Callback
|
||
onCardDamaged?.Invoke(target, value);
|
||
|
||
//Deathtouch
|
||
if (value > 0 && attacker.HasStatus(StatusType.Deathtouch) && target.CardData.type == CardType.Character)
|
||
KillCard(attacker, target);
|
||
|
||
//Kill card if no hp
|
||
if (target.GetHP() <= 0)
|
||
KillCard(attacker, target);
|
||
}
|
||
|
||
//A card that kills another card
|
||
public virtual void KillCard(Card attacker, Card target)
|
||
{
|
||
if (attacker == null || target == null)
|
||
return;
|
||
|
||
if (!game_data.IsOnBoard(target) && !game_data.IsEquipped(target))
|
||
return; //Already killed
|
||
|
||
if (target.HasStatus(StatusType.Invincibility))
|
||
return; //Cant be killed
|
||
|
||
Player pattacker = game_data.GetPlayer(attacker.player_id);
|
||
if (attacker.player_id != target.player_id)
|
||
pattacker.kill_count++;
|
||
|
||
DiscardCard(target);
|
||
|
||
TriggerCardAbilityType(AbilityTrigger.OnKill, attacker, target);
|
||
}
|
||
|
||
//Send card into discard
|
||
public virtual void DiscardCard(Card card)
|
||
{
|
||
if (card == null)
|
||
return;
|
||
|
||
if (game_data.IsInDiscard(card))
|
||
return; //Already discarded
|
||
|
||
CardData icard = card.CardData;
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
bool was_on_board = game_data.IsOnBoard(card) || game_data.IsEquipped(card);
|
||
|
||
//Unequip card
|
||
UnequipAll(card);
|
||
|
||
//Remove card from board and add to discard
|
||
player.RemoveCardFromAllGroups(card);
|
||
player.cards_discard.Add(card);
|
||
game_data.last_destroyed = card.uid;
|
||
|
||
//Remove from bearer
|
||
Card bearer = player.GetBearerCard(card);
|
||
if (bearer != null)
|
||
bearer.equipped_uid = null;
|
||
|
||
if (was_on_board)
|
||
{
|
||
//Trigger on death abilities
|
||
TriggerCardAbilityType(AbilityTrigger.OnDeath, card);
|
||
TriggerOtherCardsAbilityType(AbilityTrigger.OnDeathOther, card);
|
||
TriggerSecrets(AbilityTrigger.OnDeathOther, card);
|
||
UpdateOngoingCards(); //Not UpdateOngoing() here to avoid recursive calls in UpdateOngoingKills
|
||
}
|
||
|
||
cards_to_clear.Add(card); //Will be Clear() in the next UpdateOngoing, so that simultaneous damage effects work
|
||
onCardDiscarded?.Invoke(card);
|
||
|
||
// 检查场上是否为空,如果为空则强制召唤角色牌
|
||
if (was_on_board)
|
||
{
|
||
CheckAndForceSummonCharacter(player);
|
||
}
|
||
}
|
||
|
||
// 检查并强制召唤角色牌
|
||
public virtual void CheckAndForceSummonCharacter(Player player)
|
||
{
|
||
// 检查场上是否为空
|
||
if (player.cards_board.Count > 0)
|
||
return; // 场上还有牌,不需要强制召唤
|
||
|
||
// 检查手牌中是否有角色牌
|
||
Card characterCard = GetRandomCharacterFromHand(player);
|
||
if (characterCard != null)
|
||
{
|
||
// 从手牌强制上场角色牌
|
||
ForceSummonCharacterFromHand(player, characterCard);
|
||
}
|
||
else if (player.cards_hand.Count == 0 && player.cards_deck.Count > 0)
|
||
{
|
||
// 没有手牌但卡组还有牌,判为输
|
||
EndGame(GetOpponentId(player.player_id));
|
||
}
|
||
}
|
||
|
||
// 从手牌中获取随机角色牌
|
||
private Card GetRandomCharacterFromHand(Player player)
|
||
{
|
||
List<Card> characterCards = new List<Card>();
|
||
foreach (Card card in player.cards_hand)
|
||
{
|
||
if (card.CardData.IsCharacter())
|
||
{
|
||
characterCards.Add(card);
|
||
}
|
||
}
|
||
|
||
if (characterCards.Count > 0)
|
||
{
|
||
return characterCards[random.Next(0, characterCards.Count)];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// 强制从手牌召唤角色牌,设置为疲劳状态,不消耗mana
|
||
private void ForceSummonCharacterFromHand(Player player, Card characterCard)
|
||
{
|
||
// 找到一个空的卡槽
|
||
Slot emptySlot = player.GetRandomEmptySlot(random);
|
||
if (emptySlot.IsValid())
|
||
{
|
||
// 不消耗mana强制上场
|
||
PlayCard(characterCard, emptySlot, true); // skip_cost = true
|
||
|
||
// 设置为疲劳状态
|
||
characterCard.exhausted = true;
|
||
}
|
||
}
|
||
|
||
// 获取对手ID
|
||
private int GetOpponentId(int playerId)
|
||
{
|
||
foreach (Player player in game_data.players)
|
||
{
|
||
if (player.player_id != playerId)
|
||
{
|
||
return player.player_id;
|
||
}
|
||
}
|
||
return -1; // 找不到对手时返回-1
|
||
}
|
||
|
||
public int RollRandomValue(int dice)
|
||
{
|
||
return RollRandomValue(1, dice + 1);
|
||
}
|
||
|
||
public virtual int RollRandomValue(int min, int max)
|
||
{
|
||
game_data.rolled_value = random.Next(min, max);
|
||
onRollValue?.Invoke(game_data.rolled_value);
|
||
resolve_queue.SetDelay(1f);
|
||
return game_data.rolled_value;
|
||
}
|
||
|
||
//--- Abilities --
|
||
|
||
public virtual void TriggerCardAbilityType(AbilityTrigger type, Card caster, Card triggerer = null)
|
||
{
|
||
foreach (AbilityData iability in caster.GetAbilities())
|
||
{
|
||
if (iability && iability.trigger == type)
|
||
{
|
||
TriggerCardAbility(iability, caster, triggerer);
|
||
}
|
||
}
|
||
|
||
Card equipped = game_data.GetEquipCard(caster.equipped_uid);
|
||
if (equipped != null)
|
||
TriggerCardAbilityType(type, equipped, triggerer);
|
||
}
|
||
|
||
public virtual void TriggerCardAbilityType(AbilityTrigger type, Card caster, Player triggerer)
|
||
{
|
||
foreach (AbilityData iability in caster.GetAbilities())
|
||
{
|
||
if (iability && iability.trigger == type)
|
||
{
|
||
TriggerCardAbility(iability, caster, triggerer);
|
||
}
|
||
}
|
||
|
||
Card equipped = game_data.GetEquipCard(caster.equipped_uid);
|
||
if (equipped != null)
|
||
TriggerCardAbilityType(type, equipped, triggerer);
|
||
}
|
||
|
||
public virtual void TriggerOtherCardsAbilityType(AbilityTrigger type, Card triggerer)
|
||
{
|
||
foreach (Player oplayer in game_data.players)
|
||
{
|
||
if (oplayer.hero != null)
|
||
TriggerCardAbilityType(type, oplayer.hero, triggerer);
|
||
|
||
foreach (Card card in oplayer.cards_board)
|
||
TriggerCardAbilityType(type, card, triggerer);
|
||
}
|
||
}
|
||
|
||
public virtual void TriggerPlayerCardsAbilityType(Player player, AbilityTrigger type)
|
||
{
|
||
if (player.hero != null)
|
||
TriggerCardAbilityType(type, player.hero, player.hero);
|
||
|
||
foreach (Card card in player.cards_board)
|
||
TriggerCardAbilityType(type, card, card);
|
||
}
|
||
|
||
public virtual void TriggerCardAbility(AbilityData iability, Card caster)
|
||
{
|
||
TriggerCardAbility(iability, caster, caster);
|
||
}
|
||
|
||
public virtual void TriggerCardAbility(AbilityData iability, Card caster, Card triggerer)
|
||
{
|
||
Card trigger_card = triggerer != null ? triggerer : caster; //Triggerer is the caster if not set
|
||
if (!caster.HasStatus(StatusType.Silenced) && iability.AreTriggerConditionsMet(game_data, caster, trigger_card))
|
||
{
|
||
resolve_queue.AddAbility(iability, caster, trigger_card, ResolveCardAbility);
|
||
}
|
||
}
|
||
|
||
public virtual void TriggerCardAbility(AbilityData iability, Card caster, Player triggerer)
|
||
{
|
||
if (!caster.HasStatus(StatusType.Silenced) && iability.AreTriggerConditionsMet(game_data, caster, triggerer))
|
||
{
|
||
resolve_queue.AddAbility(iability, caster, caster, ResolveCardAbility);
|
||
}
|
||
}
|
||
|
||
public virtual void TriggerAbilityDelayed(AbilityData iability, Card caster)
|
||
{
|
||
resolve_queue.AddAbility(iability, caster, caster, TriggerCardAbility);
|
||
}
|
||
|
||
public virtual void TriggerAbilityDelayed(AbilityData iability, Card caster, Card triggerer)
|
||
{
|
||
Card trigger_card = triggerer != null ? triggerer : caster; //Triggerer is the caster if not set
|
||
resolve_queue.AddAbility(iability, caster, trigger_card, TriggerCardAbility);
|
||
}
|
||
|
||
//Resolve a card ability, may stop to ask for target
|
||
protected virtual void ResolveCardAbility(AbilityData iability, Card caster, Card triggerer)
|
||
{
|
||
if (!caster.CanDoAbilities())
|
||
return; //Silenced card cant cast
|
||
|
||
//Debug.Log("Trigger Ability " + iability.id + " : " + caster.card_id);
|
||
|
||
onAbilityStart?.Invoke(iability, caster);
|
||
game_data.ability_triggerer = triggerer.uid;
|
||
game_data.ability_played.Add(iability.id);
|
||
|
||
bool is_selector = ResolveCardAbilitySelector(iability, caster);
|
||
if (is_selector)
|
||
return; //Wait for player to select
|
||
|
||
ResolveCardAbilityPlayTarget(iability, caster);
|
||
ResolveCardAbilityPlayers(iability, caster);
|
||
ResolveCardAbilityCards(iability, caster);
|
||
ResolveCardAbilitySlots(iability, caster);
|
||
ResolveCardAbilityCardData(iability, caster);
|
||
ResolveCardAbilityNoTarget(iability, caster);
|
||
AfterAbilityResolved(iability, caster);
|
||
}
|
||
|
||
protected virtual bool ResolveCardAbilitySelector(AbilityData iability, Card caster)
|
||
{
|
||
if (iability.target == AbilityTarget.SelectTarget)
|
||
{
|
||
//Wait for target
|
||
GoToSelectTarget(iability, caster);
|
||
return true;
|
||
}
|
||
else if (iability.target == AbilityTarget.CardSelector)
|
||
{
|
||
GoToSelectorCard(iability, caster);
|
||
return true;
|
||
}
|
||
else if (iability.target == AbilityTarget.ChoiceSelector)
|
||
{
|
||
GoToSelectorChoice(iability, caster);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilityPlayTarget(AbilityData iability, Card caster)
|
||
{
|
||
if (iability.target == AbilityTarget.PlayTarget)
|
||
{
|
||
Slot slot = caster.slot;
|
||
Card slot_card = game_data.GetSlotCard(slot);
|
||
if (slot.IsPlayerSlot())
|
||
{
|
||
Player tplayer = game_data.GetPlayer(slot.p);
|
||
if (iability.CanTarget(game_data, caster, tplayer))
|
||
ResolveEffectTarget(iability, caster, tplayer);
|
||
}
|
||
else if (slot_card != null)
|
||
{
|
||
if (iability.CanTarget(game_data, caster, slot_card))
|
||
{
|
||
game_data.last_target = slot_card.uid;
|
||
ResolveEffectTarget(iability, caster, slot_card);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (iability.CanTarget(game_data, caster, slot))
|
||
ResolveEffectTarget(iability, caster, slot);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilityPlayers(AbilityData iability, Card caster)
|
||
{
|
||
//Get Player Targets based on conditions
|
||
List<Player> targets = iability.GetPlayerTargets(game_data, caster, player_array);
|
||
|
||
//Resolve effects
|
||
foreach (Player target in targets)
|
||
{
|
||
ResolveEffectTarget(iability, caster, target);
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilityCards(AbilityData iability, Card caster)
|
||
{
|
||
//Get Cards Targets based on conditions
|
||
List<Card> targets = iability.GetCardTargets(game_data, caster, card_array);
|
||
|
||
//Resolve effects
|
||
foreach (Card target in targets)
|
||
{
|
||
ResolveEffectTarget(iability, caster, target);
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilitySlots(AbilityData iability, Card caster)
|
||
{
|
||
//Get Slot Targets based on conditions
|
||
List<Slot> targets = iability.GetSlotTargets(game_data, caster, slot_array);
|
||
|
||
//Resolve effects
|
||
foreach (Slot target in targets)
|
||
{
|
||
ResolveEffectTarget(iability, caster, target);
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilityCardData(AbilityData iability, Card caster)
|
||
{
|
||
//Get Cards Targets based on conditions
|
||
List<CardData> targets = iability.GetCardDataTargets(game_data, caster, card_data_array);
|
||
|
||
//Resolve effects
|
||
foreach (CardData target in targets)
|
||
{
|
||
ResolveEffectTarget(iability, caster, target);
|
||
}
|
||
}
|
||
|
||
protected virtual void ResolveCardAbilityNoTarget(AbilityData iability, Card caster)
|
||
{
|
||
if (iability.target == AbilityTarget.None)
|
||
iability.DoEffects(this, caster);
|
||
}
|
||
|
||
protected virtual void ResolveEffectTarget(AbilityData iability, Card caster, Player target)
|
||
{
|
||
iability.DoEffects(this, caster, target);
|
||
|
||
onAbilityTargetPlayer?.Invoke(iability, caster, target);
|
||
}
|
||
|
||
protected virtual void ResolveEffectTarget(AbilityData iability, Card caster, Card target)
|
||
{
|
||
iability.DoEffects(this, caster, target);
|
||
|
||
onAbilityTargetCard?.Invoke(iability, caster, target);
|
||
}
|
||
|
||
protected virtual void ResolveEffectTarget(AbilityData iability, Card caster, Slot target)
|
||
{
|
||
iability.DoEffects(this, caster, target);
|
||
|
||
onAbilityTargetSlot?.Invoke(iability, caster, target);
|
||
}
|
||
|
||
protected virtual void ResolveEffectTarget(AbilityData iability, Card caster, CardData target)
|
||
{
|
||
iability.DoEffects(this, caster, target);
|
||
}
|
||
|
||
protected virtual void AfterAbilityResolved(AbilityData iability, Card caster)
|
||
{
|
||
Player player = game_data.GetPlayer(caster.player_id);
|
||
|
||
//Pay cost
|
||
if (iability.trigger == AbilityTrigger.Activate || iability.trigger == AbilityTrigger.None)
|
||
{
|
||
player.mana -= iability.mana_cost;
|
||
caster.exhausted = caster.exhausted || iability.exhaust;
|
||
}
|
||
|
||
//Recalculate and clear
|
||
UpdateOngoing();
|
||
CheckForWinner();
|
||
|
||
//Chain ability
|
||
if (iability.target != AbilityTarget.ChoiceSelector && game_data.state != GameState.GameEnded)
|
||
{
|
||
foreach (AbilityData chain_ability in iability.chain_abilities)
|
||
{
|
||
if (chain_ability != null)
|
||
{
|
||
TriggerCardAbility(chain_ability, caster);
|
||
}
|
||
}
|
||
}
|
||
|
||
onAbilityEnd?.Invoke(iability, caster);
|
||
resolve_queue.ResolveAll(0.5f);
|
||
RefreshData();
|
||
}
|
||
|
||
//This function is called often to update status/stats affected by ongoing abilities
|
||
//It basically first reset the bonus to 0 (CleanOngoing) and then recalculate it to make sure it it still present
|
||
//Only cards in hand and on board are updated in this way
|
||
public virtual void UpdateOngoing()
|
||
{
|
||
Profiler.BeginSample("Update Ongoing");
|
||
UpdateOngoingCards(); //Update status and stats
|
||
UpdateOngoingKills(); //Kill cards with 0 HP
|
||
Profiler.EndSample();
|
||
}
|
||
|
||
protected virtual void UpdateOngoingCards()
|
||
{
|
||
for (int p = 0; p < game_data.players.Length; p++)
|
||
{
|
||
Player player = game_data.players[p];
|
||
player.ClearOngoing();
|
||
|
||
for (int c = 0; c < player.cards_board.Count; c++)
|
||
player.cards_board[c].ClearOngoing();
|
||
|
||
for (int c = 0; c < player.cards_equip.Count; c++)
|
||
player.cards_equip[c].ClearOngoing();
|
||
|
||
for (int c = 0; c < player.cards_hand.Count; c++)
|
||
player.cards_hand[c].ClearOngoing();
|
||
}
|
||
|
||
for (int p = 0; p < game_data.players.Length; p++)
|
||
{
|
||
Player player = game_data.players[p];
|
||
UpdateOngoingAbilities(player, player.hero); //Remove this line if hero is on the board
|
||
|
||
for (int c = 0; c < player.cards_board.Count; c++)
|
||
{
|
||
Card card = player.cards_board[c];
|
||
UpdateOngoingAbilities(player, card);
|
||
}
|
||
|
||
for (int c = 0; c < player.cards_equip.Count; c++)
|
||
{
|
||
Card card = player.cards_equip[c];
|
||
UpdateOngoingAbilities(player, card);
|
||
}
|
||
}
|
||
|
||
//Stats bonus
|
||
for (int p = 0; p < game_data.players.Length; p++)
|
||
{
|
||
Player player = game_data.players[p];
|
||
for (int c = 0; c < player.cards_board.Count; c++)
|
||
{
|
||
Card card = player.cards_board[c];
|
||
|
||
//Taunt effect
|
||
if (card.HasStatus(StatusType.Protection) && !card.HasStatus(StatusType.Stealth))
|
||
{
|
||
player.AddOngoingStatus(StatusType.Protected, 0);
|
||
|
||
for (int tc = 0; tc < player.cards_board.Count; tc++)
|
||
{
|
||
Card tcard = player.cards_board[tc];
|
||
if (!tcard.HasStatus(StatusType.Protection) && !tcard.HasStatus(StatusType.Protected))
|
||
{
|
||
tcard.AddOngoingStatus(StatusType.Protected, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Status bonus
|
||
foreach (CardStatus status in card.status)
|
||
AddOngoingStatusBonus(card, status);
|
||
foreach (CardStatus status in card.ongoing_status)
|
||
AddOngoingStatusBonus(card, status);
|
||
}
|
||
|
||
for (int c = 0; c < player.cards_hand.Count; c++)
|
||
{
|
||
Card card = player.cards_hand[c];
|
||
//Status bonus
|
||
foreach (CardStatus status in card.status)
|
||
AddOngoingStatusBonus(card, status);
|
||
foreach (CardStatus status in card.ongoing_status)
|
||
AddOngoingStatusBonus(card, status);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected virtual void UpdateOngoingKills()
|
||
{
|
||
//Kill stuff with 0 hp
|
||
for (int p = 0; p < game_data.players.Length; p++)
|
||
{
|
||
Player player = game_data.players[p];
|
||
for (int i = player.cards_board.Count - 1; i >= 0; i--)
|
||
{
|
||
if (i < player.cards_board.Count)
|
||
{
|
||
Card card = player.cards_board[i];
|
||
if (card.GetHP() <= 0)
|
||
DiscardCard(card);
|
||
}
|
||
}
|
||
for (int i = player.cards_equip.Count - 1; i >= 0; i--)
|
||
{
|
||
if (i < player.cards_equip.Count)
|
||
{
|
||
Card card = player.cards_equip[i];
|
||
if (card.GetHP() <= 0)
|
||
DiscardCard(card);
|
||
Card bearer = player.GetBearerCard(card);
|
||
if (bearer == null)
|
||
DiscardCard(card);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Clear cards
|
||
for (int c = 0; c < cards_to_clear.Count; c++)
|
||
cards_to_clear[c].Clear();
|
||
cards_to_clear.Clear();
|
||
}
|
||
|
||
protected virtual void UpdateOngoingAbilities(Player player, Card card)
|
||
{
|
||
if (card == null || !card.CanDoAbilities())
|
||
return;
|
||
|
||
List<AbilityData> cabilities = card.GetAbilities();
|
||
for (int a = 0; a < cabilities.Count; a++)
|
||
{
|
||
AbilityData ability = cabilities[a];
|
||
if (ability != null && ability.trigger == AbilityTrigger.Ongoing && ability.AreTriggerConditionsMet(game_data, card))
|
||
{
|
||
if (ability.target == AbilityTarget.Self)
|
||
{
|
||
if (ability.AreTargetConditionsMet(game_data, card, card))
|
||
{
|
||
ability.DoOngoingEffects(this, card, card);
|
||
}
|
||
}
|
||
|
||
if (ability.target == AbilityTarget.PlayerSelf)
|
||
{
|
||
if (ability.AreTargetConditionsMet(game_data, card, player))
|
||
{
|
||
ability.DoOngoingEffects(this, card, player);
|
||
}
|
||
}
|
||
|
||
if (ability.target == AbilityTarget.AllPlayers || ability.target == AbilityTarget.PlayerOpponent)
|
||
{
|
||
for (int tp = 0; tp < game_data.players.Length; tp++)
|
||
{
|
||
if (ability.target == AbilityTarget.AllPlayers || tp != player.player_id)
|
||
{
|
||
Player oplayer = game_data.players[tp];
|
||
if (ability.AreTargetConditionsMet(game_data, card, oplayer))
|
||
{
|
||
ability.DoOngoingEffects(this, card, oplayer);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (ability.target == AbilityTarget.EquippedCard)
|
||
{
|
||
if (card.CardData.IsEquipment())
|
||
{
|
||
//Get bearer of the equipment
|
||
Card target = player.GetBearerCard(card);
|
||
if (target != null && ability.AreTargetConditionsMet(game_data, card, target))
|
||
{
|
||
ability.DoOngoingEffects(this, card, target);
|
||
}
|
||
}
|
||
else if (card.equipped_uid != null)
|
||
{
|
||
//Get equipped card
|
||
Card target = game_data.GetCard(card.equipped_uid);
|
||
if (target != null && ability.AreTargetConditionsMet(game_data, card, target))
|
||
{
|
||
ability.DoOngoingEffects(this, card, target);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (ability.target == AbilityTarget.AllCardsAllPiles || ability.target == AbilityTarget.AllCardsHand || ability.target == AbilityTarget.AllCardsBoard)
|
||
{
|
||
for (int tp = 0; tp < game_data.players.Length; tp++)
|
||
{
|
||
//Looping on all cards is very slow, since there are no ongoing effects that works out of board/hand we loop on those only
|
||
Player tplayer = game_data.players[tp];
|
||
|
||
//Hand Cards
|
||
if (ability.target == AbilityTarget.AllCardsAllPiles || ability.target == AbilityTarget.AllCardsHand)
|
||
{
|
||
for (int tc = 0; tc < tplayer.cards_hand.Count; tc++)
|
||
{
|
||
Card tcard = tplayer.cards_hand[tc];
|
||
if (ability.AreTargetConditionsMet(game_data, card, tcard))
|
||
{
|
||
ability.DoOngoingEffects(this, card, tcard);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Board Cards
|
||
if (ability.target == AbilityTarget.AllCardsAllPiles || ability.target == AbilityTarget.AllCardsBoard)
|
||
{
|
||
for (int tc = 0; tc < tplayer.cards_board.Count; tc++)
|
||
{
|
||
Card tcard = tplayer.cards_board[tc];
|
||
if (ability.AreTargetConditionsMet(game_data, card, tcard))
|
||
{
|
||
ability.DoOngoingEffects(this, card, tcard);
|
||
}
|
||
}
|
||
}
|
||
|
||
//Equip Cards
|
||
if (ability.target == AbilityTarget.AllCardsAllPiles)
|
||
{
|
||
for (int tc = 0; tc < tplayer.cards_equip.Count; tc++)
|
||
{
|
||
Card tcard = tplayer.cards_equip[tc];
|
||
if (ability.AreTargetConditionsMet(game_data, card, tcard))
|
||
{
|
||
ability.DoOngoingEffects(this, card, tcard);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
protected virtual void AddOngoingStatusBonus(Card card, CardStatus status)
|
||
{
|
||
if (status.type == StatusType.AddAttack)
|
||
card.attack_ongoing += status.value;
|
||
if (status.type == StatusType.AddHP)
|
||
card.hp_ongoing += status.value;
|
||
if (status.type == StatusType.AddManaCost)
|
||
card.mana_ongoing += status.value;
|
||
}
|
||
|
||
//---- Secrets ------------
|
||
|
||
public virtual bool TriggerPlayerSecrets(Player player, AbilityTrigger secret_trigger)
|
||
{
|
||
for (int i = player.cards_secret.Count - 1; i >= 0; i--)
|
||
{
|
||
Card card = player.cards_secret[i];
|
||
CardData icard = card.CardData;
|
||
if (icard.type == CardType.Secret && !card.exhausted)
|
||
{
|
||
if (card.AreAbilityConditionsMet(secret_trigger, game_data, card, card))
|
||
{
|
||
resolve_queue.AddSecret(secret_trigger, card, card, ResolveSecret);
|
||
resolve_queue.SetDelay(0.5f);
|
||
card.exhausted = true;
|
||
|
||
if (onSecretTrigger != null)
|
||
onSecretTrigger.Invoke(card, card);
|
||
|
||
return true; //Trigger only 1 secret per trigger
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public virtual bool TriggerSecrets(AbilityTrigger secret_trigger, Card trigger_card)
|
||
{
|
||
if (trigger_card != null && trigger_card.HasStatus(StatusType.SpellImmunity))
|
||
return false; //Spell Immunity, triggerer is the one that trigger the trap, target is the one attacked, so usually the player who played the trap, so we dont check the target
|
||
|
||
for (int p = 0; p < game_data.players.Length; p++)
|
||
{
|
||
if (p != game_data.current_player)
|
||
{
|
||
Player other_player = game_data.players[p];
|
||
for (int i = other_player.cards_secret.Count - 1; i >= 0; i--)
|
||
{
|
||
Card card = other_player.cards_secret[i];
|
||
CardData icard = card.CardData;
|
||
if (icard.type == CardType.Secret && !card.exhausted)
|
||
{
|
||
Card trigger = trigger_card != null ? trigger_card : card;
|
||
if (card.AreAbilityConditionsMet(secret_trigger, game_data, card, trigger))
|
||
{
|
||
resolve_queue.AddSecret(secret_trigger, card, trigger, ResolveSecret);
|
||
resolve_queue.SetDelay(0.5f);
|
||
card.exhausted = true;
|
||
|
||
if (onSecretTrigger != null)
|
||
onSecretTrigger.Invoke(card, trigger);
|
||
|
||
return true; //Trigger only 1 secret per trigger
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
protected virtual void ResolveSecret(AbilityTrigger secret_trigger, Card secret_card, Card trigger)
|
||
{
|
||
CardData icard = secret_card.CardData;
|
||
Player player = game_data.GetPlayer(secret_card.player_id);
|
||
if (icard.type == CardType.Secret)
|
||
{
|
||
Player tplayer = game_data.GetPlayer(trigger.player_id);
|
||
if (!is_ai_predict)
|
||
tplayer.AddHistory(GameAction.SecretTriggered, secret_card, trigger);
|
||
|
||
TriggerCardAbilityType(secret_trigger, secret_card, trigger);
|
||
DiscardCard(secret_card);
|
||
|
||
if (onSecretResolve != null)
|
||
onSecretResolve.Invoke(secret_card, trigger);
|
||
}
|
||
}
|
||
|
||
//---- Resolve Selector -----
|
||
|
||
public virtual void SelectCard(Card target)
|
||
{
|
||
if (game_data.selector == SelectorType.None)
|
||
return;
|
||
|
||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||
|
||
if (caster == null || target == null || ability == null)
|
||
return;
|
||
|
||
if (game_data.selector == SelectorType.SelectTarget)
|
||
{
|
||
if (!ability.CanTarget(game_data, caster, target))
|
||
return; //Can't target that target
|
||
|
||
Player player = game_data.GetPlayer(caster.player_id);
|
||
if (!is_ai_predict)
|
||
player.AddHistory(GameAction.CastAbility, caster, ability, target);
|
||
|
||
game_data.selector = SelectorType.None;
|
||
game_data.last_target = target.uid;
|
||
ResolveEffectTarget(ability, caster, target);
|
||
AfterAbilityResolved(ability, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
|
||
if (game_data.selector == SelectorType.SelectorCard)
|
||
{
|
||
if (!ability.IsCardSelectionValid(game_data, caster, target, card_array))
|
||
return; //Supports conditions and filters
|
||
|
||
game_data.selector = SelectorType.None;
|
||
game_data.last_target = target.uid;
|
||
ResolveEffectTarget(ability, caster, target);
|
||
AfterAbilityResolved(ability, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
|
||
public virtual void SelectPlayer(Player target)
|
||
{
|
||
if (game_data.selector == SelectorType.None)
|
||
return;
|
||
|
||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||
|
||
if (caster == null || target == null || ability == null)
|
||
return;
|
||
|
||
if (game_data.selector == SelectorType.SelectTarget)
|
||
{
|
||
if (!ability.CanTarget(game_data, caster, target))
|
||
return; //Can't target that target
|
||
|
||
Player player = game_data.GetPlayer(caster.player_id);
|
||
if (!is_ai_predict)
|
||
player.AddHistory(GameAction.CastAbility, caster, ability, target);
|
||
|
||
game_data.selector = SelectorType.None;
|
||
ResolveEffectTarget(ability, caster, target);
|
||
AfterAbilityResolved(ability, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
|
||
public virtual void SelectSlot(Slot target)
|
||
{
|
||
if (game_data.selector == SelectorType.None)
|
||
return;
|
||
|
||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||
|
||
if (caster == null || ability == null || !target.IsValid())
|
||
return;
|
||
|
||
if (game_data.selector == SelectorType.SelectTarget)
|
||
{
|
||
if (!ability.CanTarget(game_data, caster, target))
|
||
return; //Conditions not met
|
||
|
||
Player player = game_data.GetPlayer(caster.player_id);
|
||
if (!is_ai_predict)
|
||
player.AddHistory(GameAction.CastAbility, caster, ability, target);
|
||
|
||
game_data.selector = SelectorType.None;
|
||
ResolveEffectTarget(ability, caster, target);
|
||
AfterAbilityResolved(ability, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
|
||
public virtual void SelectChoice(int choice)
|
||
{
|
||
if (game_data.selector == SelectorType.None)
|
||
return;
|
||
|
||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||
|
||
if (caster == null || ability == null || choice < 0)
|
||
return;
|
||
|
||
if (game_data.selector == SelectorType.SelectorChoice && ability.target == AbilityTarget.ChoiceSelector)
|
||
{
|
||
if (choice >= 0 && choice < ability.chain_abilities.Length)
|
||
{
|
||
AbilityData achoice = ability.chain_abilities[choice];
|
||
if (achoice != null && game_data.CanSelectAbility(caster, achoice))
|
||
{
|
||
game_data.selector = SelectorType.None;
|
||
AfterAbilityResolved(ability, caster);
|
||
ResolveCardAbility(achoice, caster, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void SelectCost(int select_cost)
|
||
{
|
||
if (game_data.selector == SelectorType.None)
|
||
return;
|
||
|
||
Player player = game_data.GetPlayer(game_data.selector_player_id);
|
||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||
|
||
if (player == null || caster == null || select_cost < 0)
|
||
return;
|
||
|
||
if (game_data.selector == SelectorType.SelectorCost)
|
||
{
|
||
if (select_cost >= 0 && select_cost < 10 && select_cost <= player.mana)
|
||
{
|
||
game_data.selector = SelectorType.None;
|
||
game_data.selected_value = select_cost;
|
||
player.mana -= select_cost;
|
||
RefreshData();
|
||
|
||
TriggerSecrets(AbilityTrigger.OnPlayOther, caster);
|
||
TriggerCardAbilityType(AbilityTrigger.OnPlay, caster);
|
||
TriggerOtherCardsAbilityType(AbilityTrigger.OnPlayOther, caster);
|
||
resolve_queue.ResolveAll();
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual void CancelSelection()
|
||
{
|
||
if (game_data.selector != SelectorType.None)
|
||
{
|
||
//Return card to hand if was selecting cost
|
||
if (game_data.selector == SelectorType.SelectorCost)
|
||
CancelPlayCard();
|
||
|
||
//End selection
|
||
game_data.selector = SelectorType.None;
|
||
RefreshData();
|
||
}
|
||
}
|
||
|
||
public void CancelPlayCard()
|
||
{
|
||
Card card = game_data.GetCard(game_data.selector_caster_uid);
|
||
if (card != null)
|
||
{
|
||
Player player = game_data.GetPlayer(card.player_id);
|
||
if (card.CardData.IsDynamicManaCost())
|
||
player.mana += game_data.selected_value;
|
||
else
|
||
player.mana += card.CardData.cost;
|
||
|
||
player.RemoveCardFromAllGroups(card);
|
||
player.AddCard(player.cards_hand, card);
|
||
card.Clear();
|
||
}
|
||
}
|
||
|
||
// 换牌
|
||
public virtual void Mulligan(Player player, string[] cards)
|
||
{
|
||
if (game_data.phase == GamePhase.Mulligan && !player.ready)
|
||
{
|
||
int count = 0;
|
||
List<Card> remove_list = new List<Card>();
|
||
foreach (Card card in player.cards_hand)
|
||
{
|
||
if (cards.Contains(card.uid))
|
||
{
|
||
remove_list.Add(card);
|
||
count++;
|
||
}
|
||
}
|
||
|
||
// 将换掉的卡牌临时存储,不放入弃牌堆
|
||
List<Card> mulligan_cards = new List<Card>();
|
||
foreach (Card card in remove_list)
|
||
{
|
||
player.RemoveCardFromAllGroups(card);
|
||
mulligan_cards.Add(card);
|
||
}
|
||
|
||
player.ready = true;
|
||
DrawCard(player, count);
|
||
|
||
// 将换掉的卡牌重新加入牌组并洗牌
|
||
foreach (Card card in mulligan_cards)
|
||
{
|
||
player.cards_deck.Add(card);
|
||
}
|
||
ShuffleDeck(player.cards_deck);
|
||
|
||
RefreshData();
|
||
|
||
if (game_data.AreAllPlayersReady())
|
||
{
|
||
CalculateFirstPlayer();
|
||
StartTurn();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-----Trigger Selector-----
|
||
|
||
protected virtual void GoToSelectTarget(AbilityData iability, Card caster)
|
||
{
|
||
game_data.selector = SelectorType.SelectTarget;
|
||
game_data.selector_player_id = caster.player_id;
|
||
game_data.selector_ability_id = iability.id;
|
||
game_data.selector_caster_uid = caster.uid;
|
||
RefreshData();
|
||
}
|
||
|
||
protected virtual void GoToSelectorCard(AbilityData iability, Card caster)
|
||
{
|
||
game_data.selector = SelectorType.SelectorCard;
|
||
game_data.selector_player_id = caster.player_id;
|
||
game_data.selector_ability_id = iability.id;
|
||
game_data.selector_caster_uid = caster.uid;
|
||
RefreshData();
|
||
}
|
||
|
||
protected virtual void GoToSelectorChoice(AbilityData iability, Card caster)
|
||
{
|
||
game_data.selector = SelectorType.SelectorChoice;
|
||
game_data.selector_player_id = caster.player_id;
|
||
game_data.selector_ability_id = iability.id;
|
||
game_data.selector_caster_uid = caster.uid;
|
||
RefreshData();
|
||
}
|
||
|
||
protected virtual void GoToSelectorCost(Card caster)
|
||
{
|
||
game_data.selector = SelectorType.SelectorCost;
|
||
game_data.selector_player_id = caster.player_id;
|
||
game_data.selector_ability_id = "";
|
||
game_data.selector_caster_uid = caster.uid;
|
||
game_data.selected_value = 0;
|
||
RefreshData();
|
||
}
|
||
|
||
protected virtual void GoToMulligan()
|
||
{
|
||
game_data.phase = GamePhase.Mulligan;
|
||
game_data.turn_timer = GameplayData.Get().turn_duration;
|
||
foreach (Player player in game_data.players)
|
||
player.ready = false;
|
||
RefreshData();
|
||
}
|
||
|
||
// 根据手牌攻击力计算先手玩家
|
||
protected virtual void CalculateFirstPlayer()
|
||
{
|
||
int f = 0, l = 0;
|
||
|
||
foreach (Player player in game_data.players)
|
||
{
|
||
int allCardAttack = 0;
|
||
foreach (Card card in player.cards_hand)
|
||
{
|
||
allCardAttack += card.CardData.attack;
|
||
}
|
||
|
||
if (player.player_id == 0)
|
||
{
|
||
f = allCardAttack;
|
||
}
|
||
else
|
||
{
|
||
l = allCardAttack;
|
||
}
|
||
}
|
||
|
||
game_data.first_player = f < l ? 0 : 1;
|
||
game_data.current_player = game_data.first_player;
|
||
}
|
||
|
||
//-------------
|
||
|
||
public virtual void RefreshData()
|
||
{
|
||
onRefresh?.Invoke();
|
||
}
|
||
|
||
public virtual void ClearResolve()
|
||
{
|
||
resolve_queue.Clear();
|
||
}
|
||
|
||
public virtual bool IsResolving()
|
||
{
|
||
return resolve_queue.IsResolving();
|
||
}
|
||
|
||
public virtual bool IsGameStarted()
|
||
{
|
||
return game_data.HasStarted();
|
||
}
|
||
|
||
public virtual bool IsGameEnded()
|
||
{
|
||
return game_data.HasEnded();
|
||
}
|
||
|
||
public virtual Game GetGameData()
|
||
{
|
||
return game_data;
|
||
}
|
||
|
||
public System.Random GetRandom()
|
||
{
|
||
return random;
|
||
}
|
||
|
||
public Game GameData { get { return game_data; } }
|
||
public ResolveQueue ResolveQueue { get { return resolve_queue; } }
|
||
}
|
||
} |