init
This commit is contained in:
8
Assets/TcgEngine/Scripts/AI.meta
Normal file
8
Assets/TcgEngine/Scripts/AI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34ed9bd9838a40c4d9755bc0fbba9a8c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
225
Assets/TcgEngine/Scripts/AI/AIHeuristic.cs
Normal file
225
Assets/TcgEngine/Scripts/AI/AIHeuristic.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// Values and calculations for various values of the AI decision-making, adjusting these can improve your AI
|
||||
/// Heuristic: Represent the score of a board state, high score favor AI, low score favor the opponent
|
||||
/// Action Score: Represent the score of an individual action, to proritize actions if too many in a single node
|
||||
/// Action Sort Order: Value to determine the order actions should be executed in a single turn to avoid searching same things in different order, executed in ascending order
|
||||
/// </summary>
|
||||
|
||||
public class AIHeuristic
|
||||
{
|
||||
//---------- Heuristic PARAMS -------------
|
||||
|
||||
public int board_card_value = 20; //Score of having cards on board
|
||||
public int secret_card_value = 10; //Score of having cards in secret zone
|
||||
public int hand_card_value = 5; //Score of having cards in hand
|
||||
public int kill_value = 5; //Score of killing a card
|
||||
|
||||
public int player_hp_value = 4; //Score per player hp
|
||||
public int card_attack_value = 3; //Score per board card attack
|
||||
public int card_hp_value = 2; //Score per board card hp
|
||||
public int card_status_value = 15; //Score per status on card (multiplied by hvalue of StatusData)
|
||||
|
||||
//-----------
|
||||
|
||||
private int ai_player_id; //ID of this AI, usually the human is 0 and AI is 1
|
||||
private int ai_level; //ai level (level 10 is the best, level 1 is the worst)
|
||||
private int heuristic_modifier; //Randomize heuristic for lower level ai
|
||||
private System.Random random_gen;
|
||||
|
||||
public AIHeuristic(int player_id, int level)
|
||||
{
|
||||
ai_player_id = player_id;
|
||||
ai_level = level;
|
||||
heuristic_modifier = GetHeuristicModifier();
|
||||
random_gen = new System.Random();
|
||||
}
|
||||
|
||||
public int CalculateHeuristic(Game data, NodeState node)
|
||||
{
|
||||
Player aiplayer = data.GetPlayer(ai_player_id);
|
||||
Player oplayer = data.GetOpponentPlayer(ai_player_id);
|
||||
return CalculateHeuristic(data, node, aiplayer, oplayer);
|
||||
}
|
||||
|
||||
//Calculate full heuristic
|
||||
//Should return a value between -10000 and 10000 (unless its a win)
|
||||
public int CalculateHeuristic(Game data, NodeState node, Player aiplayer, Player oplayer)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
//Victories
|
||||
if (aiplayer.IsDead())
|
||||
score += -100000 + node.tdepth * 1000; //Add node depth to seek surviving longest
|
||||
if (oplayer.IsDead())
|
||||
score += 100000 - node.tdepth * 1000; //Reduce node depth to seek fastest win
|
||||
|
||||
//Board state
|
||||
score += aiplayer.cards_board.Count * board_card_value;
|
||||
score += aiplayer.cards_equip.Count * board_card_value;
|
||||
score += aiplayer.cards_secret.Count * secret_card_value;
|
||||
score += aiplayer.cards_hand.Count * hand_card_value;
|
||||
score += aiplayer.kill_count * kill_value;
|
||||
score += aiplayer.hp * player_hp_value;
|
||||
|
||||
score -= oplayer.cards_board.Count * board_card_value;
|
||||
score -= oplayer.cards_equip.Count * board_card_value;
|
||||
score -= oplayer.cards_secret.Count * secret_card_value;
|
||||
score -= oplayer.cards_hand.Count * hand_card_value;
|
||||
score -= oplayer.kill_count * kill_value;
|
||||
score -= oplayer.hp * player_hp_value;
|
||||
|
||||
|
||||
foreach (Card card in aiplayer.cards_board)
|
||||
{
|
||||
score += card.GetAttack() * card_attack_value;
|
||||
score += card.GetHP() * card_hp_value;
|
||||
|
||||
foreach (CardStatus status in card.status)
|
||||
score += status.StatusData.hvalue * card_status_value;
|
||||
foreach (CardStatus status in card.ongoing_status)
|
||||
score += status.StatusData.hvalue * card_status_value;
|
||||
}
|
||||
foreach (Card card in oplayer.cards_board)
|
||||
{
|
||||
score -= card.GetAttack() * card_attack_value;
|
||||
score -= card.GetHP() * card_hp_value;
|
||||
|
||||
foreach (CardStatus status in card.status)
|
||||
score -= status.StatusData.hvalue * card_status_value;
|
||||
foreach (CardStatus status in card.ongoing_status)
|
||||
score -= status.StatusData.hvalue * card_status_value;
|
||||
}
|
||||
|
||||
if (heuristic_modifier > 0)
|
||||
score += random_gen.Next(-heuristic_modifier, heuristic_modifier);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
//This calculates the score of an individual action, instead of the board state
|
||||
//When too many actions are possible in a single node, only the ones with best action score will be evaluated
|
||||
//Make sure to return a positive value
|
||||
public int CalculateActionScore(Game data, AIAction order)
|
||||
{
|
||||
if (order.type == GameAction.EndTurn)
|
||||
return 0; //Other orders are better
|
||||
|
||||
if (order.type == GameAction.CancelSelect)
|
||||
return 0; //Other orders are better
|
||||
|
||||
if (order.type == GameAction.CastAbility)
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
if (order.type == GameAction.Attack)
|
||||
{
|
||||
Card card = data.GetCard(order.card_uid);
|
||||
Card target = data.GetCard(order.target_uid);
|
||||
int ascore = card.GetAttack() >= target.GetHP() ? 300 : 100; //Are you killing the card?
|
||||
int oscore = target.GetAttack() >= card.GetHP() ? -200 : 0; //Are you getting killed?
|
||||
return ascore + oscore + target.GetAttack() * 5; //Always better to get rid of high-attack cards
|
||||
}
|
||||
if (order.type == GameAction.AttackPlayer)
|
||||
{
|
||||
Card card = data.GetCard(order.card_uid);
|
||||
Player player = data.GetPlayer(order.target_player_id);
|
||||
int ascore = card.GetAttack() >= player.hp ? 500 : 200; //Are you killing the player?
|
||||
return ascore + (card.GetAttack() * 10) - player.hp; //Always better to inflict more damage
|
||||
}
|
||||
if (order.type == GameAction.PlayCard)
|
||||
{
|
||||
Player player = data.GetPlayer(ai_player_id);
|
||||
Card card = data.GetCard(order.card_uid);
|
||||
if (card.CardData.IsBoardCard())
|
||||
return 200 + (card.GetMana() * 5) - (30 * player.cards_board.Count); //High cost cards are better to play, better to play when not a lot of cards in play
|
||||
else if (card.CardData.IsEquipment())
|
||||
return 200 + (card.GetMana() * 5) - (30 * player.cards_equip.Count);
|
||||
else
|
||||
return 200 + (card.GetMana() * 5);
|
||||
}
|
||||
|
||||
if (order.type == GameAction.Move)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
return 100; //Other actions are better than End/Cancel
|
||||
}
|
||||
|
||||
//Within the same turn, actions can only be executed in sorting order, make sure it returns positive value higher than 0 or it wont be sorted
|
||||
//This prevents calculating all possibilities of A->B->C B->C->A C->A->B etc..
|
||||
//If two AIActions with same sorting value, or if sorting value is 0, ai will test all ordering variations (slower)
|
||||
//This would not be necessary in a game with only 1 action per turn (such as chess) but is useful for AI that can perform multiple actions in 1 turn
|
||||
//Ordering could be improved, pretty much random now
|
||||
public int CalculateActionSort(Game data, AIAction order)
|
||||
{
|
||||
if (order.type == GameAction.EndTurn)
|
||||
return 0; //End turn can always be performed, 0 means any order
|
||||
if (data.selector != SelectorType.None)
|
||||
return 0; //Selector actions not affected by sorting
|
||||
|
||||
Card card = data.GetCard(order.card_uid);
|
||||
Card target = order.target_uid != null ? data.GetCard(order.target_uid) : null;
|
||||
bool is_spell = card != null && !card.CardData.IsBoardCard();
|
||||
|
||||
int type_sort = 0;
|
||||
if (order.type == GameAction.PlayCard && is_spell)
|
||||
type_sort = 1; //Play Spells first
|
||||
if (order.type == GameAction.CastAbility)
|
||||
type_sort = 2; //Card Abilities second
|
||||
if (order.type == GameAction.Move)
|
||||
type_sort = 3; //Move third
|
||||
if (order.type == GameAction.Attack)
|
||||
type_sort = 4; //Attacks fourth
|
||||
if (order.type == GameAction.AttackPlayer)
|
||||
type_sort = 5; //Player attacks fifth
|
||||
if (order.type == GameAction.PlayCard && !is_spell)
|
||||
type_sort = 7; //Play Characters last
|
||||
|
||||
int card_sort = card != null ? (card.Hash % 100) : 0;
|
||||
int target_sort = target != null ? (target.Hash % 100) : 0;
|
||||
int sort = type_sort * 10000 + card_sort * 100 + target_sort + 1;
|
||||
return sort;
|
||||
}
|
||||
|
||||
//Lower level AI add a random number to their heuristic
|
||||
private int GetHeuristicModifier()
|
||||
{
|
||||
if (ai_level >= 10)
|
||||
return 0;
|
||||
if (ai_level == 9)
|
||||
return 5;
|
||||
if (ai_level == 8)
|
||||
return 10;
|
||||
if (ai_level == 7)
|
||||
return 20;
|
||||
if (ai_level == 6)
|
||||
return 30;
|
||||
if (ai_level == 5)
|
||||
return 40;
|
||||
if (ai_level == 4)
|
||||
return 50;
|
||||
if (ai_level == 3)
|
||||
return 75;
|
||||
if (ai_level == 2)
|
||||
return 100;
|
||||
if (ai_level <= 1)
|
||||
return 200;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Check if this node represent one of the players winning
|
||||
public bool IsWin(NodeState node)
|
||||
{
|
||||
return node.hvalue > 50000 || node.hvalue < -50000;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/AI/AIHeuristic.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/AI/AIHeuristic.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a435ad5e46007f5449dc8d627fbe175f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
825
Assets/TcgEngine/Scripts/AI/AILogic.cs
Normal file
825
Assets/TcgEngine/Scripts/AI/AILogic.cs
Normal file
@@ -0,0 +1,825 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimax algorithm for AI.
|
||||
/// </summary>
|
||||
|
||||
public class AILogic
|
||||
{
|
||||
//-------- AI Logic Params ------------------
|
||||
|
||||
public int ai_depth = 3; //How many turns in advance does it check, higher number takes exponentially longer
|
||||
public int ai_depth_wide = 1; //For these first few turns, will consider more options, slow!
|
||||
public int actions_per_turn = 2; //AI wont predict more than this number of sequential actions per turn, if more than that will EndTurn (Do A, then do B, then do C, then end turn)
|
||||
public int actions_per_turn_wide = 3; //Same but in wide depth
|
||||
public int nodes_per_action = 4; //For a turn action (1st, 2nd, or 3rd...), cannot evaluate more than this number of child nodes, if more, will only process the AIActions with with best score
|
||||
public int nodes_per_action_wide = 7; //Same but in wide depth
|
||||
|
||||
//Example: for the first turn, AI will predict 3 sequential actions (I play a card, then attack with this one, then play a spell),
|
||||
//for each of those actions, it will look at 7 possibilities, if more will cut based on score, keeping the actions with highest score
|
||||
//At depth 2 and 3 it will only try to perform 2 actions but for each one will evaluate 4 possibilities. Depth 2 is the opponent's turn and depth 3 is the AI's next turn.
|
||||
//For the nodes that are evaluated, will go down to depth 3 and calculate heuristic at the max depth, and then propagate the heuristic up in the node tree.
|
||||
//AI will choose the move that has a path leading to the best heuristic.
|
||||
|
||||
//-----
|
||||
|
||||
public int ai_player_id; //AI player_id (usually its 1)
|
||||
public int ai_level; //AI level
|
||||
|
||||
private GameLogic game_logic; //Game logic used to calculate moves
|
||||
private Game original_data; //Original game data when start calculating possibilities
|
||||
private AIHeuristic heuristic;
|
||||
private Thread ai_thread;
|
||||
|
||||
private NodeState first_node = null;
|
||||
private NodeState best_move = null;
|
||||
|
||||
private bool running = false;
|
||||
private int nb_calculated = 0;
|
||||
private int reached_depth = 0;
|
||||
|
||||
private System.Random random_gen;
|
||||
|
||||
private Pool<NodeState> node_pool = new Pool<NodeState>();
|
||||
private Pool<Game> data_pool = new Pool<Game>();
|
||||
private Pool<AIAction> action_pool = new Pool<AIAction>();
|
||||
private Pool<List<AIAction>> list_pool = new Pool<List<AIAction>>();
|
||||
private ListSwap<Card> card_array = new ListSwap<Card>();
|
||||
private ListSwap<Slot> slot_array = new ListSwap<Slot>();
|
||||
|
||||
public static AILogic Create(int player_id, int level)
|
||||
{
|
||||
AILogic job = new AILogic();
|
||||
job.ai_player_id = player_id;
|
||||
job.ai_level = level;
|
||||
|
||||
job.heuristic = new AIHeuristic(player_id, level);
|
||||
job.game_logic = new GameLogic(true); //Skip all delays for the AI calculations
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
public void RunAI(Game data)
|
||||
{
|
||||
if (running)
|
||||
return;
|
||||
|
||||
original_data = Game.CloneNew(data); //Clone game data to keep original data unaffected
|
||||
game_logic.ClearResolve(); //Clear temp memory
|
||||
game_logic.SetData(original_data); //Assign data to game logic
|
||||
random_gen = new System.Random(); //Reset random seed
|
||||
|
||||
first_node = null;
|
||||
reached_depth = 0;
|
||||
nb_calculated = 0;
|
||||
running = true;
|
||||
|
||||
//Uncomment these lines to run on separate thread (and comment Execute()), better for production so it doesn't freeze the UI while calculating the AI
|
||||
ai_thread = new Thread(Execute);
|
||||
ai_thread.Start();
|
||||
|
||||
//Uncomment this line to run on main thread (and comment the thread one), better for debuging since you will be able to use breakpoints, profiler and Debug.Log
|
||||
//Execute();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
running = false;
|
||||
if (ai_thread != null && ai_thread.IsAlive)
|
||||
ai_thread.Abort();
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
//Create first node
|
||||
first_node = CreateNode(null, null, ai_player_id, 0, 0);
|
||||
first_node.hvalue = heuristic.CalculateHeuristic(original_data, first_node);
|
||||
first_node.alpha = int.MinValue;
|
||||
first_node.beta = int.MaxValue;
|
||||
|
||||
Profiler.BeginSample("AI");
|
||||
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
//Calculate first node
|
||||
CalculateNode(original_data, first_node);
|
||||
|
||||
Debug.Log("AI: Time " + watch.ElapsedMilliseconds + "ms Depth " + reached_depth + " Nodes " + nb_calculated);
|
||||
Profiler.EndSample();
|
||||
|
||||
//Save best move
|
||||
best_move = first_node.best_child;
|
||||
running = false;
|
||||
}
|
||||
|
||||
//Add list of all possible orders and search in all of them
|
||||
private void CalculateNode(Game data, NodeState node)
|
||||
{
|
||||
Profiler.BeginSample("Add Actions");
|
||||
Player player = data.GetPlayer(data.current_player);
|
||||
List<AIAction> action_list = list_pool.Create();
|
||||
|
||||
int max_actions = node.tdepth < ai_depth_wide ? actions_per_turn_wide : actions_per_turn;
|
||||
if (node.taction < max_actions)
|
||||
{
|
||||
if (data.selector == SelectorType.None)
|
||||
{
|
||||
//Play card
|
||||
for (int c = 0; c < player.cards_hand.Count; c++)
|
||||
{
|
||||
Card card = player.cards_hand[c];
|
||||
AddActions(action_list, data, node, GameAction.PlayCard, card);
|
||||
}
|
||||
|
||||
//Action on board
|
||||
for (int c = 0; c < player.cards_board.Count; c++)
|
||||
{
|
||||
Card card = player.cards_board[c];
|
||||
AddActions(action_list, data, node, GameAction.Attack, card);
|
||||
AddActions(action_list, data, node, GameAction.AttackPlayer, card);
|
||||
AddActions(action_list, data, node, GameAction.CastAbility, card);
|
||||
//AddActions(action_list, data, node, GameAction.Move, card); //Uncomment to consider move actions
|
||||
}
|
||||
|
||||
if (player.hero != null)
|
||||
AddActions(action_list, data, node, GameAction.CastAbility, player.hero);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSelectActions(action_list, data, node);
|
||||
}
|
||||
}
|
||||
|
||||
//End Turn (dont add action if ai can still attack player, or ai hasnt spent any mana)
|
||||
bool is_full_mana = HasAction(action_list, GameAction.PlayCard) && player.mana >= player.mana_max;
|
||||
bool can_attack_player = HasAction(action_list, GameAction.AttackPlayer);
|
||||
bool can_end = !can_attack_player && !is_full_mana && data.selector == SelectorType.None;
|
||||
if (action_list.Count == 0 || can_end)
|
||||
{
|
||||
AIAction actiont = CreateAction(GameAction.EndTurn);
|
||||
action_list.Add(actiont);
|
||||
}
|
||||
|
||||
//Remove actions with low score
|
||||
FilterActions(data, node, action_list);
|
||||
Profiler.EndSample();
|
||||
|
||||
//Execute valid action and search child node
|
||||
for (int o = 0; o < action_list.Count; o++)
|
||||
{
|
||||
AIAction action = action_list[o];
|
||||
if (action.valid && node.alpha < node.beta)
|
||||
{
|
||||
CalculateChildNode(data, node, action);
|
||||
}
|
||||
}
|
||||
|
||||
action_list.Clear();
|
||||
list_pool.Dispose(action_list);
|
||||
}
|
||||
|
||||
//Mark valid/invalid on each action, if too many actions, will keep only the ones with best score
|
||||
private void FilterActions(Game data, NodeState node, List<AIAction> action_list)
|
||||
{
|
||||
int count_valid = 0;
|
||||
for (int o = 0; o < action_list.Count; o++)
|
||||
{
|
||||
AIAction action = action_list[o];
|
||||
action.sort = heuristic.CalculateActionSort(data, action);
|
||||
action.valid = action.sort <= 0 || action.sort >= node.sort_min;
|
||||
if (action.valid)
|
||||
count_valid++;
|
||||
}
|
||||
|
||||
int max_actions = node.tdepth < ai_depth_wide ? nodes_per_action_wide : nodes_per_action;
|
||||
int max_actions_skip = max_actions + 2; //No need to calculate all scores if its just to remove 1-2 actions
|
||||
if (count_valid <= max_actions_skip)
|
||||
return; //No filtering needed
|
||||
|
||||
//Calculate scores
|
||||
for (int o = 0; o < action_list.Count; o++)
|
||||
{
|
||||
AIAction action = action_list[o];
|
||||
if (action.valid)
|
||||
{
|
||||
action.score = heuristic.CalculateActionScore(data, action);
|
||||
}
|
||||
}
|
||||
|
||||
//Sort, and invalidate actions with low score
|
||||
action_list.Sort((AIAction a, AIAction b) => { return b.score.CompareTo(a.score); });
|
||||
for (int o = 0; o < action_list.Count; o++)
|
||||
{
|
||||
AIAction action = action_list[o];
|
||||
action.valid = action.valid && o < max_actions;
|
||||
}
|
||||
}
|
||||
|
||||
//Create a child node for parent, and calculate it
|
||||
private void CalculateChildNode(Game data, NodeState parent, AIAction action)
|
||||
{
|
||||
if (action.type == GameAction.None)
|
||||
return;
|
||||
|
||||
int player_id = data.current_player;
|
||||
|
||||
//Clone data so we can update it in a new node
|
||||
Profiler.BeginSample("Clone Data");
|
||||
Game ndata = data_pool.Create();
|
||||
Game.Clone(data, ndata); //Clone
|
||||
game_logic.ClearResolve();
|
||||
game_logic.SetData(ndata);
|
||||
Profiler.EndSample();
|
||||
|
||||
//Execute move and update data
|
||||
Profiler.BeginSample("Execute AIAction");
|
||||
DoAIAction(ndata, action, player_id);
|
||||
Profiler.EndSample();
|
||||
|
||||
//Update depth
|
||||
bool new_turn = action.type == GameAction.EndTurn;
|
||||
int next_tdepth = parent.tdepth;
|
||||
int next_taction = parent.taction + 1;
|
||||
|
||||
if (new_turn)
|
||||
{
|
||||
next_tdepth = parent.tdepth + 1;
|
||||
next_taction = 0;
|
||||
}
|
||||
|
||||
//Create node
|
||||
Profiler.BeginSample("Create Node");
|
||||
NodeState child_node = CreateNode(parent, action, player_id, next_tdepth, next_taction);
|
||||
parent.childs.Add(child_node);
|
||||
Profiler.EndSample();
|
||||
|
||||
//Set minimum sort for next AIActions, if new turn, reset to 0
|
||||
child_node.sort_min = new_turn ? 0 : Mathf.Max(action.sort, child_node.sort_min);
|
||||
|
||||
//If win or reached max depth, stop searching deeper
|
||||
if (!ndata.HasEnded() && child_node.tdepth < ai_depth)
|
||||
{
|
||||
//Calculate child
|
||||
CalculateNode(ndata, child_node);
|
||||
}
|
||||
else
|
||||
{
|
||||
//End of tree, calculate full Heuristic
|
||||
child_node.hvalue = heuristic.CalculateHeuristic(ndata, child_node);
|
||||
}
|
||||
|
||||
//Update parents hvalue, alpha, beta, and best child
|
||||
if (player_id == ai_player_id)
|
||||
{
|
||||
//AI player
|
||||
if (parent.best_child == null || child_node.hvalue > parent.hvalue)
|
||||
{
|
||||
parent.best_child = child_node;
|
||||
parent.hvalue = child_node.hvalue;
|
||||
parent.alpha = Mathf.Max(parent.alpha, parent.hvalue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Opponent player
|
||||
if (parent.best_child == null || child_node.hvalue < parent.hvalue)
|
||||
{
|
||||
parent.best_child = child_node;
|
||||
parent.hvalue = child_node.hvalue;
|
||||
parent.beta = Mathf.Min(parent.beta, parent.hvalue);
|
||||
}
|
||||
}
|
||||
|
||||
//Just for debug, keep track of node/depth count
|
||||
nb_calculated++;
|
||||
if (child_node.tdepth > reached_depth)
|
||||
reached_depth = child_node.tdepth;
|
||||
|
||||
//We are done with this game data, dispose it.
|
||||
//Dont dispose NodeState here (node_pool) since we want to retrieve the full tree path later
|
||||
data_pool.Dispose(ndata);
|
||||
}
|
||||
|
||||
private NodeState CreateNode(NodeState parent, AIAction action, int player_id, int turn_depth, int turn_action)
|
||||
{
|
||||
NodeState nnode = node_pool.Create();
|
||||
nnode.current_player = player_id;
|
||||
nnode.tdepth = turn_depth;
|
||||
nnode.taction = turn_action;
|
||||
nnode.parent = parent;
|
||||
nnode.last_action = action;
|
||||
nnode.alpha = parent != null ? parent.alpha : int.MinValue;
|
||||
nnode.beta = parent != null ? parent.beta : int.MaxValue;
|
||||
nnode.hvalue = 0;
|
||||
nnode.sort_min = 0;
|
||||
return nnode;
|
||||
}
|
||||
|
||||
//Add all possible moves for card to list of actions
|
||||
private void AddActions(List<AIAction> actions, Game data, NodeState node, ushort type, Card card)
|
||||
{
|
||||
Player player = data.GetPlayer(data.current_player);
|
||||
|
||||
if (data.selector != SelectorType.None)
|
||||
return;
|
||||
|
||||
if (card.HasStatus(StatusType.Paralysed))
|
||||
return;
|
||||
|
||||
if (type == GameAction.PlayCard)
|
||||
{
|
||||
if (card.CardData.IsBoardCard())
|
||||
{
|
||||
//Doesn't matter where the card is played
|
||||
Slot slot = player.GetRandomEmptySlot(random_gen, slot_array.Get());
|
||||
|
||||
if (data.CanPlayCard(card, slot))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.slot = slot;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
else if (card.CardData.IsEquipment())
|
||||
{
|
||||
Player tplayer = data.GetPlayer(card.player_id);
|
||||
for (int c = 0; c < tplayer.cards_board.Count; c++)
|
||||
{
|
||||
Card tcard = tplayer.cards_board[c];
|
||||
if (data.CanPlayCard(card, tcard.slot))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.slot = tcard.slot;
|
||||
action.target_player_id = tplayer.player_id;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (card.CardData.IsRequireTargetSpell())
|
||||
{
|
||||
for (int p = 0; p < data.players.Length; p++)
|
||||
{
|
||||
Player tplayer = data.players[p];
|
||||
Slot tslot = new Slot(tplayer.player_id);
|
||||
if (data.CanPlayCard(card, tslot))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.slot = tslot;
|
||||
action.target_player_id = tplayer.player_id;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
foreach (Slot slot in Slot.GetAll())
|
||||
{
|
||||
if (data.CanPlayCard(card, slot))
|
||||
{
|
||||
Card slot_card = data.GetSlotCard(slot);
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.slot = slot;
|
||||
action.target_uid = slot_card != null ? slot_card.uid : null;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.CanPlayCard(card, Slot.None))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == GameAction.Attack)
|
||||
{
|
||||
if (card.CanAttack())
|
||||
{
|
||||
for (int p = 0; p < data.players.Length; p++)
|
||||
{
|
||||
if (p != player.player_id)
|
||||
{
|
||||
Player oplayer = data.players[p];
|
||||
for (int tc = 0; tc < oplayer.cards_board.Count; tc++)
|
||||
{
|
||||
Card target = oplayer.cards_board[tc];
|
||||
if (data.CanAttackTarget(card, target))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.target_uid = target.uid;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == GameAction.AttackPlayer)
|
||||
{
|
||||
if (card.CanAttack())
|
||||
{
|
||||
for (int p = 0; p < data.players.Length; p++)
|
||||
{
|
||||
if (p != player.player_id)
|
||||
{
|
||||
Player oplayer = data.players[p];
|
||||
if (data.CanAttackTarget(card, oplayer))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.target_player_id = oplayer.player_id;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == GameAction.CastAbility)
|
||||
{
|
||||
List<AbilityData> abilities = card.GetAbilities();
|
||||
for (int a = 0; a < abilities.Count; a++)
|
||||
{
|
||||
AbilityData ability = abilities[a];
|
||||
if (ability.trigger == AbilityTrigger.Activate && data.CanCastAbility(card, ability) && ability.HasValidSelectTarget(data, card))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.ability_id = ability.id;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == GameAction.Move)
|
||||
{
|
||||
foreach (Slot slot in Slot.GetAll(player.player_id))
|
||||
{
|
||||
if (data.CanMoveCard(card, slot))
|
||||
{
|
||||
AIAction action = CreateAction(type, card);
|
||||
action.slot = slot;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Add all possible moves for a selection
|
||||
private void AddSelectActions(List<AIAction> actions, Game data, NodeState node)
|
||||
{
|
||||
if (data.selector == SelectorType.None)
|
||||
return;
|
||||
|
||||
Player player = data.GetPlayer(data.selector_player_id);
|
||||
Card caster = data.GetCard(data.selector_caster_uid);
|
||||
AbilityData ability = AbilityData.Get(data.selector_ability_id);
|
||||
if (player == null || caster == null)
|
||||
return;
|
||||
|
||||
if (data.selector == SelectorType.SelectTarget && ability != null)
|
||||
{
|
||||
for (int p = 0; p < data.players.Length; p++)
|
||||
{
|
||||
Player tplayer = data.players[p];
|
||||
if (ability.CanTarget(data, caster, tplayer))
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectPlayer, caster);
|
||||
action.target_player_id = tplayer.player_id;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Slot slot in Slot.GetAll())
|
||||
{
|
||||
Card tcard = data.GetSlotCard(slot);
|
||||
if (tcard != null && ability.CanTarget(data, caster, tcard))
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectCard, caster);
|
||||
action.target_uid = tcard.uid;
|
||||
actions.Add(action);
|
||||
}
|
||||
else if (tcard == null && ability.CanTarget(data, caster, slot))
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectSlot, caster);
|
||||
action.slot = slot;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.selector == SelectorType.SelectorCard && ability != null)
|
||||
{
|
||||
for (int p = 0; p < data.players.Length; p++)
|
||||
{
|
||||
List<Card> cards = ability.GetCardTargets(data, caster, card_array);
|
||||
foreach (Card tcard in cards)
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectCard, caster);
|
||||
action.target_uid = tcard.uid;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.selector == SelectorType.SelectorChoice && ability != null)
|
||||
{
|
||||
for (int i = 0; i < ability.chain_abilities.Length; i++)
|
||||
{
|
||||
AbilityData choice = ability.chain_abilities[i];
|
||||
if (choice != null && data.CanSelectAbility(caster, choice))
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectChoice, caster);
|
||||
action.value = i;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.selector == SelectorType.SelectorCost)
|
||||
{
|
||||
for (int i = 1; i <= player.mana; i++)
|
||||
{
|
||||
AIAction action = CreateAction(GameAction.SelectCost, caster);
|
||||
action.value = i;
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
//Add option to cancel, if no valid options
|
||||
if (actions.Count == 0)
|
||||
{
|
||||
AIAction caction = CreateAction(GameAction.CancelSelect, caster);
|
||||
actions.Add(caction);
|
||||
}
|
||||
}
|
||||
|
||||
private AIAction CreateAction(ushort type)
|
||||
{
|
||||
AIAction action = action_pool.Create();
|
||||
action.Clear();
|
||||
action.type = type;
|
||||
action.valid = true;
|
||||
return action;
|
||||
}
|
||||
|
||||
private AIAction CreateAction(ushort type, Card card)
|
||||
{
|
||||
AIAction action = action_pool.Create();
|
||||
action.Clear();
|
||||
action.type = type;
|
||||
action.card_uid = card.uid;
|
||||
action.valid = true;
|
||||
return action;
|
||||
}
|
||||
|
||||
//Simulate AI action
|
||||
private void DoAIAction(Game data, AIAction action, int player_id)
|
||||
{
|
||||
Player player = data.GetPlayer(player_id);
|
||||
|
||||
if (action.type == GameAction.PlayCard)
|
||||
{
|
||||
Card card = player.GetHandCard(action.card_uid);
|
||||
game_logic.PlayCard(card, action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.Move)
|
||||
{
|
||||
Card card = player.GetBoardCard(action.card_uid);
|
||||
game_logic.MoveCard(card, action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.Attack)
|
||||
{
|
||||
Card card = player.GetBoardCard(action.card_uid);
|
||||
Card target = data.GetBoardCard(action.target_uid);
|
||||
game_logic.AttackTarget(card, target);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.AttackPlayer)
|
||||
{
|
||||
Card card = player.GetBoardCard(action.card_uid);
|
||||
Player tplayer = data.GetPlayer(action.target_player_id);
|
||||
game_logic.AttackPlayer(card, tplayer);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.CastAbility)
|
||||
{
|
||||
Card card = player.GetCard(action.card_uid);
|
||||
AbilityData ability = AbilityData.Get(action.ability_id);
|
||||
game_logic.CastAbility(card, ability);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectCard)
|
||||
{
|
||||
Card target = data.GetCard(action.target_uid);
|
||||
game_logic.SelectCard(target);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectPlayer)
|
||||
{
|
||||
Player target = data.GetPlayer(action.target_player_id);
|
||||
game_logic.SelectPlayer(target);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectSlot)
|
||||
{
|
||||
game_logic.SelectSlot(action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectChoice)
|
||||
{
|
||||
game_logic.SelectChoice(action.value);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.CancelSelect)
|
||||
{
|
||||
game_logic.CancelSelection();
|
||||
}
|
||||
|
||||
if (action.type == GameAction.EndTurn)
|
||||
{
|
||||
game_logic.EndTurn();
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAction(List<AIAction> list, ushort type)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].type == type)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//----Return values----
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
public string GetNodePath()
|
||||
{
|
||||
return GetNodePath(first_node);
|
||||
}
|
||||
|
||||
public string GetNodePath(NodeState node)
|
||||
{
|
||||
string path = "Prediction: HValue: " + node.hvalue + "\n";
|
||||
NodeState current = node;
|
||||
AIAction move;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
move = current.last_action;
|
||||
if (move != null)
|
||||
path += "Player " + current.current_player + ": " + move.GetText(original_data) + "\n";
|
||||
current = current.best_child;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public void ClearMemory()
|
||||
{
|
||||
original_data = null;
|
||||
first_node = null;
|
||||
best_move = null;
|
||||
|
||||
foreach (NodeState node in node_pool.GetAllActive())
|
||||
node.Clear();
|
||||
foreach (AIAction order in action_pool.GetAllActive())
|
||||
order.Clear();
|
||||
|
||||
data_pool.DisposeAll();
|
||||
node_pool.DisposeAll();
|
||||
action_pool.DisposeAll();
|
||||
list_pool.DisposeAll();
|
||||
|
||||
System.GC.Collect(); //Free memory from AI
|
||||
}
|
||||
|
||||
public int GetNbNodesCalculated()
|
||||
{
|
||||
return nb_calculated;
|
||||
}
|
||||
|
||||
public int GetDepthReached()
|
||||
{
|
||||
return reached_depth;
|
||||
}
|
||||
|
||||
public NodeState GetBest()
|
||||
{
|
||||
return best_move;
|
||||
}
|
||||
|
||||
public NodeState GetFirst()
|
||||
{
|
||||
return first_node;
|
||||
}
|
||||
|
||||
public AIAction GetBestAction()
|
||||
{
|
||||
return best_move != null ? best_move.last_action : null;
|
||||
}
|
||||
|
||||
public bool IsBestFound()
|
||||
{
|
||||
return best_move != null;
|
||||
}
|
||||
}
|
||||
|
||||
public class NodeState
|
||||
{
|
||||
public int tdepth; //Depth in number of turns
|
||||
public int taction; //How many orders in current turn
|
||||
public int sort_min; //Sorting minimum value, orders below this value will be ignored to avoid calculate both path A -> B and path B -> A
|
||||
public int hvalue; //Heuristic value, this AI tries to maximize it, opponent tries to minimize it
|
||||
public int alpha; //Highest heuristic reached by the AI player, used for optimization and ignore some tree branch
|
||||
public int beta; //Lowest heuristic reached by the opponent player, used for optimization and ignore some tree branch
|
||||
|
||||
public AIAction last_action = null;
|
||||
public int current_player;
|
||||
|
||||
public NodeState parent;
|
||||
public NodeState best_child = null;
|
||||
public List<NodeState> childs = new List<NodeState>();
|
||||
|
||||
public NodeState() { }
|
||||
|
||||
public NodeState(NodeState parent, int player_id, int turn_depth, int turn_action, int turn_sort)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.current_player = player_id;
|
||||
this.tdepth = turn_depth;
|
||||
this.taction = turn_action;
|
||||
this.sort_min = turn_sort;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
last_action = null;
|
||||
best_child = null;
|
||||
parent = null;
|
||||
childs.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class AIAction
|
||||
{
|
||||
public ushort type;
|
||||
|
||||
public string card_uid;
|
||||
public string target_uid;
|
||||
public int target_player_id;
|
||||
public string ability_id;
|
||||
public Slot slot;
|
||||
public int value;
|
||||
|
||||
public int score; //Score to determine which orders get cut and ignored
|
||||
public int sort; //Orders must be executed in sort order
|
||||
public bool valid; //If false, this order will be ignored
|
||||
|
||||
public AIAction() { }
|
||||
public AIAction(ushort t) { type = t; }
|
||||
|
||||
public string GetText(Game data)
|
||||
{
|
||||
string txt = GameAction.GetString(type);
|
||||
Card card = data.GetCard(card_uid);
|
||||
Card target = data.GetCard(target_uid);
|
||||
if (card != null)
|
||||
txt += " card " + card.card_id;
|
||||
if (target != null)
|
||||
txt += " target " + target.card_id;
|
||||
if (slot != Slot.None)
|
||||
txt += " slot " + slot.x + "-" + slot.p;
|
||||
if (ability_id != null)
|
||||
txt += " ability " + ability_id;
|
||||
if (value > 0)
|
||||
txt += " value " + value;
|
||||
return txt;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
type = 0;
|
||||
valid = false;
|
||||
card_uid = null;
|
||||
target_uid = null;
|
||||
ability_id = null;
|
||||
target_player_id = -1;
|
||||
slot = Slot.None;
|
||||
value = -1;
|
||||
score = 0;
|
||||
sort = 0;
|
||||
}
|
||||
|
||||
public static AIAction None { get { AIAction a = new AIAction(); a.type = 0; return a; } }
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/AI/AILogic.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/AI/AILogic.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ab13916fa774b3419a43439fc50ec1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/TcgEngine/Scripts/AI/AIPlayer.cs
Normal file
48
Assets/TcgEngine/Scripts/AI/AIPlayer.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// AI player base class, other AI inherit from this
|
||||
/// </summary>
|
||||
|
||||
public abstract class AIPlayer
|
||||
{
|
||||
public int player_id;
|
||||
public int ai_level;
|
||||
|
||||
protected GameLogic gameplay;
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
//Script called by game server to update AI
|
||||
//Override this to let the AI play
|
||||
}
|
||||
|
||||
public bool CanPlay()
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
bool can_play = game_data.IsPlayerTurn(player) || game_data.IsPlayerMulliganTurn(player);
|
||||
return can_play && !gameplay.IsResolving();
|
||||
}
|
||||
|
||||
public static AIPlayer Create(AIType type, GameLogic gameplay, int id, int level = 0)
|
||||
{
|
||||
if (type == AIType.Random)
|
||||
return new AIPlayerRandom(gameplay, id, level);
|
||||
if (type == AIType.MiniMax)
|
||||
return new AIPlayerMM(gameplay, id, level);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AIType
|
||||
{
|
||||
Random = 0, //Dumb AI that just do random moves, useful for testing cards without getting destroyed
|
||||
MiniMax = 10, //Stronger AI using Minimax algo with alpha-beta pruning
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/AI/AIPlayer.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/AI/AIPlayer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0354b727a964c654787c7597d4091569
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
287
Assets/TcgEngine/Scripts/AI/AIPlayerMM.cs
Normal file
287
Assets/TcgEngine/Scripts/AI/AIPlayerMM.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// AI player using the MinMax AI algorithm
|
||||
/// </summary>
|
||||
|
||||
public class AIPlayerMM : AIPlayer
|
||||
{
|
||||
private AILogic ai_logic;
|
||||
|
||||
private bool is_playing = false;
|
||||
|
||||
public AIPlayerMM(GameLogic gameplay, int id, int level)
|
||||
{
|
||||
this.gameplay = gameplay;
|
||||
player_id = id;
|
||||
ai_level = Mathf.Clamp(level, 1, 10);
|
||||
ai_logic = AILogic.Create(id, ai_level);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
|
||||
if (!is_playing && game_data.IsPlayerTurn(player))
|
||||
{
|
||||
is_playing = true;
|
||||
TimeTool.StartCoroutine(AiTurn());
|
||||
}
|
||||
|
||||
if (!is_playing && game_data.IsPlayerMulliganTurn(player))
|
||||
{
|
||||
SkipMulligan();
|
||||
}
|
||||
|
||||
if (!game_data.IsPlayerTurn(player) && ai_logic.IsRunning())
|
||||
Stop();
|
||||
}
|
||||
|
||||
private IEnumerator AiTurn()
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
ai_logic.RunAI(game_data);
|
||||
|
||||
while (ai_logic.IsRunning())
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
|
||||
AIAction best = ai_logic.GetBestAction();
|
||||
|
||||
if (best != null)
|
||||
{
|
||||
Debug.Log("Execute AI Action: " + best.GetText(game_data) + "\n" + ai_logic.GetNodePath());
|
||||
//foreach (NodeState node in ai_logic.GetFirst().childs)
|
||||
// Debug.Log(ai_logic.GetNodePath(node));
|
||||
|
||||
ExecuteAction(best);
|
||||
}
|
||||
|
||||
ai_logic.ClearMemory();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
is_playing = false;
|
||||
}
|
||||
|
||||
private void Stop()
|
||||
{
|
||||
ai_logic.Stop();
|
||||
is_playing = false;
|
||||
}
|
||||
|
||||
//----------
|
||||
|
||||
private void ExecuteAction(AIAction action)
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
if (action.type == GameAction.PlayCard)
|
||||
{
|
||||
PlayCard(action.card_uid, action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.Attack)
|
||||
{
|
||||
AttackCard(action.card_uid, action.target_uid);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.AttackPlayer)
|
||||
{
|
||||
AttackPlayer(action.card_uid, action.target_player_id);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.Move)
|
||||
{
|
||||
MoveCard(action.card_uid, action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.CastAbility)
|
||||
{
|
||||
CastAbility(action.card_uid, action.ability_id);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectCard)
|
||||
{
|
||||
SelectCard(action.target_uid);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectPlayer)
|
||||
{
|
||||
SelectPlayer(action.target_player_id);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectSlot)
|
||||
{
|
||||
SelectSlot(action.slot);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectChoice)
|
||||
{
|
||||
SelectChoice(action.value);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectCost)
|
||||
{
|
||||
SelectCost(action.value);
|
||||
}
|
||||
|
||||
if (action.type == GameAction.SelectMulligan)
|
||||
{
|
||||
SkipMulligan();
|
||||
}
|
||||
|
||||
if (action.type == GameAction.CancelSelect)
|
||||
{
|
||||
CancelSelect();
|
||||
}
|
||||
|
||||
if (action.type == GameAction.EndTurn)
|
||||
{
|
||||
EndTurn();
|
||||
}
|
||||
|
||||
if (action.type == GameAction.Resign)
|
||||
{
|
||||
Resign();
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCard(string card_uid, Slot slot)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card card = game_data.GetCard(card_uid);
|
||||
if (card != null)
|
||||
{
|
||||
gameplay.PlayCard(card, slot);
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveCard(string card_uid, Slot slot)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card card = game_data.GetCard(card_uid);
|
||||
if (card != null)
|
||||
{
|
||||
gameplay.MoveCard(card, slot);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttackCard(string attacker_uid, string target_uid)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card card = game_data.GetCard(attacker_uid);
|
||||
Card target = game_data.GetCard(target_uid);
|
||||
if (card != null && target != null)
|
||||
{
|
||||
gameplay.AttackTarget(card, target);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttackPlayer(string attacker_uid, int target_player_id)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card card = game_data.GetCard(attacker_uid);
|
||||
if (card != null)
|
||||
{
|
||||
Player oplayer = game_data.GetPlayer(target_player_id);
|
||||
gameplay.AttackPlayer(card, oplayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void CastAbility(string caster_uid, string ability_id)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card caster = game_data.GetCard(caster_uid);
|
||||
AbilityData iability = AbilityData.Get(ability_id);
|
||||
if (caster != null && iability != null)
|
||||
{
|
||||
gameplay.CastAbility(caster, iability);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectCard(string target_uid)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Card target = game_data.GetCard(target_uid);
|
||||
if (target != null)
|
||||
{
|
||||
gameplay.SelectCard(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectPlayer(int tplayer_id)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player target = game_data.GetPlayer(tplayer_id);
|
||||
if (target != null)
|
||||
{
|
||||
gameplay.SelectPlayer(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectSlot(Slot slot)
|
||||
{
|
||||
if (slot != Slot.None)
|
||||
{
|
||||
gameplay.SelectSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectChoice(int choice)
|
||||
{
|
||||
gameplay.SelectChoice(choice);
|
||||
}
|
||||
|
||||
private void SelectCost(int cost)
|
||||
{
|
||||
gameplay.SelectCost(cost);
|
||||
}
|
||||
|
||||
private void CancelSelect()
|
||||
{
|
||||
if (CanPlay())
|
||||
{
|
||||
gameplay.CancelSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void SkipMulligan()
|
||||
{
|
||||
string[] cards = new string[0]; //Don't mulligan
|
||||
SelectMulligan(cards);
|
||||
}
|
||||
|
||||
private void SelectMulligan(string[] cards)
|
||||
{
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
gameplay.Mulligan(player, cards);
|
||||
}
|
||||
|
||||
private void EndTurn()
|
||||
{
|
||||
if (CanPlay())
|
||||
{
|
||||
gameplay.EndTurn();
|
||||
}
|
||||
}
|
||||
|
||||
private void Resign()
|
||||
{
|
||||
int other = player_id == 0 ? 1 : 0;
|
||||
gameplay.EndGame(other);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/AI/AIPlayerMM.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/AI/AIPlayerMM.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee38d0542562f9543b095360388a2867
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
340
Assets/TcgEngine/Scripts/AI/AIPlayerRandom.cs
Normal file
340
Assets/TcgEngine/Scripts/AI/AIPlayerRandom.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Client;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// AI player making completely random decisions, really bad AI but useful for testing
|
||||
/// </summary>
|
||||
|
||||
public class AIPlayerRandom : AIPlayer
|
||||
{
|
||||
private bool is_playing = false;
|
||||
private bool is_selecting = false;
|
||||
|
||||
private System.Random rand = new System.Random();
|
||||
|
||||
public AIPlayerRandom(GameLogic gameplay, int id, int level)
|
||||
{
|
||||
this.gameplay = gameplay;
|
||||
player_id = id;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
|
||||
if (game_data.IsPlayerTurn(player) && !gameplay.IsResolving())
|
||||
{
|
||||
if(!is_playing && game_data.selector == SelectorType.None && game_data.current_player == player_id)
|
||||
{
|
||||
is_playing = true;
|
||||
TimeTool.StartCoroutine(AiTurn());
|
||||
}
|
||||
|
||||
if (!is_selecting && game_data.selector != SelectorType.None && game_data.selector_player_id == player_id)
|
||||
{
|
||||
if (game_data.selector == SelectorType.SelectTarget)
|
||||
{
|
||||
//AI select target
|
||||
is_selecting = true;
|
||||
TimeTool.StartCoroutine(AiSelectTarget());
|
||||
}
|
||||
|
||||
if (game_data.selector == SelectorType.SelectorCard)
|
||||
{
|
||||
//AI select target
|
||||
is_selecting = true;
|
||||
TimeTool.StartCoroutine(AiSelectCard());
|
||||
}
|
||||
|
||||
if (game_data.selector == SelectorType.SelectorChoice)
|
||||
{
|
||||
//AI select target
|
||||
is_selecting = true;
|
||||
TimeTool.StartCoroutine(AiSelectChoice());
|
||||
}
|
||||
|
||||
if (game_data.selector == SelectorType.SelectorCost)
|
||||
{
|
||||
//AI select target
|
||||
is_selecting = true;
|
||||
TimeTool.StartCoroutine(AiSelectCost());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!is_selecting && game_data.IsPlayerMulliganTurn(player))
|
||||
{
|
||||
is_selecting = true;
|
||||
TimeTool.StartCoroutine(AiSelectMulligan());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator AiTurn()
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
|
||||
PlayCard();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
PlayCard();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
PlayCard();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
Attack();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
Attack();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
AttackPlayer();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
EndTurn();
|
||||
|
||||
is_playing = false;
|
||||
}
|
||||
|
||||
private IEnumerator AiSelectCard()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
SelectCard();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
CancelSelect();
|
||||
is_selecting = false;
|
||||
}
|
||||
|
||||
private IEnumerator AiSelectTarget()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
SelectTarget();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
CancelSelect();
|
||||
is_selecting = false;
|
||||
}
|
||||
|
||||
private IEnumerator AiSelectChoice()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
SelectChoice();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
CancelSelect();
|
||||
is_selecting = false;
|
||||
}
|
||||
|
||||
private IEnumerator AiSelectCost()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
SelectCost();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
CancelSelect();
|
||||
is_selecting = false;
|
||||
}
|
||||
|
||||
private IEnumerator AiSelectMulligan()
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
SelectMulligan();
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
is_selecting = false;
|
||||
}
|
||||
|
||||
//----------
|
||||
|
||||
public void PlayCard()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
if (player.cards_hand.Count > 0 && game_data.IsPlayerActionTurn(player))
|
||||
{
|
||||
Card random = player.GetRandomCard(player.cards_hand, rand);
|
||||
Slot slot = player.GetRandomEmptySlot(rand);
|
||||
|
||||
if (random != null && random.CardData.IsRequireTargetSpell())
|
||||
slot = game_data.GetRandomSlot(rand); //Spell can target any slot, not just your side
|
||||
|
||||
if(random != null && random.CardData.IsEquipment())
|
||||
slot = player.GetRandomOccupiedSlot(rand);
|
||||
|
||||
if (random != null)
|
||||
gameplay.PlayCard(random, slot);
|
||||
}
|
||||
}
|
||||
|
||||
public void Attack()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
if (player.cards_board.Count > 0 && game_data.IsPlayerActionTurn(player))
|
||||
{
|
||||
Card random = player.GetRandomCard(player.cards_board, rand);
|
||||
Card rtarget = game_data.GetRandomBoardCard(rand);
|
||||
if (random != null && rtarget != null)
|
||||
gameplay.AttackTarget(random, rtarget);
|
||||
}
|
||||
}
|
||||
|
||||
public void AttackPlayer()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
Player oplayer = game_data.GetRandomPlayer(rand);
|
||||
if (player.cards_board.Count > 0 && game_data.IsPlayerActionTurn(player))
|
||||
{
|
||||
Card random = player.GetRandomCard(player.cards_board, rand);
|
||||
if (random != null && oplayer != null && oplayer != player)
|
||||
gameplay.AttackPlayer(random, oplayer);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectCard()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||||
Card caster = game_data.GetCard(game_data.selector_caster_uid);
|
||||
if (player != null && ability != null && caster != null)
|
||||
{
|
||||
List<Card> card_list = ability.GetCardTargets(game_data, caster);
|
||||
if (card_list.Count > 0)
|
||||
{
|
||||
Card card = card_list[rand.Next(0, card_list.Count)];
|
||||
gameplay.SelectCard(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectTarget()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
if (game_data.selector != SelectorType.None)
|
||||
{
|
||||
int target_player = player_id;
|
||||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||||
if (ability != null && ability.target == AbilityTarget.SelectTarget)
|
||||
target_player = (player_id == 0 ? 1 : 0);
|
||||
|
||||
Player tplayer = game_data.GetPlayer(target_player);
|
||||
if (tplayer.cards_board.Count > 0)
|
||||
{
|
||||
Card random = tplayer.GetRandomCard(tplayer.cards_board, rand);
|
||||
if (random != null)
|
||||
gameplay.SelectCard(random);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectChoice()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
if (game_data.selector != SelectorType.None)
|
||||
{
|
||||
AbilityData ability = AbilityData.Get(game_data.selector_ability_id);
|
||||
if (ability != null && ability.chain_abilities.Length > 0)
|
||||
{
|
||||
int choice = rand.Next(0, ability.chain_abilities.Length);
|
||||
gameplay.SelectChoice(choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectCost()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
if (game_data.selector != SelectorType.None)
|
||||
{
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
Card card = game_data.GetCard(game_data.selector_caster_uid);
|
||||
if (player != null && card != null)
|
||||
{
|
||||
int max = Mathf.Clamp(player.mana, 0, 9);
|
||||
int choice = rand.Next(0, max + 1);
|
||||
gameplay.SelectCost(choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelSelect()
|
||||
{
|
||||
if (CanPlay())
|
||||
{
|
||||
gameplay.CancelSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectMulligan()
|
||||
{
|
||||
if (!CanPlay())
|
||||
return;
|
||||
|
||||
Game game_data = gameplay.GetGameData();
|
||||
if (game_data.phase == GamePhase.Mulligan)
|
||||
{
|
||||
Player player = game_data.GetPlayer(player_id);
|
||||
string[] cards = new string[0]; //Don't mulligan
|
||||
gameplay.Mulligan(player, cards);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndTurn()
|
||||
{
|
||||
if (CanPlay())
|
||||
{
|
||||
gameplay.EndTurn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/AI/AIPlayerRandom.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/AI/AIPlayerRandom.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f327588bb7c02141baa6fbeb0081e84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TcgEngine/Scripts/Api.meta
Normal file
8
Assets/TcgEngine/Scripts/Api.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4288928c4a58cb946b7eff592956d5d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
439
Assets/TcgEngine/Scripts/Api/ApiClient.cs
Normal file
439
Assets/TcgEngine/Scripts/Api/ApiClient.cs
Normal file
@@ -0,0 +1,439 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// API client communicates with the NodeJS web api
|
||||
/// Can send requests and receive responses
|
||||
/// </summary>
|
||||
|
||||
public class ApiClient : MonoBehaviour
|
||||
{
|
||||
public bool is_server;
|
||||
|
||||
public UnityAction<RegisterResponse> onRegister; //Triggered after register, even if failed
|
||||
public UnityAction<LoginResponse> onLogin; //Triggered after login, even if failed
|
||||
public UnityAction<LoginResponse> onRefresh; //Triggered after login refresh, even if failed
|
||||
public UnityAction onLogout; //Triggered after logout
|
||||
|
||||
private string user_id = "";
|
||||
private string username = "";
|
||||
private string access_token = "";
|
||||
private string refresh_token = "";
|
||||
private string api_version = "";
|
||||
|
||||
private bool logged_in = false;
|
||||
private bool expired = false;
|
||||
|
||||
private UserData udata = null;
|
||||
|
||||
private int sending = 0;
|
||||
private string last_error = "";
|
||||
private float refresh_timer = 0f;
|
||||
private float online_timer = 0f;
|
||||
private long expiration_timestamp = 0;
|
||||
|
||||
private const float online_duration = 60f * 5f; //5 min
|
||||
|
||||
private static ApiClient instance;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
//API client should be on OnDestroyOnLoad
|
||||
//dont assign here if already assigned cause new one will be destroyed in TheNetwork Awake
|
||||
if(instance == null)
|
||||
instance = this;
|
||||
|
||||
LoadTokens();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
//Refresh access token or online status
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void LoadTokens()
|
||||
{
|
||||
if (!is_server && string.IsNullOrEmpty(user_id))
|
||||
{
|
||||
access_token = PlayerPrefs.GetString("tcg_access_token");
|
||||
refresh_token = PlayerPrefs.GetString("tcg_refresh_token");
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveTokens()
|
||||
{
|
||||
if (!is_server)
|
||||
{
|
||||
PlayerPrefs.SetString("tcg_access_token", access_token);
|
||||
PlayerPrefs.SetString("tcg_refresh_token", refresh_token);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Refresh()
|
||||
{
|
||||
if (!logged_in)
|
||||
return;
|
||||
|
||||
//Check expiration
|
||||
if (!expired)
|
||||
{
|
||||
long current = GetTimestamp();
|
||||
expired = current > (expiration_timestamp - 10);
|
||||
}
|
||||
|
||||
//Refresh access token when expired
|
||||
refresh_timer += Time.deltaTime;
|
||||
if (expired && refresh_timer > 5f)
|
||||
{
|
||||
refresh_timer = 0f;
|
||||
await RefreshLogin(); //Try to relogin
|
||||
}
|
||||
|
||||
//Refresh online status
|
||||
online_timer += Time.deltaTime;
|
||||
if (!expired && online_timer > online_duration)
|
||||
{
|
||||
online_timer = 0f;
|
||||
await KeepOnline();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RegisterResponse> Register(string email, string user, string password)
|
||||
{
|
||||
RegisterRequest data = new RegisterRequest();
|
||||
data.email = email;
|
||||
data.username = user;
|
||||
data.password = password;
|
||||
data.avatar = "";
|
||||
return await Register(data);
|
||||
}
|
||||
|
||||
public async Task<RegisterResponse> Register(RegisterRequest data)
|
||||
{
|
||||
Logout(); //Disconnect
|
||||
|
||||
string url = ServerURL + "/users/register";
|
||||
string json = ApiTool.ToJson(data);
|
||||
|
||||
WebResponse res = await SendPostRequest(url, json);
|
||||
RegisterResponse regist_res = ApiTool.JsonToObject<RegisterResponse>(res.data);
|
||||
regist_res.success = res.success;
|
||||
regist_res.error = res.error;
|
||||
onRegister?.Invoke(regist_res);
|
||||
return regist_res;
|
||||
}
|
||||
|
||||
public async Task<LoginResponse> Login(string user, string password)
|
||||
{
|
||||
Logout(); //Disconnect
|
||||
|
||||
LoginRequest data = new LoginRequest();
|
||||
data.password = password;
|
||||
|
||||
if (user.Contains("@"))
|
||||
data.email = user;
|
||||
else
|
||||
data.username = user;
|
||||
|
||||
string url = ServerURL + "/auth";
|
||||
string json = ApiTool.ToJson(data);
|
||||
|
||||
WebResponse res = await SendPostRequest(url, json);
|
||||
LoginResponse login_res = GetLoginRes(res);
|
||||
AfterLogin(login_res);
|
||||
|
||||
onLogin?.Invoke(login_res);
|
||||
return login_res;
|
||||
}
|
||||
|
||||
public async Task<LoginResponse> RefreshLogin()
|
||||
{
|
||||
string url = ServerURL + "/auth/refresh";
|
||||
AutoLoginRequest data = new AutoLoginRequest();
|
||||
data.refresh_token = refresh_token;
|
||||
string json = ApiTool.ToJson(data);
|
||||
|
||||
WebResponse res = await SendPostRequest(url, json);
|
||||
LoginResponse login_res = GetLoginRes(res);
|
||||
AfterLogin(login_res);
|
||||
|
||||
onRefresh?.Invoke(login_res);
|
||||
return login_res;
|
||||
}
|
||||
|
||||
private LoginResponse GetLoginRes(WebResponse res)
|
||||
{
|
||||
LoginResponse login_res = ApiTool.JsonToObject<LoginResponse>(res.data);
|
||||
login_res.success = res.success;
|
||||
login_res.error = res.error;
|
||||
|
||||
//Uncomment to force having same client version as api
|
||||
/*if (!is_server && !IsVersionValid())
|
||||
{
|
||||
login_res.error = "Invalid Version";
|
||||
login_res.success = false;
|
||||
}*/
|
||||
|
||||
return login_res;
|
||||
}
|
||||
|
||||
private void AfterLogin(LoginResponse login_res)
|
||||
{
|
||||
last_error = login_res.error;
|
||||
|
||||
if (login_res.success)
|
||||
{
|
||||
user_id = login_res.id;
|
||||
username = login_res.username;
|
||||
access_token = login_res.access_token;
|
||||
refresh_token = login_res.refresh_token;
|
||||
api_version = login_res.version;
|
||||
expiration_timestamp = GetTimestamp() + login_res.duration;
|
||||
refresh_timer = 0f;
|
||||
online_timer = 0f;
|
||||
logged_in = true;
|
||||
expired = false;
|
||||
SaveTokens();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UserData> LoadUserData()
|
||||
{
|
||||
udata = await LoadUserData(this.username);
|
||||
return udata;
|
||||
}
|
||||
|
||||
public async Task<UserData> LoadUserData(string username)
|
||||
{
|
||||
if (!IsConnected())
|
||||
return null;
|
||||
|
||||
string url = ServerURL + "/users/" + username;
|
||||
WebResponse res = await SendGetRequest(url);
|
||||
|
||||
UserData udata = null;
|
||||
if (res.success)
|
||||
{
|
||||
udata = ApiTool.JsonToObject<UserData>(res.data);
|
||||
}
|
||||
|
||||
return udata;
|
||||
}
|
||||
|
||||
public async Task<bool> KeepOnline()
|
||||
{
|
||||
if (!IsConnected())
|
||||
return false;
|
||||
|
||||
//Keep player online
|
||||
string url = ServerURL + "/auth/keep";
|
||||
WebResponse res = await SendGetRequest(url);
|
||||
expired = !res.success;
|
||||
return res.success;
|
||||
}
|
||||
|
||||
public async Task<bool> Validate()
|
||||
{
|
||||
if (!IsConnected())
|
||||
return false;
|
||||
|
||||
//Check if connection is still valid
|
||||
string url = ServerURL + "/auth/validate";
|
||||
WebResponse res = await SendGetRequest(url);
|
||||
expired = !res.success;
|
||||
return res.success;
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
user_id = "";
|
||||
username = "";
|
||||
access_token = "";
|
||||
refresh_token = "";
|
||||
api_version = "";
|
||||
last_error = "";
|
||||
logged_in = false;
|
||||
onLogout?.Invoke();
|
||||
SaveTokens();
|
||||
}
|
||||
|
||||
public async void CreateMatch(Game game_data)
|
||||
{
|
||||
if (game_data.settings.game_type != GameType.Multiplayer)
|
||||
return;
|
||||
|
||||
AddMatchRequest req = new AddMatchRequest();
|
||||
req.players = new string[2];
|
||||
req.players[0] = game_data.players[0].username;
|
||||
req.players[1] = game_data.players[1].username;
|
||||
req.tid = game_data.game_uid;
|
||||
req.ranked = game_data.settings.IsRanked();
|
||||
req.mode = game_data.settings.GetGameModeId();
|
||||
|
||||
string url = ServerURL + "/matches/add";
|
||||
string json = ApiTool.ToJson(req);
|
||||
WebResponse res = await SendPostRequest(url, json);
|
||||
Debug.Log("Match Started! " + res.success);
|
||||
}
|
||||
|
||||
public async void EndMatch(Game game_data, int winner_id)
|
||||
{
|
||||
if (game_data.settings.game_type != GameType.Multiplayer)
|
||||
return;
|
||||
|
||||
Player player = game_data.GetPlayer(winner_id);
|
||||
CompleteMatchRequest req = new CompleteMatchRequest();
|
||||
req.tid = game_data.game_uid;
|
||||
req.winner = player != null ? player.username : "";
|
||||
|
||||
string url = ServerURL + "/matches/complete";
|
||||
string json = ApiTool.ToJson(req);
|
||||
WebResponse res = await SendPostRequest(url, json);
|
||||
Debug.Log("Match Completed! " + res.success);
|
||||
}
|
||||
|
||||
public async Task<string> SendGetVersion()
|
||||
{
|
||||
string url = ServerURL + "/version";
|
||||
WebResponse res = await SendGetRequest(url);
|
||||
|
||||
if (res.success)
|
||||
{
|
||||
VersionResponse version_data = ApiTool.JsonToObject<VersionResponse>(res.data);
|
||||
api_version = version_data.version;
|
||||
return api_version;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<WebResponse> SendGetRequest(string url)
|
||||
{
|
||||
return await SendRequest(url, WebRequest.METHOD_GET);
|
||||
}
|
||||
|
||||
public async Task<WebResponse> SendPostRequest(string url, string json_data)
|
||||
{
|
||||
return await SendRequest(url, WebRequest.METHOD_POST, json_data);
|
||||
}
|
||||
|
||||
public async Task<WebResponse> SendRequest(string url, string method, string json_data = "")
|
||||
{
|
||||
UnityWebRequest request = WebRequest.Create(url, method, json_data, access_token);
|
||||
return await SendRequest(request);
|
||||
}
|
||||
|
||||
private async Task<WebResponse> SendRequest(UnityWebRequest request)
|
||||
{
|
||||
int wait = 0;
|
||||
int wait_max = request.timeout * 1000;
|
||||
request.timeout += 1; //Add offset to make sure it aborts first
|
||||
sending++;
|
||||
|
||||
var async_oper = request.SendWebRequest();
|
||||
while (!async_oper.isDone)
|
||||
{
|
||||
await TimeTool.Delay(200);
|
||||
wait += 200;
|
||||
if (wait >= wait_max)
|
||||
request.Abort(); //Abort to avoid unity errors on timeout
|
||||
}
|
||||
|
||||
WebResponse response = WebRequest.GetResponse(request);
|
||||
response.error = GetError(response);
|
||||
last_error = response.error;
|
||||
request.Dispose();
|
||||
sending--;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private string GetError(WebResponse res)
|
||||
{
|
||||
if (res.success)
|
||||
return "";
|
||||
|
||||
ErrorResponse err = ApiTool.JsonToObject<ErrorResponse>(res.data);
|
||||
if (err != null)
|
||||
return err.error;
|
||||
else
|
||||
return res.error;
|
||||
}
|
||||
|
||||
public bool IsConnected()
|
||||
{
|
||||
return logged_in && !expired;
|
||||
}
|
||||
|
||||
public bool IsLoggedIn()
|
||||
{
|
||||
return logged_in;
|
||||
}
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
return expired;
|
||||
}
|
||||
|
||||
public bool IsBusy()
|
||||
{
|
||||
return sending > 0;
|
||||
}
|
||||
|
||||
public long GetTimestamp()
|
||||
{
|
||||
return System.DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
public string GetLastRequest()
|
||||
{
|
||||
return last_error;
|
||||
}
|
||||
|
||||
public string GetLastError()
|
||||
{
|
||||
return last_error;
|
||||
}
|
||||
|
||||
//Use this function if you want to prevent players to login with an outdated client
|
||||
//Call it inside the login and loginrefresh functions after the api_version is set and return error if invalid
|
||||
public bool IsVersionValid()
|
||||
{
|
||||
return ClientVersion == ServerVersion;
|
||||
}
|
||||
|
||||
public UserData UserData { get { return udata; } }
|
||||
|
||||
public string UserID { get { return user_id; } set { user_id = value; } }
|
||||
public string Username { get { return username; } set { username = value; } }
|
||||
public string AccessToken { get { return access_token; } set { access_token = value; } }
|
||||
public string RefreshToken { get { return refresh_token; } set { refresh_token = value; } }
|
||||
|
||||
public string ServerVersion { get { return api_version; } }
|
||||
public string ClientVersion { get { return Application.version; } }
|
||||
|
||||
public static string ServerURL
|
||||
{
|
||||
get
|
||||
{
|
||||
NetworkData data = NetworkData.Get();
|
||||
string protocol = data.api_https ? "https://" : "http://";
|
||||
return protocol + data.api_url;
|
||||
}
|
||||
}
|
||||
|
||||
public static ApiClient Get()
|
||||
{
|
||||
if (instance == null)
|
||||
instance = FindObjectOfType<ApiClient>();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Api/ApiClient.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Api/ApiClient.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbcd533fe8d4d0a4aaa364f2ae5e4077
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
211
Assets/TcgEngine/Scripts/Api/ApiMsg.cs
Normal file
211
Assets/TcgEngine/Scripts/Api/ApiMsg.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// List of data structure used by the ApiClient requests and responses
|
||||
/// </summary>
|
||||
|
||||
//--------- Requests -----------
|
||||
|
||||
[Serializable]
|
||||
public struct LoginRequest
|
||||
{
|
||||
public string email;
|
||||
public string username;
|
||||
public string password;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AutoLoginRequest
|
||||
{
|
||||
public string refresh_token;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct RegisterRequest
|
||||
{
|
||||
public string email;
|
||||
public string username;
|
||||
public string password;
|
||||
public string avatar;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct EditUserRequest
|
||||
{
|
||||
public string avatar;
|
||||
public string cardback;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct EditEmailRequest
|
||||
{
|
||||
public string email;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct EditPasswordRequest
|
||||
{
|
||||
public string password_previous;
|
||||
public string password_new;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct FriendAddRequest
|
||||
{
|
||||
public string username;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AddMatchRequest
|
||||
{
|
||||
public string tid;
|
||||
public string[] players;
|
||||
public string mode;
|
||||
public bool ranked;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CompleteMatchRequest
|
||||
{
|
||||
public string tid;
|
||||
public string winner;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct RewardGainRequest
|
||||
{
|
||||
public string reward;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BuyPackRequest
|
||||
{
|
||||
public string pack;
|
||||
public int quantity;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct BuyCardRequest
|
||||
{
|
||||
public string card;
|
||||
public string variant;
|
||||
public int quantity;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct SellDuplicateRequest
|
||||
{
|
||||
//public string variant;
|
||||
//public string rarity;
|
||||
public int keep;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct OpenPackRequest
|
||||
{
|
||||
public string pack;
|
||||
}
|
||||
|
||||
//--------- Response -----------
|
||||
|
||||
[Serializable]
|
||||
public struct VersionResponse
|
||||
{
|
||||
public string version;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct RegisterResponse
|
||||
{
|
||||
public string id;
|
||||
public string username;
|
||||
public string version;
|
||||
public bool success;
|
||||
public string error;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct LoginResponse
|
||||
{
|
||||
public string id;
|
||||
public string username;
|
||||
public string refresh_token;
|
||||
public string access_token;
|
||||
public int permission_level;
|
||||
public int validation_level;
|
||||
public int duration;
|
||||
public string version;
|
||||
public string error;
|
||||
public bool success;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct UserIdResponse
|
||||
{
|
||||
public string id;
|
||||
public string username;
|
||||
public string error;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct MatchResponse
|
||||
{
|
||||
public string tid;
|
||||
public string[] players;
|
||||
public DateTime start;
|
||||
public DateTime end;
|
||||
public string winner;
|
||||
public bool completed;
|
||||
public MatchDataResponse[] udata;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct MatchDataResponse
|
||||
{
|
||||
public string username;
|
||||
public int rank;
|
||||
public DeckData deck;
|
||||
public RewardResponse reward;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct RewardResponse
|
||||
{
|
||||
public string tid;
|
||||
public int coins;
|
||||
public int elo;
|
||||
public int xp;
|
||||
public string[] cards;
|
||||
public string[] decks;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct MarketResponse
|
||||
{
|
||||
public string seller;
|
||||
public string card;
|
||||
public int price;
|
||||
public int quantity;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct FriendResponse
|
||||
{
|
||||
public string username;
|
||||
public string server_time;
|
||||
public FriendData[] friends;
|
||||
public FriendData[] friends_requests;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct FriendData
|
||||
{
|
||||
public string username;
|
||||
public string avatar;
|
||||
public string last_online_time;
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Api/ApiMsg.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Api/ApiMsg.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d5b5a0543605d2459b2da189f0c814b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
54
Assets/TcgEngine/Scripts/Api/ApiTool.cs
Normal file
54
Assets/TcgEngine/Scripts/Api/ApiTool.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Useful tool static functions for the ApiClient
|
||||
/// </summary>
|
||||
|
||||
public class ApiTool : MonoBehaviour
|
||||
{
|
||||
// ----- Convertions ------
|
||||
|
||||
public static T JsonToObject<T>(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
T value = JsonUtility.FromJson<T>(json);
|
||||
return value;
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return (T)Activator.CreateInstance(typeof(T));
|
||||
}
|
||||
|
||||
public static T[] JsonToArray<T>(string json)
|
||||
{
|
||||
ListJson<T> list = new ListJson<T>();
|
||||
list.list = new T[0];
|
||||
try
|
||||
{
|
||||
string wrap_json = "{ \"list\": " + json + "}";
|
||||
list = JsonUtility.FromJson<ListJson<T>>(wrap_json);
|
||||
return list.list;
|
||||
}
|
||||
catch (Exception) { }
|
||||
return new T[0];
|
||||
}
|
||||
|
||||
public static string ToJson(object data)
|
||||
{
|
||||
return JsonUtility.ToJson(data);
|
||||
}
|
||||
|
||||
public static int ParseInt(string int_str, int default_val = 0)
|
||||
{
|
||||
bool success = int.TryParse(int_str, out int val);
|
||||
return success ? val : default_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Api/ApiTool.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Api/ApiTool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a80590b3916cb84fb73b14768bdf168
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
425
Assets/TcgEngine/Scripts/Api/UserData.cs
Normal file
425
Assets/TcgEngine/Scripts/Api/UserData.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Contain UserData retrieved from the web api database
|
||||
/// </summary>
|
||||
|
||||
|
||||
[System.Serializable]
|
||||
public class UserData
|
||||
{
|
||||
public string id;
|
||||
public string username;
|
||||
|
||||
public string email;
|
||||
public string avatar;
|
||||
public string cardback;
|
||||
public int permission_level = 1;
|
||||
public int validation_level = 1;
|
||||
|
||||
public int coins;
|
||||
public int xp;
|
||||
public int elo;
|
||||
|
||||
public int matches;
|
||||
public int victories;
|
||||
public int defeats;
|
||||
|
||||
public UserCardData[] cards;
|
||||
public UserCardData[] packs;
|
||||
public UserDeckData[] decks;
|
||||
public string[] rewards;
|
||||
public string[] avatars;
|
||||
public string[] cardbacks;
|
||||
public string[] friends;
|
||||
|
||||
public UserData()
|
||||
{
|
||||
cards = new UserCardData[0];
|
||||
packs = new UserCardData[0];
|
||||
decks = new UserDeckData[0];
|
||||
rewards = new string[0];
|
||||
avatars = new string[0];
|
||||
cardbacks = new string[0];
|
||||
friends = new string[0];
|
||||
permission_level = 1;
|
||||
coins = 10000;
|
||||
elo = 1000;
|
||||
}
|
||||
|
||||
public int GetLevel()
|
||||
{
|
||||
return Mathf.FloorToInt(xp / 1000) + 1;
|
||||
}
|
||||
|
||||
public string GetAvatar()
|
||||
{
|
||||
if (avatar != null)
|
||||
return avatar;
|
||||
return "";
|
||||
}
|
||||
|
||||
public string GetCardback()
|
||||
{
|
||||
if (cardback != null)
|
||||
return cardback;
|
||||
return "";
|
||||
}
|
||||
|
||||
public void SetDeck(UserDeckData deck)
|
||||
{
|
||||
for(int i=0; i<decks.Length; i++)
|
||||
{
|
||||
if (decks[i].tid == deck.tid)
|
||||
{
|
||||
decks[i] = deck;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Not found
|
||||
List<UserDeckData> ldecks = new List<UserDeckData>(decks);
|
||||
ldecks.Add(deck);
|
||||
this.decks = ldecks.ToArray();
|
||||
}
|
||||
|
||||
public UserDeckData GetDeck(string tid)
|
||||
{
|
||||
foreach (UserDeckData deck in decks)
|
||||
{
|
||||
if (deck.tid == tid)
|
||||
return deck;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public UserCardData GetCard(string tid, string variant)
|
||||
{
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (card.tid == tid && card.variant == variant)
|
||||
return card;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int GetCardQuantity(CardData card, VariantData variant)
|
||||
{
|
||||
return GetCardQuantity(card.id, variant.id, variant.is_default);
|
||||
}
|
||||
|
||||
public int GetCardQuantity(string tid, string variant, bool default_variant = false)
|
||||
{
|
||||
if (cards == null)
|
||||
return 0;
|
||||
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (card.tid == tid && card.variant == variant)
|
||||
return card.quantity;
|
||||
if (card.tid == tid && card.variant == "" && default_variant)
|
||||
return card.quantity;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public UserCardData GetPack(string tid)
|
||||
{
|
||||
foreach (UserCardData pack in packs)
|
||||
{
|
||||
if (pack.tid == tid)
|
||||
return pack;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int GetPackQuantity(string tid)
|
||||
{
|
||||
if (packs == null)
|
||||
return 0;
|
||||
|
||||
foreach (UserCardData pack in packs)
|
||||
{
|
||||
if (pack.tid == tid)
|
||||
return pack.quantity;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int CountUniqueCards()
|
||||
{
|
||||
if (cards == null)
|
||||
return 0;
|
||||
|
||||
HashSet<string> unique_cards = new HashSet<string>();
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (!unique_cards.Contains(card.tid))
|
||||
unique_cards.Add(card.tid);
|
||||
}
|
||||
return unique_cards.Count;
|
||||
}
|
||||
|
||||
public int CountCardType(VariantData variant)
|
||||
{
|
||||
int value = 0;
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (card.variant == variant.id)
|
||||
value += 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public bool HasDeckCards(UserDeckData deck)
|
||||
{
|
||||
foreach (UserCardData card in deck.cards)
|
||||
{
|
||||
bool default_variant = true; //Count "" variant as valid for compatibilty with older vers
|
||||
if (GetCardQuantity(card.tid, card.variant, default_variant) < card.quantity)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsDeckValid(UserDeckData deck)
|
||||
{
|
||||
if (Authenticator.Get().IsApi())
|
||||
return HasDeckCards(deck) && deck.IsValid();
|
||||
return deck.IsValid();
|
||||
}
|
||||
|
||||
public void AddDeck(UserDeckData deck)
|
||||
{
|
||||
List<UserDeckData> udecks = new List<UserDeckData>(decks);
|
||||
udecks.Add(deck);
|
||||
decks = udecks.ToArray();
|
||||
|
||||
foreach (UserCardData card in deck.cards)
|
||||
{
|
||||
AddCard(card.tid, card.variant, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPack(string tid, int quantity)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (UserCardData pack in packs)
|
||||
{
|
||||
if (pack.tid == tid)
|
||||
{
|
||||
found = true;
|
||||
pack.quantity += quantity;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
UserCardData npack = new UserCardData();
|
||||
npack.tid = tid;
|
||||
npack.quantity = quantity;
|
||||
List<UserCardData> apacks = new List<UserCardData>(packs);
|
||||
apacks.Add(npack);
|
||||
packs = apacks.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCard(string tid, string variant, int quantity)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (card.tid == tid && card.variant == variant)
|
||||
{
|
||||
found = true;
|
||||
card.quantity += quantity;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
UserCardData ncard = new UserCardData();
|
||||
ncard.tid = tid;
|
||||
ncard.variant = variant;
|
||||
ncard.quantity = quantity;
|
||||
List<UserCardData> acards = new List<UserCardData>(cards);
|
||||
acards.Add(ncard);
|
||||
cards = acards.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddReward(string tid)
|
||||
{
|
||||
if (!HasReward(tid))
|
||||
{
|
||||
List<string> arewards = new List<string>(rewards);
|
||||
arewards.Add(tid);
|
||||
rewards = arewards.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCard(string card_tid, string variant, int quantity = 1)
|
||||
{
|
||||
foreach (UserCardData card in cards)
|
||||
{
|
||||
if (card.tid == card_tid && card.variant == variant && card.quantity >= quantity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasPack(string pack_tid, int quantity=1)
|
||||
{
|
||||
foreach (UserCardData pack in packs)
|
||||
{
|
||||
if (pack.tid == pack_tid && pack.quantity >= quantity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasAvatar(string avatar_tid)
|
||||
{
|
||||
return avatars.Contains(avatar_tid);
|
||||
}
|
||||
|
||||
public bool HasCardback(string cardback_tid)
|
||||
{
|
||||
return cardbacks.Contains(cardback_tid);
|
||||
}
|
||||
|
||||
public bool HasReward(string reward_id)
|
||||
{
|
||||
foreach (string reward in rewards)
|
||||
{
|
||||
if (reward == reward_id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetCoinsString()
|
||||
{
|
||||
return coins.ToString();
|
||||
}
|
||||
|
||||
public bool HasFriend(string username)
|
||||
{
|
||||
List<string> flist = new List<string>(friends);
|
||||
return flist.Contains(username);
|
||||
}
|
||||
|
||||
public void AddFriend(string username)
|
||||
{
|
||||
List<string> flist = new List<string>(friends);
|
||||
if (!flist.Contains(username))
|
||||
flist.Add(username);
|
||||
friends = flist.ToArray();
|
||||
}
|
||||
|
||||
public void RemoveFriend(string username)
|
||||
{
|
||||
List<string> flist = new List<string>(friends);
|
||||
if (flist.Contains(username))
|
||||
flist.Remove(username);
|
||||
friends = flist.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class UserDeckData : INetworkSerializable
|
||||
{
|
||||
public string tid;
|
||||
public string title;
|
||||
public UserCardData hero;
|
||||
public UserCardData[] cards;
|
||||
|
||||
public UserDeckData() {}
|
||||
|
||||
public UserDeckData(string tid, string title)
|
||||
{
|
||||
this.tid = tid;
|
||||
this.title = title;
|
||||
hero = new UserCardData();
|
||||
cards = new UserCardData[0];
|
||||
}
|
||||
|
||||
public UserDeckData(DeckData deck)
|
||||
{
|
||||
tid = deck.id;
|
||||
title = deck.title;
|
||||
hero = new UserCardData(deck.hero, VariantData.GetDefault());
|
||||
cards = new UserCardData[deck.cards.Length];
|
||||
for (int i = 0; i < deck.cards.Length; i++)
|
||||
{
|
||||
cards[i] = new UserCardData(deck.cards[i], VariantData.GetDefault());
|
||||
}
|
||||
}
|
||||
|
||||
public int GetQuantity()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (UserCardData card in cards)
|
||||
count += card.quantity;
|
||||
return count;
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(tid) && !string.IsNullOrWhiteSpace(title) && GetQuantity() >= GameplayData.Get().deck_size;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref tid);
|
||||
serializer.SerializeValue(ref title);
|
||||
serializer.SerializeValue(ref hero);
|
||||
NetworkTool.NetSerializeArray(serializer, ref cards);
|
||||
}
|
||||
|
||||
public static UserDeckData Default
|
||||
{
|
||||
get
|
||||
{
|
||||
UserDeckData deck = new UserDeckData();
|
||||
deck.tid = "";
|
||||
deck.title = "";
|
||||
deck.hero = new UserCardData();
|
||||
deck.cards = new UserCardData[0];
|
||||
return deck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class UserCardData : INetworkSerializable
|
||||
{
|
||||
public string tid;
|
||||
public string variant;
|
||||
public int quantity;
|
||||
|
||||
public UserCardData() { tid = ""; variant = ""; quantity = 1; }
|
||||
public UserCardData(string id, string v) { tid = id; variant = v; quantity = 1; }
|
||||
public UserCardData(CardData card, VariantData variant)
|
||||
{
|
||||
this.tid = card != null ? card.id : "";
|
||||
this.variant = variant != null ? variant.id : "";
|
||||
this.quantity = 1;
|
||||
}
|
||||
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref tid);
|
||||
serializer.SerializeValue(ref variant);
|
||||
serializer.SerializeValue(ref quantity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
11
Assets/TcgEngine/Scripts/Api/UserData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Api/UserData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3b3bfd879cfe2d4a920e9db8c539b62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TcgEngine/Scripts/Conditions.meta
Normal file
8
Assets/TcgEngine/Scripts/Conditions.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77c3e5da08ce919409a521ceae835a56
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/TcgEngine/Scripts/Conditions/ConditionCardData.cs
Normal file
34
Assets/TcgEngine/Scripts/Conditions/ConditionCardData.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that checks the card data matches
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardData", order = 10)]
|
||||
public class ConditionCardData : ConditionData
|
||||
{
|
||||
[Header("Card is")]
|
||||
public CardData card_type;
|
||||
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(target.card_id == card_type.id, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return false; //Not a card
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return false; //Not a card
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dafbad6af09c6ed49bfbe03e2d2bf8f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
71
Assets/TcgEngine/Scripts/Conditions/ConditionCardPile.cs
Normal file
71
Assets/TcgEngine/Scripts/Conditions/ConditionCardPile.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that checks in which pile a card is (deck/discard/hand/board/secrets)
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardPile", order = 10)]
|
||||
public class ConditionCardPile : ConditionData
|
||||
{
|
||||
[Header("Card is in pile")]
|
||||
public PileType type;
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
if (type == PileType.Hand)
|
||||
{
|
||||
return CompareBool(data.IsInHand(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Board)
|
||||
{
|
||||
return CompareBool(data.IsOnBoard(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Equipped)
|
||||
{
|
||||
return CompareBool(data.IsEquipped(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Deck)
|
||||
{
|
||||
return CompareBool(data.IsInDeck(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Discard)
|
||||
{
|
||||
return CompareBool(data.IsInDiscard(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Secret)
|
||||
{
|
||||
return CompareBool(data.IsInSecret(target), oper);
|
||||
}
|
||||
|
||||
if (type == PileType.Temp)
|
||||
{
|
||||
return CompareBool(data.IsInTemp(target), oper);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return false; //Player cannot be in a pile
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return type == PileType.Board && target != Slot.None; //Slot is always on board
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c274f103d1df9814494533f6e0f3aedc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
52
Assets/TcgEngine/Scripts/Conditions/ConditionCardType.cs
Normal file
52
Assets/TcgEngine/Scripts/Conditions/ConditionCardType.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that checks the type, team and traits of a card
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardType", order = 10)]
|
||||
public class ConditionCardType : ConditionData
|
||||
{
|
||||
[Header("Card is of type")]
|
||||
public CardType has_type;
|
||||
public TeamData has_team;
|
||||
public TraitData has_trait;
|
||||
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(IsTrait(target), oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return false; //Not a card
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return false; //Not a card
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, CardData target)
|
||||
{
|
||||
bool is_type = target.type == has_type || has_type == CardType.None;
|
||||
bool is_team = target.team == has_team || has_team == null;
|
||||
bool is_trait = target.HasTrait(has_trait) || has_trait == null;
|
||||
return (is_type && is_team && is_trait);
|
||||
}
|
||||
|
||||
private bool IsTrait(Card card)
|
||||
{
|
||||
bool is_type = card.CardData.type == has_type || has_type == CardType.None;
|
||||
bool is_team = card.CardData.team == has_team || has_team == null;
|
||||
bool is_trait = card.HasTrait(has_trait) || has_trait == null;
|
||||
return (is_type && is_team && is_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d028860b966ab8f44908a62e25280a39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
95
Assets/TcgEngine/Scripts/Conditions/ConditionCount.cs
Normal file
95
Assets/TcgEngine/Scripts/Conditions/ConditionCount.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
public enum ConditionPlayerType
|
||||
{
|
||||
Self = 0,
|
||||
Opponent = 1,
|
||||
Both = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger condition that count the amount of cards in pile of your choise (deck/discard/hand/board...)
|
||||
/// Can also only count cards of a specific type/team/trait
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/Count", order = 10)]
|
||||
public class ConditionCount : ConditionData
|
||||
{
|
||||
[Header("Count cards of type")]
|
||||
public ConditionPlayerType target;
|
||||
public PileType pile;
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
[Header("Traits")]
|
||||
public CardType has_type;
|
||||
public TeamData has_team;
|
||||
public TraitData has_trait;
|
||||
|
||||
public override bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
int count = 0;
|
||||
if (target == ConditionPlayerType.Self || target == ConditionPlayerType.Both)
|
||||
{
|
||||
Player player = data.GetPlayer(caster.player_id);
|
||||
count += CountPile(player, pile);
|
||||
}
|
||||
if (target == ConditionPlayerType.Opponent || target == ConditionPlayerType.Both)
|
||||
{
|
||||
Player player = data.GetOpponentPlayer(caster.player_id);
|
||||
count += CountPile(player, pile);
|
||||
}
|
||||
return CompareInt(count, oper, value);
|
||||
}
|
||||
|
||||
private int CountPile(Player player, PileType pile)
|
||||
{
|
||||
List<Card> card_pile = null;
|
||||
|
||||
if (pile == PileType.Hand)
|
||||
card_pile = player.cards_hand;
|
||||
|
||||
if (pile == PileType.Board)
|
||||
card_pile = player.cards_board;
|
||||
|
||||
if (pile == PileType.Equipped)
|
||||
card_pile = player.cards_equip;
|
||||
|
||||
if (pile == PileType.Deck)
|
||||
card_pile = player.cards_deck;
|
||||
|
||||
if (pile == PileType.Discard)
|
||||
card_pile = player.cards_discard;
|
||||
|
||||
if (pile == PileType.Secret)
|
||||
card_pile = player.cards_secret;
|
||||
|
||||
if (pile == PileType.Temp)
|
||||
card_pile = player.cards_temp;
|
||||
|
||||
if (card_pile != null)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Card card in card_pile)
|
||||
{
|
||||
if (IsTrait(card))
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private bool IsTrait(Card card)
|
||||
{
|
||||
bool is_type = card.CardData.type == has_type || has_type == CardType.None;
|
||||
bool is_team = card.CardData.team == has_team || has_team == null;
|
||||
bool is_trait = card.HasTrait(has_trait) || has_trait == null;
|
||||
return (is_type && is_team && is_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionCount.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionCount.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9642af46d64291e468622adccd555b22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/TcgEngine/Scripts/Conditions/ConditionDamaged.cs
Normal file
32
Assets/TcgEngine/Scripts/Conditions/ConditionDamaged.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a card/player is damaged
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/Damaged", order = 10)]
|
||||
public class ConditionDamaged : ConditionData
|
||||
{
|
||||
[Header("Card is damaged")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(target.damage > 0, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareBool(target.hp < target.hp_max, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionDamaged.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionDamaged.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39b7e85a87131bc4494769ae3ed91e20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/TcgEngine/Scripts/Conditions/ConditionDeckbuilding.cs
Normal file
27
Assets/TcgEngine/Scripts/Conditions/ConditionDeckbuilding.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that check if the CardData is a valid deckbuilding card (not a summon token)
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardDeckbuilding", order = 10)]
|
||||
public class ConditionDeckbuilding : ConditionData
|
||||
{
|
||||
[Header("Card is Deckbuilding")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(target.CardData.deckbuilding, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, CardData target)
|
||||
{
|
||||
return CompareBool(target.deckbuilding, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47697886145249247bdbaa7a3452ef34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/TcgEngine/Scripts/Conditions/ConditionEquipped.cs
Normal file
32
Assets/TcgEngine/Scripts/Conditions/ConditionEquipped.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that check if the card is equipped with an equipment card
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardEquipped", order = 10)]
|
||||
public class ConditionEquipped : ConditionData
|
||||
{
|
||||
[Header("Target is equipped")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(target.equipped_uid != null, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 303a0dbac5969f440a9cf0d8e8c7a577
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/TcgEngine/Scripts/Conditions/ConditionExhaust.cs
Normal file
32
Assets/TcgEngine/Scripts/Conditions/ConditionExhaust.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that check if the card is exhausted or not
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardExhausted", order = 10)]
|
||||
public class ConditionExhaust : ConditionData
|
||||
{
|
||||
[Header("Target is exhausted")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(target.exhausted, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareBool(false, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return CompareBool(false, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionExhaust.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionExhaust.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d05746ad826de244783b23ff99b0cbe5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/TcgEngine/Scripts/Conditions/ConditionOnce.cs
Normal file
20
Assets/TcgEngine/Scripts/Conditions/ConditionOnce.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Add this to an ability to prevent it from being cast more than once per turn
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/OncePerTurn", order = 10)]
|
||||
public class ConditionOnce : ConditionData
|
||||
{
|
||||
public override bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
return !data.ability_played.Contains(ability.id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionOnce.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionOnce.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3b28cf451548d840bae6f9839af5596
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
35
Assets/TcgEngine/Scripts/Conditions/ConditionOwner.cs
Normal file
35
Assets/TcgEngine/Scripts/Conditions/ConditionOwner.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that check the owner of the target match the owner of the caster
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardOwner", order = 10)]
|
||||
public class ConditionOwner : ConditionData
|
||||
{
|
||||
[Header("Target owner is caster owner")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
bool same_owner = caster.player_id == target.player_id;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
bool same_owner = caster.player_id == target.player_id;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
bool same_owner = Slot.GetP(caster.player_id) == target.p;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionOwner.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionOwner.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 004c422eee4e9ee41a7603d981a2fff7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/TcgEngine/Scripts/Conditions/ConditionOwnerAI.cs
Normal file
51
Assets/TcgEngine/Scripts/Conditions/ConditionOwnerAI.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that is checked only by the AI.
|
||||
/// Prevents the AI from targeting itself with bad spells even though you want to give real players the flexibility to do it
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardOwnerAI", order = 10)]
|
||||
public class ConditionOwnerAI : ConditionData
|
||||
{
|
||||
[Header("AI Only: Target owner is caster owner")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
if (!IsAIPlayer(data, caster))
|
||||
return true; //Condition always true for human players
|
||||
|
||||
bool same_owner = caster.player_id == target.player_id;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
if (!IsAIPlayer(data, caster))
|
||||
return true; //Condition always true for human players
|
||||
|
||||
bool same_owner = caster.player_id == target.player_id;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
if (!IsAIPlayer(data, caster))
|
||||
return true; //Condition always true for human players
|
||||
|
||||
bool same_owner = Slot.GetP(caster.player_id) == target.p;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
private bool IsAIPlayer(Game data, Card caster)
|
||||
{
|
||||
Player player = data.GetPlayer(caster.player_id);
|
||||
return player.is_ai;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionOwnerAI.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionOwnerAI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5f2a0ee74f5ccd428221969a56ee854
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/TcgEngine/Scripts/Conditions/ConditionPlayerStat.cs
Normal file
40
Assets/TcgEngine/Scripts/Conditions/ConditionPlayerStat.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares basic player stats such as attack/hp/mana
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/PlayerStat", order = 10)]
|
||||
public class ConditionPlayerStat : ConditionData
|
||||
{
|
||||
[Header("Card stat is")]
|
||||
public ConditionStatType type;
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
Player ptarget = data.GetPlayer(target.player_id);
|
||||
return IsTargetConditionMet(data, ability, caster, ptarget);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
if (type == ConditionStatType.HP)
|
||||
{
|
||||
return CompareInt(target.hp, oper, value);
|
||||
}
|
||||
|
||||
if (type == ConditionStatType.Mana)
|
||||
{
|
||||
return CompareInt(target.mana, oper, value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2829dec3f20bf1749be842a462dc7209
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/TcgEngine/Scripts/Conditions/ConditionRolled.cs
Normal file
38
Assets/TcgEngine/Scripts/Conditions/ConditionRolled.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if its your turn
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/RolledValue", order = 10)]
|
||||
public class ConditionRolled : ConditionData
|
||||
{
|
||||
[Header("Value Rolled is")]
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
public override bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
return CompareInt(data.rolled_value, oper, value);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareInt(data.rolled_value, oper, value);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareInt(data.rolled_value, oper, value);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return CompareInt(data.rolled_value, oper, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionRolled.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionRolled.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d038beb97da312344a27296b92629b7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Check selected value for card cost
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/SelectedValue", order = 10)]
|
||||
public class ConditionSelectedValue : ConditionData
|
||||
{
|
||||
[Header("Selected Value is")]
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
public override bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
return CompareInt(data.selected_value, oper, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b36c860069675084cbf42329be6ed7bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/TcgEngine/Scripts/Conditions/ConditionSelf.cs
Normal file
33
Assets/TcgEngine/Scripts/Conditions/ConditionSelf.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that check if the target is the same as the caster
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardSelf", order = 10)]
|
||||
public class ConditionSelf : ConditionData
|
||||
{
|
||||
[Header("Target is caster")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(caster == target, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
bool same_owner = caster.player_id == target.player_id;
|
||||
return CompareBool(same_owner, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return CompareBool(caster.slot == target, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionSelf.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionSelf.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b48d9d9834b3f54689381231f73f1d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/TcgEngine/Scripts/Conditions/ConditionSlotDist.cs
Normal file
32
Assets/TcgEngine/Scripts/Conditions/ConditionSlotDist.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// SlotDist is the travel distance from the caster to the target
|
||||
/// Unlike SlotRange which is just checking each X,Y,P separately
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/SlotDist", order = 11)]
|
||||
public class ConditionSlotDist : ConditionData
|
||||
{
|
||||
[Header("Slot Distance")]
|
||||
public int distance = 1;
|
||||
public bool diagonals;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return IsTargetConditionMet(data, ability, caster, target.slot);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
Slot cslot = caster.slot;
|
||||
if (diagonals)
|
||||
return cslot.IsInDistance(target, distance);
|
||||
return cslot.IsInDistanceStraight(target, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23876ed3b55361b4e8abbc86b94a342d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/TcgEngine/Scripts/Conditions/ConditionSlotEmpty.cs
Normal file
33
Assets/TcgEngine/Scripts/Conditions/ConditionSlotEmpty.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a slot contains a card or not
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/SlotEmpty", order = 11)]
|
||||
public class ConditionSlotEmpty : ConditionData
|
||||
{
|
||||
[Header("Slot Is Empty")]
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(false, oper); //Target is not empty slot
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareBool(false, oper); //Target is not empty slot
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
Card slot_card = data.GetSlotCard(target);
|
||||
return CompareBool(slot_card == null, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3dd4e502c8dfa34fbb025ff1a8aaeb0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/TcgEngine/Scripts/Conditions/ConditionSlotRange.cs
Normal file
34
Assets/TcgEngine/Scripts/Conditions/ConditionSlotRange.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// SlotRange check each axis variable individualy for range between the caster and target
|
||||
/// If you want to check the travel distance instead (all at once) use SlotDist
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/SlotRange", order = 11)]
|
||||
public class ConditionSlotRange : ConditionData
|
||||
{
|
||||
[Header("Slot Range")]
|
||||
public int range_x = 1;
|
||||
public int range_y = 1;
|
||||
public int range_p = 0;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return IsTargetConditionMet(data, ability, caster, target.slot);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
Slot cslot = caster.slot;
|
||||
int dist_x = Mathf.Abs(cslot.x - target.x);
|
||||
int dist_y = Mathf.Abs(cslot.y - target.y);
|
||||
int dist_p = Mathf.Abs(cslot.p - target.p);
|
||||
return dist_x <= range_x && dist_y <= range_y && dist_p <= range_p;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8debe5efbe667de4fbf47dc9212bd159
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/TcgEngine/Scripts/Conditions/ConditionSlotValue.cs
Normal file
33
Assets/TcgEngine/Scripts/Conditions/ConditionSlotValue.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// SlotValue compare each slot x and y to a specific value, like slot.x >=3 and slot.y < 5
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/SlotValue", order = 11)]
|
||||
public class ConditionSlotValue : ConditionData
|
||||
{
|
||||
[Header("Slot Value")]
|
||||
public ConditionOperatorInt oper_x;
|
||||
public int value_x = 0;
|
||||
|
||||
public ConditionOperatorInt oper_y;
|
||||
public int value_y = 0;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return IsTargetConditionMet(data, ability, caster, target.slot);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
bool valid_x = CompareInt(target.x, oper_x, value_x);
|
||||
bool valid_y = CompareInt(target.y, oper_y, value_y);
|
||||
return valid_x && valid_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 274374999bbffee429c9dfa75d2592db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/TcgEngine/Scripts/Conditions/ConditionStat.cs
Normal file
62
Assets/TcgEngine/Scripts/Conditions/ConditionStat.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
public enum ConditionStatType
|
||||
{
|
||||
None = 0,
|
||||
Attack = 10,
|
||||
HP = 20,
|
||||
Mana = 30,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares basic card or player stats such as attack/hp/mana
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/Stat", order = 10)]
|
||||
public class ConditionStat : ConditionData
|
||||
{
|
||||
[Header("Card stat is")]
|
||||
public ConditionStatType type;
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
if (type == ConditionStatType.Attack)
|
||||
{
|
||||
return CompareInt(target.GetAttack(), oper, value);
|
||||
}
|
||||
|
||||
if (type == ConditionStatType.HP)
|
||||
{
|
||||
return CompareInt(target.GetHP(), oper, value);
|
||||
}
|
||||
|
||||
if (type == ConditionStatType.Mana)
|
||||
{
|
||||
return CompareInt(target.GetMana(), oper, value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
if (type == ConditionStatType.HP)
|
||||
{
|
||||
return CompareInt(target.hp, oper, value);
|
||||
}
|
||||
|
||||
if (type == ConditionStatType.Mana)
|
||||
{
|
||||
return CompareInt(target.mana, oper, value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionStat.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionStat.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56b5ff935f8b7194ea353bb38598d96e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
37
Assets/TcgEngine/Scripts/Conditions/ConditionStatus.cs
Normal file
37
Assets/TcgEngine/Scripts/Conditions/ConditionStatus.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Checks if a player or card has a status effect
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/CardStatus", order = 10)]
|
||||
public class ConditionStatus : ConditionData
|
||||
{
|
||||
[Header("Card has status")]
|
||||
public StatusType has_status;
|
||||
public int value = 0;
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
bool hstatus = target.HasStatus(has_status) && target.GetStatusValue(has_status) >= value;
|
||||
return CompareBool(hstatus, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
bool hstatus = target.HasStatus(has_status) && target.GetStatusValue(has_status) >= value;
|
||||
return CompareBool(hstatus, oper);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
Card card = data.GetSlotCard(target);
|
||||
if (card != null)
|
||||
return IsTargetConditionMet(data, ability, caster, card);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionStatus.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionStatus.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3c78f482b96ed47896e9c6ad583d1e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/TcgEngine/Scripts/Conditions/ConditionTarget.cs
Normal file
42
Assets/TcgEngine/Scripts/Conditions/ConditionTarget.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.AI;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition that compares the target category of an ability to the actual target (card, player or slot)
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/Player", order = 10)]
|
||||
public class ConditionTarget : ConditionData
|
||||
{
|
||||
[Header("Target is of type")]
|
||||
public ConditionTargetType type;
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareBool(type == ConditionTargetType.Card, oper); //Is Card
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareBool(type == ConditionTargetType.Player, oper); //Is Player
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return CompareBool(type == ConditionTargetType.Slot, oper); //Is Player
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConditionTargetType
|
||||
{
|
||||
None = 0,
|
||||
Card = 10,
|
||||
Player = 20,
|
||||
Slot = 30,
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionTarget.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06df69f70efef364b8eab89605ed0d70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/TcgEngine/Scripts/Conditions/ConditionTrait.cs
Normal file
29
Assets/TcgEngine/Scripts/Conditions/ConditionTrait.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares cards or players custom stats
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/StatCustom", order = 10)]
|
||||
public class ConditionTrait : ConditionData
|
||||
{
|
||||
[Header("Card stat is")]
|
||||
public TraitData trait;
|
||||
public ConditionOperatorInt oper;
|
||||
public int value;
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return CompareInt(target.GetTraitValue(trait.id), oper, value);
|
||||
}
|
||||
|
||||
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return CompareInt(target.GetTraitValue(trait.id), oper, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionTrait.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionTrait.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc5ee312d5d0efd448bf6e1a851af5ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/TcgEngine/Scripts/Conditions/ConditionTurn.cs
Normal file
22
Assets/TcgEngine/Scripts/Conditions/ConditionTurn.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if its your turn
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "condition", menuName = "TcgEngine/Condition/Turn", order = 10)]
|
||||
public class ConditionTurn : ConditionData
|
||||
{
|
||||
public ConditionOperatorBool oper;
|
||||
|
||||
public override bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
bool yourturn = caster.player_id == data.current_player;
|
||||
return CompareBool(yourturn, oper);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/ConditionTurn.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/ConditionTurn.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbbc55df9ef91354d88c2afe3b5105b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/TcgEngine/Scripts/Conditions/FilterFirst.cs
Normal file
38
Assets/TcgEngine/Scripts/Conditions/FilterFirst.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Pick X first cards from the source array
|
||||
|
||||
[CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/First", order = 10)]
|
||||
public class FilterFirst : FilterData
|
||||
{
|
||||
public int amount = 1; //Number of first targets selected
|
||||
|
||||
public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
for (int i = 0; i < max; i++)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public override List<Player> FilterTargets(Game data, AbilityData ability, Card caster, List<Player> source, List<Player> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
for (int i = 0; i < max; i++)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public override List<Slot> FilterTargets(Game data, AbilityData ability, Card caster, List<Slot> source, List<Slot> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
for (int i = 0; i < max; i++)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/FilterFirst.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/FilterFirst.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 344a45cba28a5db439b6bf1f57127475
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/TcgEngine/Scripts/Conditions/FilterHighestStat.cs
Normal file
53
Assets/TcgEngine/Scripts/Conditions/FilterHighestStat.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Pick all targets with the highest stat
|
||||
|
||||
[CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/HighestStat", order = 10)]
|
||||
public class FilterHighestStat : FilterData
|
||||
{
|
||||
public ConditionStatType stat;
|
||||
|
||||
public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
|
||||
{
|
||||
//Find highest
|
||||
int highest = -999;
|
||||
foreach (Card card in source)
|
||||
{
|
||||
int stat = GetStat(card);
|
||||
if (stat > highest)
|
||||
highest = stat;
|
||||
}
|
||||
|
||||
//Add all highest
|
||||
foreach (Card card in source)
|
||||
{
|
||||
int stat = GetStat(card);
|
||||
if (stat == highest)
|
||||
dest.Add(card);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private int GetStat(Card card)
|
||||
{
|
||||
if (stat == ConditionStatType.Attack)
|
||||
{
|
||||
return card.GetAttack();
|
||||
}
|
||||
if (stat == ConditionStatType.HP)
|
||||
{
|
||||
return card.GetHP();
|
||||
}
|
||||
if (stat == ConditionStatType.Mana)
|
||||
{
|
||||
return card.GetMana();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26d6c56df3cf5e64d857568f0f8cc444
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/TcgEngine/Scripts/Conditions/FilterLast.cs
Normal file
41
Assets/TcgEngine/Scripts/Conditions/FilterLast.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Pick X first cards from the source array
|
||||
|
||||
[CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/Last", order = 10)]
|
||||
public class FilterLast : FilterData
|
||||
{
|
||||
public int amount = 1; //Number of first targets selected
|
||||
|
||||
public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
int min = source.Count - max;
|
||||
for (int i = source.Count-1; i >= min; i--)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public override List<Player> FilterTargets(Game data, AbilityData ability, Card caster, List<Player> source, List<Player> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
int min = source.Count - max;
|
||||
for (int i = source.Count - 1; i >= min; i--)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public override List<Slot> FilterTargets(Game data, AbilityData ability, Card caster, List<Slot> source, List<Slot> dest)
|
||||
{
|
||||
int max = Mathf.Min(source.Count, amount);
|
||||
int min = source.Count - max;
|
||||
for (int i = source.Count - 1; i >= min; i--)
|
||||
dest.Add(source[i]);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/FilterLast.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/FilterLast.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08d4e8ec101e29f47a83f7531bc32e74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/TcgEngine/Scripts/Conditions/FilterLowestStat.cs
Normal file
53
Assets/TcgEngine/Scripts/Conditions/FilterLowestStat.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Pick all targets with the lowest stat
|
||||
|
||||
[CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/LowestStat", order = 10)]
|
||||
public class FilterLowestStat : FilterData
|
||||
{
|
||||
public ConditionStatType stat;
|
||||
|
||||
public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
|
||||
{
|
||||
//Find lowest
|
||||
int lowest = 99999;
|
||||
foreach (Card card in source)
|
||||
{
|
||||
int stat = GetStat(card);
|
||||
if (stat < lowest)
|
||||
lowest = stat;
|
||||
}
|
||||
|
||||
//Add all lowest
|
||||
foreach (Card card in source)
|
||||
{
|
||||
int stat = GetStat(card);
|
||||
if (stat == lowest)
|
||||
dest.Add(card);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
private int GetStat(Card card)
|
||||
{
|
||||
if (stat == ConditionStatType.Attack)
|
||||
{
|
||||
return card.GetAttack();
|
||||
}
|
||||
if (stat == ConditionStatType.HP)
|
||||
{
|
||||
return card.GetHP();
|
||||
}
|
||||
if (stat == ConditionStatType.Mana)
|
||||
{
|
||||
return card.GetMana();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/FilterLowestStat.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/FilterLowestStat.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bda5e3e8df054240a5ff29e526a22a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/TcgEngine/Scripts/Conditions/FilterRandom.cs
Normal file
34
Assets/TcgEngine/Scripts/Conditions/FilterRandom.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Pick X number of targets at random from the source array
|
||||
|
||||
[CreateAssetMenu(fileName = "filter", menuName = "TcgEngine/Filter/Random", order = 10)]
|
||||
public class FilterRandom : FilterData
|
||||
{
|
||||
public int amount = 1; //Number of random targets selected
|
||||
|
||||
public override List<Card> FilterTargets(Game data, AbilityData ability, Card caster, List<Card> source, List<Card> dest)
|
||||
{
|
||||
return GameTool.PickXRandom(source, dest, amount);
|
||||
}
|
||||
|
||||
public override List<Player> FilterTargets(Game data, AbilityData ability, Card caster, List<Player> source, List<Player> dest)
|
||||
{
|
||||
return GameTool.PickXRandom(source, dest, amount);
|
||||
}
|
||||
|
||||
public override List<Slot> FilterTargets(Game data, AbilityData ability, Card caster, List<Slot> source, List<Slot> dest)
|
||||
{
|
||||
return GameTool.PickXRandom(source, dest, amount);
|
||||
}
|
||||
|
||||
public override List<CardData> FilterTargets(Game data, AbilityData ability, Card caster, List<CardData> source, List<CardData> dest)
|
||||
{
|
||||
return GameTool.PickXRandom(source, dest, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Conditions/FilterRandom.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Conditions/FilterRandom.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5f59c8a06280cb40b425b437dec3c57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/TcgEngine/Scripts/Data.meta
Normal file
8
Assets/TcgEngine/Scripts/Data.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69515c7f3e81be04c9a5916ddd6a91ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
692
Assets/TcgEngine/Scripts/Data/AbilityData.cs
Normal file
692
Assets/TcgEngine/Scripts/Data/AbilityData.cs
Normal file
@@ -0,0 +1,692 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all ability data
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "ability", menuName = "TcgEngine/AbilityData", order = 5)]
|
||||
public class AbilityData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
|
||||
[Header("Trigger")]
|
||||
public AbilityTrigger trigger; //WHEN does the ability trigger?
|
||||
public ConditionData[] conditions_trigger; //Condition checked on the card triggering the ability (usually the caster)
|
||||
|
||||
[Header("Target")]
|
||||
public AbilityTarget target; //WHO is targeted?
|
||||
public ConditionData[] conditions_target; //Condition checked on the target to know if its a valid taget
|
||||
public FilterData[] filters_target; //Condition checked on the target to know if its a valid taget
|
||||
|
||||
[Header("Effect")]
|
||||
public EffectData[] effects; //WHAT this does?
|
||||
public StatusData[] status; //Status added by this ability
|
||||
public int value; //Value passed to the effect (deal X damage)
|
||||
public int duration; //Duration passed to the effect (usually for status, 0=permanent)
|
||||
|
||||
[Header("Chain/Choices")]
|
||||
public AbilityData[] chain_abilities; //Abilities that will be triggered after this one
|
||||
|
||||
[Header("Activated Ability")]
|
||||
public int mana_cost; //Mana cost for activated abilities
|
||||
public bool exhaust; //Action cost for activated abilities
|
||||
|
||||
[Header("FX")]
|
||||
public GameObject board_fx;
|
||||
public GameObject caster_fx;
|
||||
public GameObject target_fx;
|
||||
public GameObject projectile_fx;
|
||||
public AudioClip cast_audio;
|
||||
public AudioClip target_audio;
|
||||
public bool charge_target;
|
||||
|
||||
[Header("Text")]
|
||||
public string title;
|
||||
[TextArea(5, 7)]
|
||||
public string desc;
|
||||
|
||||
public static List<AbilityData> ability_list = new List<AbilityData>(); //Faster access in loops
|
||||
public static Dictionary<string, AbilityData> ability_dict = new Dictionary<string, AbilityData>(); //Faster access in Get(id)
|
||||
|
||||
public static void Load(string folder = "")
|
||||
{
|
||||
if (ability_list.Count == 0)
|
||||
{
|
||||
ability_list.AddRange(Resources.LoadAll<AbilityData>(folder));
|
||||
|
||||
foreach (AbilityData ability in ability_list)
|
||||
ability_dict.Add(ability.id, ability);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
public string GetDesc()
|
||||
{
|
||||
return desc;
|
||||
}
|
||||
|
||||
public string GetDesc(CardData card)
|
||||
{
|
||||
string dsc = desc;
|
||||
dsc = dsc.Replace("<name>", card.title);
|
||||
dsc = dsc.Replace("<value>", value.ToString());
|
||||
dsc = dsc.Replace("<duration>", duration.ToString());
|
||||
return dsc;
|
||||
}
|
||||
|
||||
//Generic condition for the ability to trigger
|
||||
public bool AreTriggerConditionsMet(Game data, Card caster)
|
||||
{
|
||||
return AreTriggerConditionsMet(data, caster, caster); //Triggerer is the caster
|
||||
}
|
||||
|
||||
//Some abilities are caused by another card (PlayOther), otherwise most of the time the triggerer is the caster, check condition on triggerer
|
||||
public bool AreTriggerConditionsMet(Game data, Card caster, Card trigger_card)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_trigger)
|
||||
{
|
||||
if (cond != null)
|
||||
{
|
||||
if (!cond.IsTriggerConditionMet(data, this, caster))
|
||||
return false;
|
||||
if (!cond.IsTargetConditionMet(data, this, caster, trigger_card))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Some abilities are caused by an action on a player (OnFight when attacking the player), check condition on that player
|
||||
public bool AreTriggerConditionsMet(Game data, Card caster, Player trigger_player)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_trigger)
|
||||
{
|
||||
if (cond != null)
|
||||
{
|
||||
if (!cond.IsTriggerConditionMet(data, this, caster))
|
||||
return false;
|
||||
if (!cond.IsTargetConditionMet(data, this, caster, trigger_player))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check if the card target is valid
|
||||
public bool AreTargetConditionsMet(Game data, Card caster, Card target_card)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_target)
|
||||
{
|
||||
if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_card))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check if the player target is valid
|
||||
public bool AreTargetConditionsMet(Game data, Card caster, Player target_player)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_target)
|
||||
{
|
||||
if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_player))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check if the slot target is valid
|
||||
public bool AreTargetConditionsMet(Game data, Card caster, Slot target_slot)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_target)
|
||||
{
|
||||
if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_slot))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check if the card data target is valid
|
||||
public bool AreTargetConditionsMet(Game data, Card caster, CardData target_card)
|
||||
{
|
||||
foreach (ConditionData cond in conditions_target)
|
||||
{
|
||||
if (cond != null && !cond.IsTargetConditionMet(data, this, caster, target_card))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//CanTarget is similar to AreTargetConditionsMet but only applies to targets on the board, with extra board-only conditions
|
||||
public bool CanTarget(Game data, Card caster, Card target)
|
||||
{
|
||||
if (target.HasStatus(StatusType.Stealth))
|
||||
return false; //Hidden
|
||||
|
||||
if (target.HasStatus(StatusType.SpellImmunity))
|
||||
return false; //Spell immunity
|
||||
|
||||
bool condition_match = AreTargetConditionsMet(data, caster, target);
|
||||
return condition_match;
|
||||
}
|
||||
|
||||
//Can target check additional restrictions and is usually for SelectTarget or PlayTarget abilities
|
||||
public bool CanTarget(Game data, Card caster, Player target)
|
||||
{
|
||||
bool condition_match = AreTargetConditionsMet(data, caster, target);
|
||||
return condition_match;
|
||||
}
|
||||
|
||||
public bool CanTarget(Game data, Card caster, Slot target)
|
||||
{
|
||||
return AreTargetConditionsMet(data, caster, target); //No additional conditions for slots
|
||||
}
|
||||
|
||||
//Check if destination array has the target after being filtered, used to support filters in CardSelector
|
||||
public bool IsCardSelectionValid(Game data, Card caster, Card target, ListSwap<Card> card_array = null)
|
||||
{
|
||||
List<Card> targets = GetCardTargets(data, caster, card_array);
|
||||
return targets.Contains(target); //Card is still in array after filtering
|
||||
}
|
||||
|
||||
public void DoEffects(GameLogic logic, Card caster)
|
||||
{
|
||||
foreach(EffectData effect in effects)
|
||||
effect?.DoEffect(logic, this, caster);
|
||||
}
|
||||
|
||||
public void DoEffects(GameLogic logic, Card caster, Card target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoEffect(logic, this, caster, target);
|
||||
foreach(StatusData stat in status)
|
||||
target.AddStatus(stat, value, duration);
|
||||
}
|
||||
|
||||
public void DoEffects(GameLogic logic, Card caster, Player target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoEffect(logic, this, caster, target);
|
||||
foreach (StatusData stat in status)
|
||||
target.AddStatus(stat, value, duration);
|
||||
}
|
||||
|
||||
public void DoEffects(GameLogic logic, Card caster, Slot target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoEffect(logic, this, caster, target);
|
||||
}
|
||||
|
||||
public void DoEffects(GameLogic logic, Card caster, CardData target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoEffect(logic, this, caster, target);
|
||||
}
|
||||
|
||||
public void DoOngoingEffects(GameLogic logic, Card caster, Card target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoOngoingEffect(logic, this, caster, target);
|
||||
foreach (StatusData stat in status)
|
||||
target.AddOngoingStatus(stat, value);
|
||||
}
|
||||
|
||||
public void DoOngoingEffects(GameLogic logic, Card caster, Player target)
|
||||
{
|
||||
foreach (EffectData effect in effects)
|
||||
effect?.DoOngoingEffect(logic, this, caster, target);
|
||||
foreach (StatusData stat in status)
|
||||
target.AddOngoingStatus(stat, value);
|
||||
}
|
||||
|
||||
public bool HasEffect<T>() where T : EffectData
|
||||
{
|
||||
foreach (EffectData eff in effects)
|
||||
{
|
||||
if (eff != null && eff is T)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasStatus(StatusType type)
|
||||
{
|
||||
foreach (StatusData sta in status)
|
||||
{
|
||||
if (sta != null && sta.effect == type)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetDamage()
|
||||
{
|
||||
int damage = 0;
|
||||
foreach (EffectData eff in effects)
|
||||
{
|
||||
if (eff != null && eff is EffectDamage)
|
||||
{
|
||||
damage += this.value;
|
||||
}
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
private void AddValidCards(Game data, Card caster, List<Card> source, List<Card> targets)
|
||||
{
|
||||
foreach (Card card in source)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, card))
|
||||
targets.Add(card);
|
||||
}
|
||||
}
|
||||
|
||||
//Return cards targets, memory_array is used for optimization and avoid allocating new memory
|
||||
public List<Card> GetCardTargets(Game data, Card caster, ListSwap<Card> memory_array = null)
|
||||
{
|
||||
if (memory_array == null)
|
||||
memory_array = new ListSwap<Card>(); //Slow operation
|
||||
|
||||
List<Card> targets = memory_array.Get();
|
||||
|
||||
if (target == AbilityTarget.Self)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, caster))
|
||||
targets.Add(caster);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.AllCardsBoard || target == AbilityTarget.SelectTarget)
|
||||
{
|
||||
foreach (Player player in data.players)
|
||||
{
|
||||
foreach (Card card in player.cards_board)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, card))
|
||||
targets.Add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.AllCardsHand)
|
||||
{
|
||||
foreach (Player player in data.players)
|
||||
{
|
||||
foreach (Card card in player.cards_hand)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, card))
|
||||
targets.Add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.AllCardsAllPiles || target == AbilityTarget.CardSelector)
|
||||
{
|
||||
foreach (Player player in data.players)
|
||||
{
|
||||
AddValidCards(data, caster, player.cards_deck, targets);
|
||||
AddValidCards(data, caster, player.cards_discard, targets);
|
||||
AddValidCards(data, caster, player.cards_hand, targets);
|
||||
AddValidCards(data, caster, player.cards_secret, targets);
|
||||
AddValidCards(data, caster, player.cards_board, targets);
|
||||
AddValidCards(data, caster, player.cards_equip, targets);
|
||||
AddValidCards(data, caster, player.cards_temp, targets);
|
||||
}
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.LastPlayed)
|
||||
{
|
||||
Card target = data.GetCard(data.last_played);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.LastDestroyed)
|
||||
{
|
||||
Card target = data.GetCard(data.last_destroyed);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.LastTargeted)
|
||||
{
|
||||
Card target = data.GetCard(data.last_target);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.LastSummoned)
|
||||
{
|
||||
Card target = data.GetCard(data.last_summoned);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.AbilityTriggerer)
|
||||
{
|
||||
Card target = data.GetCard(data.ability_triggerer);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.EquippedCard)
|
||||
{
|
||||
if (caster.CardData.IsEquipment())
|
||||
{
|
||||
//Get bearer of the equipment
|
||||
Player player = data.GetPlayer(caster.player_id);
|
||||
Card target = player.GetBearerCard(caster);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
else if(caster.equipped_uid != null)
|
||||
{
|
||||
//Get equipped card
|
||||
Card target = data.GetCard(caster.equipped_uid);
|
||||
if (target != null && AreTargetConditionsMet(data, caster, target))
|
||||
targets.Add(target);
|
||||
}
|
||||
}
|
||||
|
||||
//Filter targets
|
||||
if (filters_target != null && targets.Count > 0)
|
||||
{
|
||||
foreach (FilterData filter in filters_target)
|
||||
{
|
||||
if (filter != null)
|
||||
targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
//Return player targets, memory_array is used for optimization and avoid allocating new memory
|
||||
public List<Player> GetPlayerTargets(Game data, Card caster, ListSwap<Player> memory_array = null)
|
||||
{
|
||||
if (memory_array == null)
|
||||
memory_array = new ListSwap<Player>(); //Slow operation
|
||||
|
||||
List<Player> targets = memory_array.Get();
|
||||
|
||||
if (target == AbilityTarget.PlayerSelf)
|
||||
{
|
||||
Player player = data.GetPlayer(caster.player_id);
|
||||
targets.Add(player);
|
||||
}
|
||||
else if (target == AbilityTarget.PlayerOpponent)
|
||||
{
|
||||
for (int tp = 0; tp < data.players.Length; tp++)
|
||||
{
|
||||
if (tp != caster.player_id)
|
||||
{
|
||||
Player oplayer = data.players[tp];
|
||||
targets.Add(oplayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target == AbilityTarget.AllPlayers)
|
||||
{
|
||||
foreach (Player player in data.players)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, player))
|
||||
targets.Add(player);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Filter targets
|
||||
if (filters_target != null && targets.Count > 0)
|
||||
{
|
||||
foreach (FilterData filter in filters_target)
|
||||
{
|
||||
if (filter != null)
|
||||
targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
//Return slot targets, memory_array is used for optimization and avoid allocating new memory
|
||||
public List<Slot> GetSlotTargets(Game data, Card caster, ListSwap<Slot> memory_array = null)
|
||||
{
|
||||
if (memory_array == null)
|
||||
memory_array = new ListSwap<Slot>(); //Slow operation
|
||||
|
||||
List<Slot> targets = memory_array.Get();
|
||||
|
||||
if (target == AbilityTarget.AllSlots)
|
||||
{
|
||||
List<Slot> slots = Slot.GetAll();
|
||||
foreach (Slot slot in slots)
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, slot))
|
||||
targets.Add(slot);
|
||||
}
|
||||
}
|
||||
|
||||
//Filter targets
|
||||
if (filters_target != null && targets.Count > 0)
|
||||
{
|
||||
foreach (FilterData filter in filters_target)
|
||||
{
|
||||
if (filter != null)
|
||||
targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
public List<CardData> GetCardDataTargets(Game data, Card caster, ListSwap<CardData> memory_array = null)
|
||||
{
|
||||
if (memory_array == null)
|
||||
memory_array = new ListSwap<CardData>(); //Slow operation
|
||||
|
||||
List<CardData> targets = memory_array.Get();
|
||||
|
||||
if (target == AbilityTarget.AllCardData)
|
||||
{
|
||||
foreach (CardData card in CardData.GetAll())
|
||||
{
|
||||
if (AreTargetConditionsMet(data, caster, card))
|
||||
targets.Add(card);
|
||||
}
|
||||
}
|
||||
|
||||
//Filter targets
|
||||
if (filters_target != null && targets.Count > 0)
|
||||
{
|
||||
foreach (FilterData filter in filters_target)
|
||||
{
|
||||
if (filter != null)
|
||||
targets = filter.FilterTargets(data, this, caster, targets, memory_array.GetOther(targets));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
// Check if there is any valid target, if not, AI wont try to cast activated ability
|
||||
public bool HasValidSelectTarget(Game game_data, Card caster)
|
||||
{
|
||||
if (target == AbilityTarget.SelectTarget)
|
||||
{
|
||||
if (HasValidBoardCardTarget(game_data, caster))
|
||||
return true;
|
||||
if (HasValidPlayerTarget(game_data, caster))
|
||||
return true;
|
||||
if (HasValidSlotTarget(game_data, caster))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.CardSelector)
|
||||
{
|
||||
if (HasValidCardTarget(game_data, caster))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == AbilityTarget.ChoiceSelector)
|
||||
{
|
||||
foreach (AbilityData choice in chain_abilities)
|
||||
{
|
||||
if(choice.AreTriggerConditionsMet(game_data, caster))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; //Not selecting, valid
|
||||
}
|
||||
|
||||
public bool HasValidBoardCardTarget(Game game_data, Card caster)
|
||||
{
|
||||
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];
|
||||
if (CanTarget(game_data, caster, card))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasValidCardTarget(Game game_data, Card caster)
|
||||
{
|
||||
for (int p = 0; p < game_data.players.Length; p++)
|
||||
{
|
||||
Player player = game_data.players[p];
|
||||
bool v1 = HasValidCardTarget(game_data, caster, player.cards_deck);
|
||||
bool v2 = HasValidCardTarget(game_data, caster, player.cards_discard);
|
||||
bool v3 = HasValidCardTarget(game_data, caster, player.cards_hand);
|
||||
bool v4 = HasValidCardTarget(game_data, caster, player.cards_board);
|
||||
bool v5 = HasValidCardTarget(game_data, caster, player.cards_equip);
|
||||
bool v6 = HasValidCardTarget(game_data, caster, player.cards_secret);
|
||||
bool v7 = HasValidCardTarget(game_data, caster, player.cards_temp);
|
||||
if (v1 || v2 || v3 || v4 || v5 || v6 || v7)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasValidCardTarget(Game game_data, Card caster, List<Card> list)
|
||||
{
|
||||
for (int c = 0; c < list.Count; c++)
|
||||
{
|
||||
Card card = list[c];
|
||||
if (AreTargetConditionsMet(game_data, caster, card))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasValidPlayerTarget(Game game_data, Card caster)
|
||||
{
|
||||
for (int p = 0; p < game_data.players.Length; p++)
|
||||
{
|
||||
Player player = game_data.players[p];
|
||||
if (CanTarget(game_data, caster, player))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasValidSlotTarget(Game game_data, Card caster)
|
||||
{
|
||||
foreach (Slot slot in Slot.GetAll())
|
||||
{
|
||||
if (CanTarget(game_data, caster, slot))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSelector()
|
||||
{
|
||||
return target == AbilityTarget.SelectTarget || target == AbilityTarget.CardSelector || target == AbilityTarget.ChoiceSelector;
|
||||
}
|
||||
|
||||
public static AbilityData Get(string id)
|
||||
{
|
||||
if (id == null)
|
||||
return null;
|
||||
bool success = ability_dict.TryGetValue(id, out AbilityData ability);
|
||||
if (success)
|
||||
return ability;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<AbilityData> GetAll()
|
||||
{
|
||||
return ability_list;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum AbilityTrigger
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Ongoing = 2, //Always active (does not work with all effects)
|
||||
Activate = 5, //Action
|
||||
|
||||
OnPlay = 10, //When playeds
|
||||
OnPlayOther = 12, //When another card played
|
||||
|
||||
StartOfTurn = 20, //Every turn
|
||||
EndOfTurn = 22, //Every turn
|
||||
|
||||
OnBeforeAttack = 30, //When attacking, before damage
|
||||
OnAfterAttack = 31, //When attacking, after damage if still alive
|
||||
OnBeforeDefend = 32, //When being attacked, before damage
|
||||
OnAfterDefend = 33, //When being attacked, after damage if still alive
|
||||
OnKill = 35, //When killing another card during an attack
|
||||
|
||||
OnDeath = 40, //When dying
|
||||
OnDeathOther = 42, //When another dying
|
||||
}
|
||||
|
||||
public enum AbilityTarget
|
||||
{
|
||||
None = 0,
|
||||
Self = 1,
|
||||
|
||||
PlayerSelf = 4,
|
||||
PlayerOpponent = 5,
|
||||
AllPlayers = 7,
|
||||
|
||||
AllCardsBoard = 10,
|
||||
AllCardsHand = 11,
|
||||
AllCardsAllPiles = 12,
|
||||
AllSlots = 15,
|
||||
AllCardData = 17, //For card Create effects only
|
||||
|
||||
PlayTarget = 20, //The target selected at the same time the spell was played (spell only)
|
||||
AbilityTriggerer = 25, //The card that triggered the trap
|
||||
EquippedCard = 27, //If equipment, the bearer, if character, the item equipped
|
||||
|
||||
SelectTarget = 30, //Select a card, player or slot on board
|
||||
CardSelector = 40, //Card selector menu
|
||||
ChoiceSelector = 50, //Choice selector menu
|
||||
|
||||
LastPlayed = 70, //Last card that was played
|
||||
LastTargeted = 72, //Last card that was targeted with an ability
|
||||
LastDestroyed = 74, //Last card that was killed
|
||||
LastSummoned = 77, //Last card that was summoned or created
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/AbilityData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/AbilityData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ce5f81e5cc37f547af6923758602c8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/TcgEngine/Scripts/Data/AssetData.cs
Normal file
50
Assets/TcgEngine/Scripts/Data/AssetData.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
//Default fx and audio, some can be overrided on each individual card
|
||||
|
||||
[CreateAssetMenu(fileName = "AssetData", menuName = "TcgEngine/AssetData", order = 0)]
|
||||
public class AssetData : ScriptableObject
|
||||
{
|
||||
[Header("FX")]
|
||||
public GameObject card_spawn_fx;
|
||||
public GameObject card_destroy_fx;
|
||||
public GameObject card_attack_fx;
|
||||
public GameObject card_damage_fx;
|
||||
public GameObject card_exhausted_fx;
|
||||
public GameObject player_damage_fx;
|
||||
public GameObject damage_fx;
|
||||
public GameObject play_card_fx;
|
||||
public GameObject play_card_other_fx;
|
||||
public GameObject play_secret_fx;
|
||||
public GameObject play_secret_other_fx;
|
||||
public GameObject dice_roll_fx;
|
||||
public GameObject hover_text_box;
|
||||
public GameObject new_turn_fx;
|
||||
public GameObject win_fx;
|
||||
public GameObject lose_fx;
|
||||
public GameObject tied_fx;
|
||||
|
||||
[Header("Audio")]
|
||||
public AudioClip card_spawn_audio;
|
||||
public AudioClip card_destroy_audio;
|
||||
public AudioClip card_attack_audio;
|
||||
public AudioClip card_move_audio;
|
||||
public AudioClip card_damage_audio;
|
||||
public AudioClip player_damage_audio;
|
||||
public AudioClip hand_card_click_audio;
|
||||
public AudioClip new_turn_audio;
|
||||
public AudioClip win_audio;
|
||||
public AudioClip defeat_audio;
|
||||
public AudioClip win_music;
|
||||
public AudioClip defeat_music;
|
||||
|
||||
public static AssetData Get()
|
||||
{
|
||||
return DataLoader.Get().assets;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/AssetData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/AssetData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cca20da9cbfd28a449b51ac452845bc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/TcgEngine/Scripts/Data/AvatarData.cs
Normal file
48
Assets/TcgEngine/Scripts/Data/AvatarData.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all avatar data
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "Avatar", menuName = "TcgEngine/Avatar", order = 10)]
|
||||
public class AvatarData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
public Sprite avatar;
|
||||
public int sort_order;
|
||||
|
||||
public static List<AvatarData> avatar_list = new List<AvatarData>();
|
||||
|
||||
public static void Load(string folder = "")
|
||||
{
|
||||
if (avatar_list.Count == 0)
|
||||
avatar_list.AddRange(Resources.LoadAll<AvatarData>(folder));
|
||||
|
||||
avatar_list.Sort((AvatarData a, AvatarData b) => {
|
||||
if (a.sort_order == b.sort_order)
|
||||
return a.id.CompareTo(b.id);
|
||||
else
|
||||
return a.sort_order.CompareTo(b.sort_order);
|
||||
});
|
||||
}
|
||||
|
||||
public static AvatarData Get(string id)
|
||||
{
|
||||
foreach (AvatarData avatar in GetAll())
|
||||
{
|
||||
if (avatar.id == id)
|
||||
return avatar;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<AvatarData> GetAll()
|
||||
{
|
||||
return avatar_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/AvatarData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/AvatarData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c92c9d3c273cc8940be4b9e940d8933e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
316
Assets/TcgEngine/Scripts/Data/CardData.cs
Normal file
316
Assets/TcgEngine/Scripts/Data/CardData.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
public enum CardType
|
||||
{
|
||||
None = 0,
|
||||
Hero = 5,
|
||||
Character = 10,
|
||||
Spell = 20,
|
||||
Artifact = 30,
|
||||
Secret = 40,
|
||||
Equipment = 50,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines all card data
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "card", menuName = "TcgEngine/CardData", order = 5)]
|
||||
public class CardData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
|
||||
[Header("Display")]
|
||||
public string title;
|
||||
public Sprite art_full;
|
||||
public Sprite art_board;
|
||||
|
||||
[Header("Stats")]
|
||||
public CardType type;
|
||||
public TeamData team;
|
||||
public RarityData rarity;
|
||||
public int mana;
|
||||
public int attack;
|
||||
public int hp;
|
||||
|
||||
[Header("Traits")]
|
||||
public TraitData[] traits;
|
||||
public TraitStat[] stats;
|
||||
|
||||
[Header("Abilities")]
|
||||
public AbilityData[] abilities;
|
||||
|
||||
[Header("Card Text")]
|
||||
[TextArea(3, 5)]
|
||||
public string text;
|
||||
|
||||
[Header("Description")]
|
||||
[TextArea(5, 10)]
|
||||
public string desc;
|
||||
|
||||
[Header("FX")]
|
||||
public GameObject spawn_fx;
|
||||
public GameObject death_fx;
|
||||
public GameObject attack_fx;
|
||||
public GameObject damage_fx;
|
||||
public GameObject idle_fx;
|
||||
public AudioClip spawn_audio;
|
||||
public AudioClip death_audio;
|
||||
public AudioClip attack_audio;
|
||||
public AudioClip damage_audio;
|
||||
|
||||
[Header("Availability")]
|
||||
public bool deckbuilding = false;
|
||||
public int cost = 100;
|
||||
public PackData[] packs;
|
||||
|
||||
public static List<CardData> card_list = new List<CardData>(); //Faster access in loops
|
||||
public static Dictionary<string, CardData> card_dict = new Dictionary<string, CardData>(); //Faster access in Get(id)
|
||||
|
||||
public static void Load(string folder = "")
|
||||
{
|
||||
if (card_list.Count == 0)
|
||||
{
|
||||
card_list.AddRange(Resources.LoadAll<CardData>(folder));
|
||||
|
||||
foreach (CardData card in card_list)
|
||||
card_dict.Add(card.id, card);
|
||||
}
|
||||
}
|
||||
|
||||
public Sprite GetBoardArt(VariantData variant)
|
||||
{
|
||||
return art_board;
|
||||
}
|
||||
|
||||
public Sprite GetFullArt(VariantData variant)
|
||||
{
|
||||
return art_full;
|
||||
}
|
||||
|
||||
public string GetTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
|
||||
public string GetText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public string GetDesc()
|
||||
{
|
||||
return desc;
|
||||
}
|
||||
|
||||
public string GetTypeId()
|
||||
{
|
||||
if (type == CardType.Hero)
|
||||
return "hero";
|
||||
if (type == CardType.Character)
|
||||
return "character";
|
||||
if (type == CardType.Artifact)
|
||||
return "artifact";
|
||||
if (type == CardType.Spell)
|
||||
return "spell";
|
||||
if (type == CardType.Secret)
|
||||
return "secret";
|
||||
if (type == CardType.Equipment)
|
||||
return "equipment";
|
||||
return "";
|
||||
}
|
||||
|
||||
public string GetAbilitiesDesc()
|
||||
{
|
||||
string txt = "";
|
||||
foreach (AbilityData ability in abilities)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ability.desc))
|
||||
txt += "<b>" + ability.GetTitle() + ":</b> " + ability.GetDesc(this) + "\n";
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
public bool IsCharacter()
|
||||
{
|
||||
return type == CardType.Character;
|
||||
}
|
||||
|
||||
public bool IsSecret()
|
||||
{
|
||||
return type == CardType.Secret;
|
||||
}
|
||||
|
||||
public bool IsBoardCard()
|
||||
{
|
||||
return type == CardType.Character || type == CardType.Artifact;
|
||||
}
|
||||
|
||||
public bool IsRequireTarget()
|
||||
{
|
||||
return type == CardType.Equipment || IsRequireTargetSpell();
|
||||
}
|
||||
|
||||
public bool IsRequireTargetSpell()
|
||||
{
|
||||
return type == CardType.Spell && HasAbility(AbilityTrigger.OnPlay, AbilityTarget.PlayTarget);
|
||||
}
|
||||
|
||||
public bool IsEquipment()
|
||||
{
|
||||
return type == CardType.Equipment;
|
||||
}
|
||||
|
||||
public bool IsDynamicManaCost()
|
||||
{
|
||||
return mana > 99;
|
||||
}
|
||||
|
||||
public bool HasTrait(string trait)
|
||||
{
|
||||
foreach (TraitData t in traits)
|
||||
{
|
||||
if (t.id == trait)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasTrait(TraitData trait)
|
||||
{
|
||||
if(trait != null)
|
||||
return HasTrait(trait.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasStat(string trait)
|
||||
{
|
||||
if (stats == null)
|
||||
return false;
|
||||
|
||||
foreach (TraitStat stat in stats)
|
||||
{
|
||||
if (stat.trait.id == trait)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasStat(TraitData trait)
|
||||
{
|
||||
if(trait != null)
|
||||
return HasStat(trait.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetStat(string trait_id)
|
||||
{
|
||||
if (stats == null)
|
||||
return 0;
|
||||
|
||||
foreach (TraitStat stat in stats)
|
||||
{
|
||||
if (stat.trait.id == trait_id)
|
||||
return stat.value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetStat(TraitData trait)
|
||||
{
|
||||
if(trait != null)
|
||||
return GetStat(trait.id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool HasAbility(AbilityData tability)
|
||||
{
|
||||
foreach (AbilityData ability in abilities)
|
||||
{
|
||||
if (ability && ability.id == tability.id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasAbility(AbilityTrigger trigger)
|
||||
{
|
||||
foreach (AbilityData ability in abilities)
|
||||
{
|
||||
if (ability && ability.trigger == trigger)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasAbility(AbilityTrigger trigger, AbilityTarget target)
|
||||
{
|
||||
foreach (AbilityData ability in abilities)
|
||||
{
|
||||
if (ability && ability.trigger == trigger && ability.target == target)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AbilityData GetAbility(AbilityTrigger trigger)
|
||||
{
|
||||
foreach (AbilityData ability in abilities)
|
||||
{
|
||||
if (ability && ability.trigger == trigger)
|
||||
return ability;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasPack(PackData pack)
|
||||
{
|
||||
foreach (PackData apack in packs)
|
||||
{
|
||||
if (apack == pack)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static CardData Get(string id)
|
||||
{
|
||||
if (id == null)
|
||||
return null;
|
||||
bool success = card_dict.TryGetValue(id, out CardData card);
|
||||
if (success)
|
||||
return card;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<CardData> GetAllDeckbuilding()
|
||||
{
|
||||
List<CardData> multi_list = new List<CardData>();
|
||||
foreach (CardData acard in GetAll())
|
||||
{
|
||||
if (acard.deckbuilding)
|
||||
multi_list.Add(acard);
|
||||
}
|
||||
return multi_list;
|
||||
}
|
||||
|
||||
public static List<CardData> GetAll(PackData pack)
|
||||
{
|
||||
List<CardData> multi_list = new List<CardData>();
|
||||
foreach (CardData acard in GetAll())
|
||||
{
|
||||
if (acard.HasPack(pack))
|
||||
multi_list.Add(acard);
|
||||
}
|
||||
return multi_list;
|
||||
}
|
||||
|
||||
public static List<CardData> GetAll()
|
||||
{
|
||||
return card_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/CardData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/CardData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 843a21f7f6f205741a0a7341aec8e84d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/TcgEngine/Scripts/Data/CardbackData.cs
Normal file
49
Assets/TcgEngine/Scripts/Data/CardbackData.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all cardback data
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "Cardback", menuName = "TcgEngine/Cardback", order = 10)]
|
||||
public class CardbackData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
public Sprite cardback;
|
||||
public Sprite deck;
|
||||
public int sort_order;
|
||||
|
||||
public static List<CardbackData> cardback_list = new List<CardbackData>();
|
||||
|
||||
public static void Load(string folder = "")
|
||||
{
|
||||
if (cardback_list.Count == 0)
|
||||
cardback_list.AddRange(Resources.LoadAll<CardbackData>(folder));
|
||||
|
||||
cardback_list.Sort((CardbackData a, CardbackData b) => {
|
||||
if (a.sort_order == b.sort_order)
|
||||
return a.id.CompareTo(b.id);
|
||||
else
|
||||
return a.sort_order.CompareTo(b.sort_order);
|
||||
});
|
||||
}
|
||||
|
||||
public static CardbackData Get(string id)
|
||||
{
|
||||
foreach (CardbackData cardback in GetAll())
|
||||
{
|
||||
if (cardback.id == id)
|
||||
return cardback;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<CardbackData> GetAll()
|
||||
{
|
||||
return cardback_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/CardbackData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/CardbackData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b848b003e799d264cba32a383c514e74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
90
Assets/TcgEngine/Scripts/Data/ConditionData.cs
Normal file
90
Assets/TcgEngine/Scripts/Data/ConditionData.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all ability conditions, override the IsConditionMet function
|
||||
/// </summary>
|
||||
|
||||
public class ConditionData : ScriptableObject
|
||||
{
|
||||
public virtual bool IsTriggerConditionMet(Game data, AbilityData ability, Card caster)
|
||||
{
|
||||
return true; //Override this, applies to any target, always checked
|
||||
}
|
||||
|
||||
public virtual bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
return true; //Override this, condition targeting card
|
||||
}
|
||||
|
||||
public virtual bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
return true; //Override this, condition targeting player
|
||||
}
|
||||
|
||||
public virtual bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
return true; //Override this, condition targeting slot
|
||||
}
|
||||
|
||||
public virtual bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, CardData target)
|
||||
{
|
||||
return true; //Override this, for effects that create new cards
|
||||
}
|
||||
|
||||
public bool CompareBool(bool condition, ConditionOperatorBool oper)
|
||||
{
|
||||
if (oper == ConditionOperatorBool.IsFalse)
|
||||
return !condition;
|
||||
return condition;
|
||||
}
|
||||
|
||||
public bool CompareInt(int ival1, ConditionOperatorInt oper, int ival2)
|
||||
{
|
||||
if (oper == ConditionOperatorInt.Equal)
|
||||
{
|
||||
return ival1 == ival2;
|
||||
}
|
||||
if (oper == ConditionOperatorInt.NotEqual)
|
||||
{
|
||||
return ival1 != ival2;
|
||||
}
|
||||
if (oper == ConditionOperatorInt.GreaterEqual)
|
||||
{
|
||||
return ival1 >= ival2;
|
||||
}
|
||||
if (oper == ConditionOperatorInt.LessEqual)
|
||||
{
|
||||
return ival1 <= ival2;
|
||||
}
|
||||
if (oper == ConditionOperatorInt.Greater)
|
||||
{
|
||||
return ival1 > ival2;
|
||||
}
|
||||
if (oper == ConditionOperatorInt.Less)
|
||||
{
|
||||
return ival1 < ival2; ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConditionOperatorInt
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterEqual,
|
||||
LessEqual,
|
||||
Greater,
|
||||
Less,
|
||||
}
|
||||
|
||||
public enum ConditionOperatorBool
|
||||
{
|
||||
IsTrue,
|
||||
IsFalse,
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/ConditionData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/ConditionData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 110953428c5a7d942bb601a0ac3acf65
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
166
Assets/TcgEngine/Scripts/Data/DataLoader.cs
Normal file
166
Assets/TcgEngine/Scripts/Data/DataLoader.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Client;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This script initiates loading all the game data
|
||||
/// </summary>
|
||||
|
||||
public class DataLoader : MonoBehaviour
|
||||
{
|
||||
public GameplayData data;
|
||||
public AssetData assets;
|
||||
|
||||
private HashSet<string> card_ids = new HashSet<string>();
|
||||
private HashSet<string> ability_ids = new HashSet<string>();
|
||||
private HashSet<string> deck_ids = new HashSet<string>();
|
||||
|
||||
private static DataLoader instance;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
instance = this;
|
||||
LoadData();
|
||||
}
|
||||
|
||||
public void LoadData()
|
||||
{
|
||||
//To make loading faster, add a path inside each Load() function, relative to Resources folder
|
||||
//For example CardData.Load("Cards"); to only load data inside the Resources/Cards folder
|
||||
CardData.Load();
|
||||
TeamData.Load();
|
||||
RarityData.Load();
|
||||
TraitData.Load();
|
||||
VariantData.Load();
|
||||
PackData.Load();
|
||||
LevelData.Load();
|
||||
DeckData.Load();
|
||||
AbilityData.Load();
|
||||
StatusData.Load();
|
||||
AvatarData.Load();
|
||||
CardbackData.Load();
|
||||
RewardData.Load();
|
||||
|
||||
CheckCardData();
|
||||
CheckAbilityData();
|
||||
CheckDeckData();
|
||||
CheckVariantData();
|
||||
}
|
||||
|
||||
//Make sure the data is valid
|
||||
private void CheckCardData()
|
||||
{
|
||||
card_ids.Clear();
|
||||
foreach (CardData card in CardData.GetAll())
|
||||
{
|
||||
if (string.IsNullOrEmpty(card.id))
|
||||
Debug.LogError(card.name + " id is empty");
|
||||
if (card_ids.Contains(card.id))
|
||||
Debug.LogError("Dupplicate Card ID: " + card.id);
|
||||
|
||||
if (card.team == null)
|
||||
Debug.LogError(card.id + " team is null");
|
||||
if (card.rarity == null)
|
||||
Debug.LogError(card.id + " rarity is null");
|
||||
|
||||
foreach (TraitData trait in card.traits)
|
||||
{
|
||||
if (trait == null)
|
||||
Debug.LogError(card.id + " has null trait");
|
||||
}
|
||||
|
||||
if (card.stats != null)
|
||||
{
|
||||
foreach (TraitStat stat in card.stats)
|
||||
{
|
||||
if (stat.trait == null)
|
||||
Debug.LogError(card.id + " has null stat trait");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (AbilityData ability in card.abilities)
|
||||
{
|
||||
if(ability == null)
|
||||
Debug.LogError(card.id + " has null ability");
|
||||
}
|
||||
|
||||
card_ids.Add(card.id);
|
||||
}
|
||||
}
|
||||
|
||||
//Make sure the data is valid
|
||||
private void CheckAbilityData()
|
||||
{
|
||||
ability_ids.Clear();
|
||||
foreach (AbilityData ability in AbilityData.GetAll())
|
||||
{
|
||||
if (string.IsNullOrEmpty(ability.id))
|
||||
Debug.LogError(ability.name + " id is empty");
|
||||
if (ability_ids.Contains(ability.id))
|
||||
Debug.LogError("Dupplicate Ability ID: " + ability.id);
|
||||
|
||||
foreach (AbilityData chain in ability.chain_abilities)
|
||||
{
|
||||
if (chain == null)
|
||||
Debug.LogError(ability.id + " has null chain ability");
|
||||
}
|
||||
|
||||
ability_ids.Add(ability.id);
|
||||
}
|
||||
}
|
||||
|
||||
//Make sure the data is valid
|
||||
private void CheckDeckData()
|
||||
{
|
||||
GameplayData gdata = GameplayData.Get();
|
||||
CheckDeckArray(gdata.ai_decks);
|
||||
CheckDeckArray(gdata.free_decks);
|
||||
CheckDeckArray(gdata.starter_decks);
|
||||
|
||||
if(gdata.test_deck == null || gdata.test_deck_ai == null)
|
||||
Debug.Log("Deck is null in Resources/GameplayData");
|
||||
|
||||
deck_ids.Clear();
|
||||
foreach (DeckData deck in DeckData.GetAll())
|
||||
{
|
||||
if (string.IsNullOrEmpty(deck.id))
|
||||
Debug.LogError(deck.name + " id is empty");
|
||||
if (deck_ids.Contains(deck.id))
|
||||
Debug.LogError("Dupplicate Deck ID: " + deck.id);
|
||||
|
||||
foreach (CardData card in deck.cards)
|
||||
{
|
||||
if (card == null)
|
||||
Debug.LogError(deck.id + " has null card");
|
||||
}
|
||||
|
||||
deck_ids.Add(deck.id);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckDeckArray(DeckData[] decks)
|
||||
{
|
||||
foreach (DeckData deck in decks)
|
||||
{
|
||||
if (deck == null)
|
||||
Debug.Log("Deck is null in Resources/GameplayData");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckVariantData()
|
||||
{
|
||||
VariantData dvariant = VariantData.GetDefault();
|
||||
if(dvariant == null)
|
||||
Debug.LogError("No default variant data found, make sure you have a default VariantData");
|
||||
}
|
||||
|
||||
public static DataLoader Get()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/DataLoader.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/DataLoader.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b8b650ea59aaba4a81d2630d662eabc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
56
Assets/TcgEngine/Scripts/Data/DeckData.cs
Normal file
56
Assets/TcgEngine/Scripts/Data/DeckData.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all fixed deck data (for user custom decks, check UserData.cs)
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(fileName = "DeckData", menuName = "TcgEngine/DeckData", order = 7)]
|
||||
public class DeckData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
|
||||
[Header("Display")]
|
||||
public string title;
|
||||
|
||||
[Header("Cards")]
|
||||
public CardData hero;
|
||||
public CardData[] cards;
|
||||
|
||||
public static List<DeckData> deck_list = new List<DeckData>();
|
||||
|
||||
public static void Load(string folder = "")
|
||||
{
|
||||
if(deck_list.Count == 0)
|
||||
deck_list.AddRange(Resources.LoadAll<DeckData>(folder));
|
||||
}
|
||||
|
||||
public int GetQuantity()
|
||||
{
|
||||
return cards.Length;
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return cards.Length >= GameplayData.Get().deck_size;
|
||||
}
|
||||
|
||||
public static DeckData Get(string id)
|
||||
{
|
||||
foreach (DeckData deck in GetAll())
|
||||
{
|
||||
if (deck.id == id)
|
||||
return deck;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<DeckData> GetAll()
|
||||
{
|
||||
return deck_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/DeckData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/DeckData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87ecef176d27eb040a9770e0eefea2ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
37
Assets/TcgEngine/Scripts/Data/DeckPuzzleData.cs
Normal file
37
Assets/TcgEngine/Scripts/Data/DeckPuzzleData.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Deck with more fields for having specific cards or starting board cards
|
||||
/// </summary>
|
||||
|
||||
[System.Serializable]
|
||||
public class DeckCardSlot
|
||||
{
|
||||
public CardData card;
|
||||
public SlotXY slot;
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "DeckPuzzleData", menuName = "TcgEngine/DeckPuzzleData", order = 7)]
|
||||
public class DeckPuzzleData : DeckData
|
||||
{
|
||||
public DeckCardSlot[] board_cards;
|
||||
public int start_cards = 5;
|
||||
public int start_mana = 2;
|
||||
public int start_hp = 20;
|
||||
public bool dont_shuffle_deck;
|
||||
|
||||
public static new DeckPuzzleData Get(string id)
|
||||
{
|
||||
foreach (DeckData deck in GetAll())
|
||||
{
|
||||
if (deck.id == id && deck is DeckPuzzleData)
|
||||
return (DeckPuzzleData) deck;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/DeckPuzzleData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/DeckPuzzleData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 069f5be85a1dcec46b083fc9c26e6870
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
65
Assets/TcgEngine/Scripts/Data/EffectData.cs
Normal file
65
Assets/TcgEngine/Scripts/Data/EffectData.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using TcgEngine.Gameplay;
|
||||
|
||||
namespace TcgEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all ability effects, override the IsConditionMet function
|
||||
/// </summary>
|
||||
|
||||
public class EffectData : ScriptableObject
|
||||
{
|
||||
public virtual void DoEffect(GameLogic logic, AbilityData ability, Card caster)
|
||||
{
|
||||
//Server side gameplay logic
|
||||
}
|
||||
|
||||
public virtual void DoEffect(GameLogic logic, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
//Server side gameplay logic
|
||||
}
|
||||
|
||||
public virtual void DoEffect(GameLogic logic, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
//Server side gameplay logic
|
||||
}
|
||||
|
||||
public virtual void DoEffect(GameLogic logic, AbilityData ability, Card caster, Slot target)
|
||||
{
|
||||
//Server side gameplay logic
|
||||
}
|
||||
|
||||
public virtual void DoEffect(GameLogic logic, AbilityData ability, Card caster, CardData target)
|
||||
{
|
||||
//Server side gameplay logic
|
||||
}
|
||||
|
||||
public virtual void DoOngoingEffect(GameLogic logic, AbilityData ability, Card caster, Card target)
|
||||
{
|
||||
//Ongoing effect only
|
||||
}
|
||||
|
||||
public virtual void DoOngoingEffect(GameLogic logic, AbilityData ability, Card caster, Player target)
|
||||
{
|
||||
//Ongoing effect only
|
||||
}
|
||||
|
||||
public int AddOrSet(int original_val, EffectOperatorInt oper, int add_value)
|
||||
{
|
||||
if (oper == EffectOperatorInt.Add)
|
||||
return original_val + add_value;
|
||||
if (oper == EffectOperatorInt.Set)
|
||||
return add_value;
|
||||
return original_val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum EffectOperatorInt
|
||||
{
|
||||
Add,
|
||||
Set,
|
||||
}
|
||||
}
|
||||
11
Assets/TcgEngine/Scripts/Data/EffectData.cs.meta
Normal file
11
Assets/TcgEngine/Scripts/Data/EffectData.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9b30fc8e458fec41a859f97cd4db714
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user