//==============================================================================
//  NicePack / NiceAbilityManager
//==============================================================================
//  Class that manager active abilities, introduced along with a NicePack.
//  Can support at most 5 ('maxAbilitiesAmount') different abilities at once.
//  NICETODO: refactor later
//==============================================================================
//  'Nice pack' source
//  Do whatever the fuck you want with it
//  Author: dkanus
//  E-mail: dkanus@gmail.com
//==============================================================================
class NiceAbilityManager extends Actor;
var const int maxAbilitiesAmount;
//  Defines a list of all possible ability's states
enum EAbilityState{
    //  Ability is ready to use
    ASTATE_READY,
    //  Ability is being used
    ASTATE_ACTIVE,
    //  Ability is on cooldown
    ASTATE_COOLDOWN
};
//  Describes all the necessary information about an ability
struct NiceAbilityDescription{
    //  Ability's ID, supposed to be unique per ability,
    //      but no checks are enforced yet
    var string  ID;
    //  Image to be used as an ability's icon
    var Texture icon;
    //  Default cooldown duration
    var int cooldownLength;
    //  Can ability be canceled once activated?
    var bool canBeCancelled;
};
//  Complete description of current status of an ability,
//      including it's complete description.
struct NiceAbilityStatus{
    //  Complete description of ability in question
    var NiceAbilityDescription description;
    //  Current cooldown value
    var int cooldown;
    //  Current state of an ability
    var EAbilityState myState;
};
var NiceAbilityStatus   currentAbilities[5];
var int                 currentAbilitiesAmount;
//  Refers to the player whose abilities we manage
var NicePlayerController relatedPlayer;
var const class<NiceAbilitiesEvents> events;
//  Unfortunately this hackk is required to force replication of structure array
var int hackCounter;
replication{
    reliable if(Role == ROLE_Authority)
       currentAbilities, currentAbilitiesAmount, hackCounter;
}
simulated function PostBeginPlay(){
    relatedPlayer = NicePlayerController(owner);
}
function AddAbility(NiceAbilityDescription description){
    local int               i;
    local NiceAbilityStatus newRecord;
    if(currentAbilitiesAmount >= maxAbilitiesAmount) return;
    for(i = 0;i < currentAbilitiesAmount;i ++)
       if(currentAbilities[i].description.ID ~= description.ID)
           return;
    newRecord.description   = description;
    newRecord.cooldown      = 0;
    newRecord.myState       = ASTATE_READY;
    currentAbilities[currentAbilitiesAmount] = newRecord;
    currentAbilitiesAmount += 1;
    events.static.CallAbilityAdded(description.ID, relatedPlayer);
    netUpdateTime = level.timeSeconds - 1;
}
function RemoveAbility(string abilityID){
    local int   i, j;
    local bool  wasRemoved;
    j = 0;
    for(i = 0;i < currentAbilitiesAmount;i ++){
       if(currentAbilities[i].description.ID ~= abilityID){
           wasRemoved = true;
           continue;
       }
       currentAbilities[j] = currentAbilities[i];
       j += 1;
    }
    currentAbilitiesAmount = j;
    if(wasRemoved)
       events.static.CallAbilityRemoved(abilityID, relatedPlayer);
    netUpdateTime = level.timeSeconds - 1;
}
function ClearAbilities(){
    currentAbilitiesAmount = 0;
    netUpdateTime = level.timeSeconds - 1;
}
//  Returns index of the ability with a given name.
//  Returns '-1' if such ability doesn't exist.
simulated function int GetAbilityIndex(string abilityID){
    local int i;
    for(i = 0;i < currentAbilitiesAmount;i ++)
       if(currentAbilities[i].description.ID ~= abilityID)
           return i;
    return -1;
}
simulated function bool IsAbilityActive(string abilityID){
    local int index;
    index = GetAbilityIndex(abilityID);
    if(index < 0)
       return false;
    return (currentAbilities[index].myState == ASTATE_ACTIVE);
}
//  Sets ability to a proper state.
//  Does nothing if ability is already in a specified state.
//  Setting active ability to a ready state is only allowed
//      if ability can be canceled.
//  Updates cooldown to full length if new state is 'ASTATE_COOLDOWN'.
function SetAbilityState(int abilityIndex, EAbilityState newState){
    local float         cooldown;
    local EAbilityState currentState;
    if(abilityIndex < 0 || abilityIndex >= currentAbilitiesAmount) return;
    currentState = currentAbilities[abilityIndex].myState;
    if(currentState == newState)
        return;
    if(     currentState == ASTATE_ACTIVE && newState == ASTATE_READY
        &&  !currentAbilities[abilityIndex].description.canBeCancelled)
        return;
    currentAbilities[abilityIndex].myState = newState;
    if(newState == ASTATE_COOLDOWN){
        cooldown = currentAbilities[abilityIndex].description.cooldownLength;
        events.static.CallModAbilityCooldown(
            currentAbilities[abilityIndex].description.ID,
            relatedPlayer,
            cooldown
        );
        currentAbilities[abilityIndex].cooldown = cooldown;
    }
    hackCounter ++;
    netUpdateTime = level.timeSeconds - 1;
    //  Fire off events
    if(newState == ASTATE_ACTIVE){
        events.static.CallAbilityActivated(
            currentAbilities[abilityIndex].description.ID,
            relatedPlayer
        );
    }
}
//  Changes ability's cooldown by a given amount.
//  If this brings cooldown to zero or below -
//      resets current ability to a 'ready' (ASTATE_READY) state.
function AddToCooldown(int abilityIndex, int delta){
    if(abilityIndex < 0 || abilityIndex >= currentAbilitiesAmount)  return;
    if(currentAbilities[abilityIndex].myState != ASTATE_COOLDOWN)   return;
    currentAbilities[abilityIndex].cooldown += delta;
    if(currentAbilities[abilityIndex].cooldown <= 0)
       SetAbilityState(abilityIndex, ASTATE_READY);
    hackCounter ++;
}

function AddToAllCooldowns(int delta){
    local int i;

    for (i = 0; i < currentAbilitiesAmount; i += 1) {
        if (currentAbilities[i].myState == ASTATE_COOLDOWN) {
            currentAbilities[i].cooldown += delta;
            if(currentAbilities[i].cooldown <= 0) {
                SetAbilityState(i, ASTATE_READY);
            }
        }
    }
    hackCounter ++;
}

defaultproperties
{
    maxAbilitiesAmount=5
    Events=class'NiceAbilitiesEvents'
    DrawType=DT_None
}
