My complex and extendable skill type

 Let`s dive into the skill type implementation I coded for a more generalized approach to handle unique character skills and their pool effects.

The abilities could include but not limited to:

Stun :

Stuns the enemy and in which case the enemy misssed the turn the generalize method takes into account the case in which a hero ability could stun ally and enemy heroes at the same time with different stun turns. This twist should be implemented in a way that Turn manager loops through all the pool effects and processes the pool effects befor ending the turn.

Burn:

This pool effect means before ending the turn characters affecetd by bleed skill must reduce their health based on the amount described in their scriptable objects according to hero level and skill`s upgraded strength.

Countdown Tile:

This implemented feature adds a countdown tile oto the board and the code for it is generalized enough to take into account differet actions that could be triggered on countdown tile timer tick down or ReachedZero event. In each case you can choose the unique skill type from the inspector (thanks to Odin Inspector) and allow the countdown tile to trigger an effect or repeat its timer.

Atk or Defense tiles:

This skill type casts a spell and drops multiple “attack buff” or “defense buff” tiles on the board and this actually depends on the hero`s scriptable objects and their unique atk or defense tiles. For example one skill could drop 3 attack tile for friendly player and 2 defense tiles for enemy players. There could be alot more varities in skill setup, and all we need to do is just check some booleans and enter some numbers in the inspector for the ability to adjust.

As you can see in top figure I have added Frontier and Countdown ability simply by adding a new abstract skill type and this ability of frontier drops a CountDownTile as an ability to the board which is triggered on ReachedZero and removes the frontier ability and the hero no loger takes the whole damage in frontline! The skills are actually working as you can see in my demo  (LINK)

There are much more twists to these setups that I coded for the cryptic clash but I would like to share some of the hero ability`s setup and see how we could use them in action :

This is my HeroAttack class which is the base for all the hero spell initiatives:

As you can see in HeroPowerQueueCommand I put all my incidents inside a command objects. This generalized setup helps you to put a match-3 swap command or a unique skill command to be sent over network using the followinf methods in command.cs:

				
					public override void Execute()
    {
 
        if (CommandManager.Instance.ActionQueue.Any())
        {
            GameEventManager._abstractEvents[CommandManager.Instance.ActionQueue.Dequeue()].ProcessEvent(this); 
        } 
    }

  
    public override void AddCommand()
    {
        if(!isValidCommand())
        {
            DropCommand();
            return;
        }
        TakeCommandResourcesForLaterUse();
        CommandManager.Instance.ActionQueue.Enqueue(GameEventType.FirePower);
        CommandManager.Instance.heroPowerQueue.Enqueue(heroAttack);
    }

    public override void DropCommand()
    {
        CommandManager.Instance.m_CommandsBuffer.Dequeue();
    }

    public override void TakeCommandResourcesForLaterUse()
    {
        ActivateSkill.instance.TakeFirePowerResources(heroAttack);
    }

    public override ICommandDataHolder GetCommandData()
    {
        return heroAttack;
    }
				
			
				
					public class HeroAttack : RealtimeServer.MessageBase,ICommandDataHolder
    {
        // int32 section
        [HideInInspector] public int positionInList;
        [HideInInspector] public ColorPiece.ColorType SkillColorType;
        [HideInInspector] public int agentNumber;
        [HideInInspector] public int skillNumber;
        [HideInInspector] public int baseExpectedLevel;
        [HideInInspector] public int skillLevel;
    
        // string section
 


        // boolean section
        [HideInInspector] public bool isHost;
        [HideInInspector] public bool[] isFriendly;

        [HideInInspector] public bool executeByHost;
        [HideInInspector] public bool isFromServer;

        [OdinSerialize] public AbstractSkillType[] _abstractSkillTypes;
        [OdinSerialize] public Command command;

       
        public override void Serialize(BinaryWriter writer)
        {
            //ints
            if (_abstractSkillTypes == null)
                writer.Write(-1);
            else
            {
                writer.Write(_abstractSkillTypes.Length);
            }

            writer.Write(positionInList);
            writer.Write((int)SkillColorType);
            writer.Write(agentNumber);
            writer.Write(skillNumber);
            writer.Write(baseExpectedLevel);
            writer.Write(skillLevel);
        

            //strings
       
            //booleans
            writer.Write(isHost);
         

            writer.Write(executeByHost);
            writer.Write(isFromServer);
          

            if (_abstractSkillTypes != null)
            {

                for (int i = 0; i < _abstractSkillTypes.Length; i++)
                {
                    writer.Write((int)_abstractSkillTypes[i].skillType);
                    _abstractSkillTypes[i].Serialize(writer);
                }
            }
        
        }

        public override void Deserialize(BinaryReader reader)
        {
            //ints
            int count = reader.ReadInt32();
           
            positionInList = reader.ReadInt32();
            SkillColorType =  (ColorPiece.ColorType) reader.ReadInt32();
            agentNumber = reader.ReadInt32();
            skillNumber = reader.ReadInt32();
            baseExpectedLevel = reader.ReadInt32();
            skillLevel = reader.ReadInt32();
         
            //strings
       
   
            //booleans
            isHost = reader.ReadBoolean();
        
            executeByHost = reader.ReadBoolean();
            isFromServer = reader.ReadBoolean();
           

            if (count != -1 )
            {
                _abstractSkillTypes = new AbstractSkillType[count];
                for (int i = 0; i < count; i++)
                {
                    var skillType = (SkillType)reader.ReadInt32();
                    _abstractSkillTypes[i] = SkillTypeManager.instance._abstractSkillTypes[skillType];
                    _abstractSkillTypes[i].Deserialize(reader);
                }
            }

            command = new HeroPowerQueueCommand(executeByHost, isFromServer, this);
        }
    }
				
			
				
					using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.IO;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UnityEngine;

public class DamageAbility : AbstractSkillType
{
    int[] levelArray;


    [ShowInInspector] public DamageClassInfo _damageClassInfo;


    public override SkillType skillType
    {
       get => SkillType.DamageAbility;
    //   set => skillType = value;
    }

    public override void ProcessSkill(ApadanaFirst.HeroAttack heroAttack)
    {
        List<TargetHeroType> listInSKillLevel = new List<TargetHeroType>(_damageClassInfo.targetIndexType[heroAttack.skillLevel]) ;
        CreatureGameAgent thePerformer;
        bool amIPerformerClient = SkillTypeManager.instance.AmIPerformerClient(heroAttack);

        if (amIPerformerClient)
        {
            thePerformer = Grid.Instance.myAgents[heroAttack.agentNumber];
        }
        else
        {
            thePerformer = Grid.Instance.enemyAgents[heroAttack.agentNumber];
        }
       
        SplineManager.instance.UpdateHeroIndexes();
        for (int i = 0; i < listInSKillLevel.Count; i++)
        {
            TargetHeroType targetHeroType = listInSKillLevel[i];
            Debug.Log(thePerformer.data.currentLevel + " / " + heroAttack.baseExpectedLevel + " / " +
                      targetHeroType.damageMaxlevel +"/"+ targetHeroType.damage);

            int damageApplied = GameMathHelper.CalculateValueOnLevel(thePerformer.data.currentLevel,
                heroAttack.baseExpectedLevel,
                thePerformer.data.maxLevel,
                targetHeroType.damage, targetHeroType.damageMaxlevel);

            if (!amIPerformerClient)
            {
                targetHeroType.IsAlly = ! targetHeroType.IsAlly;
            }

            Debug.Log($"listInSKillLevel[i].IsAlly {targetHeroType.IsAlly},of i: {i}");
            int index = SkillTypeManager.instance._targetDirectionDict[targetHeroType.targetDirectionType].ReturnIndex(targetHeroType);
            if (targetHeroType.IsAlly)
            {
                SkillTypeManager.instance.gridRef.myAgents[index]
                    .ApplyDamage(damageApplied, targetHeroType.IsAlly);
            }
            else
            {
                SkillTypeManager.instance.gridRef.enemyAgents[index]
                    .ApplyDamage(damageApplied, targetHeroType.IsAlly);
            }
        }
    }

    public override void ProcessOffline( ApadanaFirst.HeroAttack heroAttack,bool isFriendly, CreatureGameAgent agent, GamePiece piece)
    {
        List<TargetHeroType> listInSKillLevel = new List<TargetHeroType>(_damageClassInfo.targetIndexType[heroAttack.skillLevel]) ;
     
       
        SplineManager.instance.UpdateHeroIndexes();
        for (int i = 0; i < listInSKillLevel.Count; i++)
        {
            TargetHeroType targetHeroType = listInSKillLevel[i];
            Debug.Log(agent.data.currentLevel + " / " + heroAttack.baseExpectedLevel + " / " +
                      targetHeroType.damageMaxlevel +"/"+ targetHeroType.damage);

            int damageApplied = GameMathHelper.CalculateValueOnLevel(agent.data.currentLevel,
                heroAttack.baseExpectedLevel,
                agent.data.maxLevel,
                targetHeroType.damage, targetHeroType.damageMaxlevel);

            if ((Grid.Instance.isHost && !piece.hostIsActivator)||(!Grid.Instance.isHost && piece.hostIsActivator))
            {
                targetHeroType.IsAlly = !targetHeroType.IsAlly;
            }

            Debug.Log($"listInSKillLevel[i].IsAlly {targetHeroType.IsAlly},of i: {i}");
            int index = SkillTypeManager.instance._targetDirectionDict[targetHeroType.targetDirectionType].ReturnIndex(targetHeroType);
            if (targetHeroType.IsAlly)
            {
                SkillTypeManager.instance.gridRef.myAgents[index]
                    .ApplyDamage(damageApplied, targetHeroType.IsAlly);
            }
            else
            {
                SkillTypeManager.instance.gridRef.enemyAgents[index]
                    .ApplyDamage(damageApplied, targetHeroType.IsAlly);
            }
        }
    }

    public override void ProcessClear(ApadanaFirst.HeroAttack heroAttack, bool isFriendly, CreatureGameAgent agent, GamePiece piece)
    {
// nothing
    }

    public override void Serialize(BinaryWriter writer)
    {
     //   _damageClassInfo.SetDamageClassInfo();
        writer.Write(_damageClassInfo.targetIndexType.Count);

        for (int i = 0; i < _damageClassInfo.targetIndexType.Count; i++)
        { 
            writer.Write(_damageClassInfo.targetIndexType[i].Count);
            for (int j = 0; j < _damageClassInfo.targetIndexType[i].Count; j++)
            {
               

                _damageClassInfo.targetIndexType[i][j].Serialize(writer);
            }
        }





    }

    public override void Deserialize(BinaryReader reader)
    {
        _damageClassInfo = new DamageClassInfo();
        int skillCounts = reader.ReadInt32();
        List<TargetHeroType> lowerList = new  List<TargetHeroType>(skillCounts);
        
        _damageClassInfo.targetIndexType = new List<List<TargetHeroType>>();
        
        for (int i = 0; i < skillCounts; i++)
        {
            lowerList.Clear();
            int heroTypeCounts = reader.ReadInt32();
            for (int j = 0; j < heroTypeCounts; j++)
            {
                lowerList.Add(new TargetHeroType());
                lowerList[j].Deserialize(reader);
            }
            _damageClassInfo.targetIndexType.Add(lowerList);
        }

    //    _damageClassInfo.SetDamageClassInfo();



    }

    public override void InitializeTheSkill()
    {
       // throw new NotImplementedException();
    }
}
				
			

Leave a Reply

Your email address will not be published. Required fields are marked *