/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:           volclassedit.inc
 *  Type:           Module
 *  Description:    Class editor volumetric feature.
 *
 *  Copyright (C) 2009-2013  Greyscale, Richard Helgeby
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ============================================================================
 */

#define VOL_CLASSNAME_SIZE  64

/**
 * Empty attribute structure with all settings set to be ignored.
 */
new VolEmptyAttributes[ClassEditableAttributes] = {
    -1,                             /** ModelSkinIndex */
    -1,                             /** AlphaInitial */
    -1,                             /** AlphaDamaged */
    -1,                             /** AlphaDamage */

    "nochange",                     /** OverlayPath */
    -1,                             /** Nvgs */
    -1,                             /** Fov */

    -1,                             /** HasNapalm */
    -1.0,                           /** NapalmTime */
    Immunity_Invalid,               /** ImmunityMode */
    -1,                             /** ImmunityAmount */
    -1,                             /** ImmunityCooldown */
    -1,                             /** NoFallDamage */

    -1.0,                           /** RegenInterval */
    -1,                             /** RegenAmount */
    -1,                             /** InfectGain */
    -1,                             /** KillBonus */

    -1.0,                           /** Speed */
    ZR_CLASS_KNOCKBACK_IGNORE,      /** KnockBack */
    -1.0,                           /** JumpHeight */
    -1.0,                           /** JumpDistance */
};

/**
 * List of class edit modes.
 */
enum VolClassEditMode
{
    ClassEditMode_Name,         /** Change whole player class. */
    ClassEditMode_Attributes    /** Override class attributes. */
}

/**
 * Data structure for class editor.
 */
enum VolTypeClassEdit
{
    bool:VolClassEdit_InUse,

    VolClassEditMode:VolClassEdit_Mode,
    String:VolClassEdit_ClassName[VOL_CLASSNAME_SIZE],
    VolClassEdit_ClassData[ClassEditableAttributes]
}

/**
 * Class editor data.
 */
new VolClassEditData[ZR_VOLUMES_MAX][VolTypeClassEdit];

/**
 * The player's selected class index. Used by volumes in "name" mode.
 */
new VolClassEditSelectedClass[MAXPLAYERS + 1];

/**
 * Checks if the specified index is in use.
 *
 * @param dataIndex     Index to check.
 * @return              True if in use, false otherwise.
 */
bool:VolClassEditInUse(dataIndex)
{
    return VolClassEditData[dataIndex][VolClassEdit_InUse];
}

/**
 * Gets the first free data index.
 *
 * @return  Data index or -1 if failed.
 */
VolClassEditGetFreeIndex()
{
    // Loop through all indexes.
    for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++)
    {
        // Check if unused.
        if (!VolClassEditInUse(dataindex))
        {
            // Mark as in use.
            VolClassEditData[dataindex][VolClassEdit_InUse] = true;

            // Unused index found.
            return dataindex;
        }
    }

    // Unused index not found.
    return -1;
}

VolClassEditReset(dataIndex)
{
    VolClassEditData[dataIndex][VolClassEdit_InUse] = false;
    VolClassEditData[dataIndex][VolClassEdit_Mode] = ClassEditMode_Attributes;

    strcopy(VolClassEditData[dataIndex][VolClassEdit_ClassName], 64, "");
    VolClassEditData[dataIndex][VolClassEdit_ClassData] = VolEmptyAttributes;
}

/**
 * Initialize class editor.
 */
VolClassEditInit()
{
    for (new dataindex = 0; dataindex < ZR_VOLUMES_MAX; dataindex++)
    {
        VolClassEditReset(dataindex);
    }
}

/**
 * Sets class editor spesific attributes on a anticamp volume.
 *
 * @param dataIndex     Local data index.
 * @param attribName    Attribute to modify.
 * @param attribVlue    Attribute value to set.
 * @return              True if successfully set, false otherwise.
 */
VolClassEditSetAttribute(dataIndex, const String:attribName[], const String:attribValue[])
{
    // Validate index.
    if (!VolIsValidIndex(dataIndex))
    {
        return false;
    }

    /* Class Editor Attributes */
    if (StrEqual(attribName, "mode", false))
    {
        if (VolClassEditSetMode(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "name", false))
    {
        if (VolClassEditSetName(dataIndex, attribValue))
        {
            return true;
        }
    }

    /* Model */
    else if (StrEqual(attribName, "alpha_initial", false))
    {
        if (VolClassEditSetAlphaInitial(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "alpha_damaged", false))
    {
        if (VolClassEditSetAlphaDamaged(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "alpha_damage", false))
    {
        if (VolClassEditSetAlphaDamage(dataIndex, attribValue))
        {
            return true;
        }
    }

    /* Hud */
    else if (StrEqual(attribName, "overlay_path", false))
    {
        if (VolClassEditSetOverlayPath(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "nvgs", false))
    {
        if (VolClassEditSetNvgs(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "fov", false))
    {
        if (VolClassEditSetFov(dataIndex, attribValue))
        {
            return true;
        }
    }

    /* Effects */
    else if (StrEqual(attribName, "has_napalm", false))
    {
        if (VolClassEditSetHasNapalm(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "napalm_time", false))
    {
        if (VolClassEditSetNapalmTime(dataIndex, attribValue))
        {
            return true;
        }
    }

    /* Player behavior */
    else if (StrEqual(attribName, "immunity_mode", false))
    {
        if (VolClassEditSetImmunityMode(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "immunity_amount", false))
    {
        if (VolClassEditSetImmunityAmount(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "immunity_cooldown", false))
    {
        if (VolClassEditSetImmunityCooldown(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "no_fall_damage", false))
    {
        if (VolClassEditSetNoFallDamage(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "health_regen_interval", false))
    {
        if (VolClassEditSetRegenInterval(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "health_regen_amount", false))
    {
        if (VolClassEditSetRegenAmount(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "infect_gain", false))
    {
        if (VolClassEditSetInfectGain(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "kill_bonus", false))
    {
        if (VolClassEditSetKillBonus(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "speed", false))
    {
        if (VolClassEditSetSpeed(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "knockback", false))
    {
        if (VolClassEditSetKnockBack(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "jump_height", false))
    {
        if (VolClassEditSetJumpHeight(dataIndex, attribValue))
        {
            return true;
        }
    }
    else if (StrEqual(attribName, "jump_distance", false))
    {
        if (VolClassEditSetJumpDistance(dataIndex, attribValue))
        {
            return true;
        }
    }

    // Invalid attribute name or empty value.
    return false;
}

/**
 * Dumps data to be used by zr_vol_list command.
 *
 * @param dataIndex     Index in anticamp data array.
 * @param buffer        Destination string buffer.
 * @param maxlen        Size of destination buffer.
 * @return              Number of cells written.
 */
VolClassEditDumpData(dataIndex, String:buffer[], maxlen)
{
    #define CLASSEDIT_DUMP_FORMAT "%-19s %s\n"

    decl String:linebuffer[128];
    decl String:valuebuffer[256];
    new cache[VolTypeClassEdit];
    new cellswritten;

    // Validate index.
    if (!VolIsValidIndex(dataIndex))
    {
        return 0;
    }

    // Initialize and clear buffer.
    buffer[0] = 0;

    // Cache data.
    cache = VolClassEditData[dataIndex];

    VolClassEditModeToString(cache[VolClassEdit_Mode], valuebuffer, sizeof(valuebuffer));
    Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Mode:", valuebuffer);
    cellswritten += StrCat(buffer, maxlen, linebuffer);

    switch (cache[VolClassEdit_Mode])
    {
        case ClassEditMode_Name:
        {
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Class Name:", cache[VolClassEdit_ClassName]);
            cellswritten += StrCat(buffer, maxlen, linebuffer);
        }
        case ClassEditMode_Attributes:
        {
            VolClassEditIntToString(dataIndex, ClassEdit_AlphaInitial, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Alpha initial:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_AlphaDamaged, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Alpha damaged:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_AlphaDamage, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Alpha damage:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditStringToHumanStr(dataIndex, ClassEdit_OverlayPath, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Overlay path:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_Nvgs, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "NVGs:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_Fov, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "FOV:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_HasNapalm, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Has napalm:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_NapalmTime, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Napalm time:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            ImmunityModeToString(VolClassEditData[dataIndex][ClassEdit_ImmunityMode], valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Immunity mode:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_ImmunityAmount, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Immunity amount:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_ImmunityCooldown, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Immunity cooldown:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_NoFallDamage, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "No fall damage:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_RegenInterval, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Regen interval:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_RegenAmount, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Regen amount:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_InfectGain, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Infect gain:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditIntToString(dataIndex, ClassEdit_KillBonus, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Kill bonus:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_Speed, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Speed:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_KnockBack, valuebuffer, sizeof(valuebuffer), ZR_CLASS_KNOCKBACK_IGNORE);
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Knock back:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_JumpHeight, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Jump height:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);

            VolClassEditFloatToString(dataIndex, ClassEdit_JumpDistance, valuebuffer, sizeof(valuebuffer));
            Format(linebuffer, sizeof(linebuffer), CLASSEDIT_DUMP_FORMAT, "Jump distance:", valuebuffer);
            cellswritten += StrCat(buffer, maxlen, linebuffer);
        }
    }

    return cellswritten;
}


/**************************************
 *                                    *
 *  EVENTS                            *
 *                                    *
 **************************************/

/**
 * Applies new class attributes to the player.
 *
 * @param client        The client index.
 * @param dataIndex     Local data index.
 */
VolClassEditApply(client, dataIndex)
{
    switch (VolClassEditData[dataIndex][VolClassEdit_Mode])
    {
        case ClassEditMode_Name:
        {
            // Cache volume attributes.
            new classindex = ClassGetIndexByName(VolClassEditData[dataIndex][VolClassEdit_ClassName]);

            // Save player's selected class.
            VolClassEditSelectedClass[client] = ClassGetActiveIndex(client);

            // Update cache with new attributes.
            ClassReloadPlayerCache(client, classindex);
        }
        case ClassEditMode_Attributes:
        {
            // Update player's entire cache with attributes from the specified
            // class attributes.
            VolClassEditUpdateAttributes(client, VolClassEditData[dataIndex][VolClassEdit_ClassData]);
        }
    }

    LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "ClassEdit", "Applied class data on client %d.", client);

    // Apply the updated attributes.
    ClassApplyAttributes(client);
}

/**
 * Restores the player's regular class attributes (from modified cache).
 *
 * @param client        The client index.
 */
VolClassEditRestore(client, dataIndex)
{
    new classindex = ClassGetActiveIndex(client);

    switch (VolClassEditData[dataIndex][VolClassEdit_Mode])
    {
        case ClassEditMode_Name:
        {
            // Restore player's entire cache with attributes from the selected
            // class.
            ClassReloadPlayerCache(client, VolClassEditSelectedClass[client]);
        }
        case ClassEditMode_Attributes:
        {
            // Restore individual attributes, if specified.
            VolClassEditRestoreAttributes(client, classindex, VolClassEditData[dataIndex][VolClassEdit_ClassData]);
        }
    }

    // Apply the restored attributes.
    if (ClassApplyAttributes(client))
    {
        LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "ClassEdit", "Restored class data on client %d.", client);
    }
}

/**
 * Event callback. A player entered a class edit volume.
 *
 * @param client        The client index.
 * @param volumeIndex   The volume index.
 */
VolClassEditOnPlayerEnter(client, volumeIndex)
{
    VolClassEditApply(client, Volumes[volumeIndex][Vol_DataIndex]);
}

/**
 * Event callback. A player left a class edit volume.
 *
 * @param client        The client index.
 * @param volumeIndex   The volume index.
 */
VolClassEditOnPlayerLeave(client, volumeIndex)
{
    VolClassEditRestore(client, Volumes[volumeIndex][Vol_DataIndex]);
}

/**
 * Event callback. A class edit volume was enabled.
 *
 * @param volumeIndex   The volume index.
 */
/*VolClassEditOnEnabled(volumeIndex)
{

}*/

/**
 * Event callback. A class edit volume was enabled.
 *
 * @param volumeIndex   The volume index.
 */
VolClassEditOnDisabled(volumeIndex)
{
    for (new client = 1; client < MaxClients; client++)
    {
        if (IsClientConnected(client) && IsClientInGame(client))
        {
            // Only forward event if the player really is in the volume..
            if (VolPlayerInVolume[client][volumeIndex] && VolClassEditSelectedClass[client] >= 0)
            {
                VolClassEditOnPlayerLeave(client, volumeIndex);
            }
        }
    }
}


/**************************************
 *                                    *
 *  ATTRIBUTE FUNCTIONS               *
 *                                    *
 **************************************/

/**
 * Converts the specified mode to a string.
 *
 * @param mode      Mode to convert.
 * @param buffer    Destination string buffer.
 * @param maxlen    Size of buffer.
 * @return          Number of cells written.
 */
VolClassEditModeToString(VolClassEditMode:mode, String:buffer[], maxlen)
{
    switch (mode)
    {
        case ClassEditMode_Name:
        {
            return strcopy(buffer, maxlen, "Name");
        }
        case ClassEditMode_Attributes:
        {
            return strcopy(buffer, maxlen, "Attributes");
        }
    }

    return 0;
}

/**
 * Gets a integer attribute and converts it to a human readable string.
 *
 * Note: attribute is assumed to be a integer (cell) and is not type cheked!
 *
 * @param dataIndex     Local data index.
 * @param attribute     Attribute to convert.
 * @param buffer        Destination string buffer.
 * @param maxlen        Size of destination buffer.
 */
VolClassEditIntToString(dataIndex, ClassEditableAttributes:attribute, String:buffer[], maxlen)
{
    new intVal;
    new String:strVal[8];

    intVal = VolClassEditData[dataIndex][VolClassEdit_ClassData][attribute];

    // Check if the attribute is marked as ignored.
    if (intVal == -1)
    {
        return strcopy(buffer, maxlen, "(no change)");
    }
    else
    {
        IntToString(intVal, strVal, sizeof(strVal));
        return strcopy(buffer, maxlen, strVal);
    }
}

/**
 * Gets a float attribute and converts it to a human readable string.
 *
 * Note: attribute is assumed to be a float and is not type cheked!
 *
 * @param dataIndex     Local data index.
 * @param attribute     Attribute to convert.
 * @param buffer        Destination string buffer.
 * @param maxlen        Size of destination buffer.
 */
VolClassEditFloatToString(dataIndex, ClassEditableAttributes:attribute, String:buffer[], maxlen, Float:minval = -1.0)
{
    new Float:floatVal;
    new String:strVal[8];

    floatVal = Float:VolClassEditData[dataIndex][VolClassEdit_ClassData][attribute];

    // Check if the attribute is marked as ignored.
    if (floatVal == minval)
    {
        return strcopy(buffer, maxlen, "(no change)");
    }
    else
    {
        FloatToString(floatVal, strVal, sizeof(strVal));
        return strcopy(buffer, maxlen, strVal);
    }
}

/**
 * Gets a string attribute and converts it to a human readable string.
 *
 * Note: attribute is assumed to be a string and is not type cheked!
 *
 * @param dataIndex     Local data index.
 * @param attribute     Attribute to convert.
 * @param buffer        Destination string buffer.
 * @param maxlen        Size of destination buffer.
 */
VolClassEditStringToHumanStr(dataIndex, ClassEditableAttributes:attribute, String:buffer[], maxlen)
{
    decl String:strVal[PLATFORM_MAX_PATH];
    strcopy(strVal, sizeof(strVal), String:VolClassEditData[dataIndex][VolClassEdit_ClassData][attribute]);

    // Check if the attribute is marked as ignored.
    if (StrEqual(strVal, "nochange", false))
    {
        return strcopy(buffer, maxlen, "(no change)");
    }
    else
    {
        return strcopy(buffer, maxlen, strVal);
    }
}

/**
 * Sets the mode attribute.
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetMode(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    if (StrEqual(value, "name", false))
    {
        VolClassEditData[dataIndex][VolClassEdit_Mode] = ClassEditMode_Name;
        return true;
    }
    else if (StrEqual(value, "attributes", false))
    {
        VolClassEditData[dataIndex][VolClassEdit_Mode] = ClassEditMode_Attributes;
        return true;
    }

    // No match.
    return false;
}

/**
 * Sets the class name attribute (name of the class to switch to).
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetName(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    strcopy(VolClassEditData[dataIndex][VolClassEdit_ClassName], VOL_CLASSNAME_SIZE, value);

    // TODO: Validate name.

    return true;
}

/**
 * Sets the alpha initial attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetAlphaInitial(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_AlphaInitial] = StringToInt(value);
    return true;
}

/**
 * Sets the alpha damaged attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetAlphaDamaged(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_AlphaDamaged] = StringToInt(value);
    return true;
}

/**
 * Sets the alpha damage attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetAlphaDamage(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_AlphaDamage] = StringToInt(value);
    return true;
}

/**
 * Sets the overlay path attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetOverlayPath(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    strcopy(VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_OverlayPath], PLATFORM_MAX_PATH, value);
    return true;
}

/**
 * Sets the nvgs attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetNvgs(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_Nvgs] = StringToInt(value);
    return true;
}

/**
 * Sets the fov attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetFov(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_Fov] = StringToInt(value);
    return true;
}

/**
 * Sets the has napalm attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetHasNapalm(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_HasNapalm] = StringToInt(value);
    return true;
}

/**
 * Sets the napalm time attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetNapalmTime(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_NapalmTime] = StringToFloat(value);
    return true;
}

/**
 * Sets the immunity mode attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetImmunityMode(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    new ImmunityMode:mode = ImmunityStringToMode(value);
    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_ImmunityMode] = mode;
    return true;
}

/**
 * Sets the immunity amount attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetImmunityAmount(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_ImmunityAmount] = StringToInt(value);
    return true;
}

/**
 * Sets the immunity cooldown attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetImmunityCooldown(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_ImmunityCooldown] = StringToInt(value);
    return true;
}

/**
 * Sets the no fall damage attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetNoFallDamage(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_NoFallDamage] = StringToInt(value);
    return true;
}

/**
 * Sets the health regen interval attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetRegenInterval(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_RegenInterval] = StringToFloat(value);
    return true;
}

/**
 * Sets the health regen amount attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetRegenAmount(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_RegenAmount] = StringToInt(value);
    return true;
}

/**
 * Sets the infect gain attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetInfectGain(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_InfectGain] = StringToInt(value);
    return true;
}

/**
 * Sets the kill bonus attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetKillBonus(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_KillBonus] = StringToInt(value);
    return true;
}

/**
 * Sets the speed attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetSpeed(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_Speed] = StringToFloat(value);
    return true;
}

/**
 * Sets the knock back attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetKnockBack(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_KnockBack] = StringToFloat(value);
    return true;
}

/**
 * Sets the jump heigt attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetJumpHeight(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_JumpHeight] = StringToFloat(value);
    return true;
}

/**
 * Sets the jump distance attribute.
 *
 * Note: The value is not validated!
 *
 * @param dataIndex     Local data index.
 * @param value         String value to set (converted to proper type by this
 *                      function).
 * @return              True if set, false if empty.
 */
bool:VolClassEditSetJumpDistance(dataIndex, const String:value[])
{
    if (strlen(value) == 0)
    {
        return false;
    }

    VolClassEditData[dataIndex][VolClassEdit_ClassData][ClassEdit_JumpDistance] = StringToFloat(value);
    return true;
}

/**
 * Updates a player's cache with the specified attribute set. Only active
 * attributes are applied.
 *
 * Note: These attributes are not validated!
 *
 * @param client        The client index.
 * @param attributes    Attribute set to apply (ClassEditableAttributes)
 * @return              Number of attributes changed.
 */
VolClassEditUpdateAttributes(client, const attributes[])
{
    new numChanges;

    // Alpha initial.
    if (attributes[ClassEdit_AlphaInitial] > -1)
    {
        ClassPlayerCache[client][Class_AlphaInitial] = attributes[ClassEdit_AlphaInitial];
        numChanges++;
    }

    // Alpha damaged.
    if (attributes[ClassEdit_AlphaDamaged] > -1)
    {
        ClassPlayerCache[client][Class_AlphaDamaged] = attributes[ClassEdit_AlphaDamaged];
        numChanges++;
    }

    // Alpha damage.
    if (attributes[ClassEdit_AlphaDamage] > -1)
    {
        ClassPlayerCache[client][Class_AlphaDamage] = attributes[ClassEdit_AlphaDamage];
        numChanges++;
    }

    // Overlay path.
    if (!StrEqual(attributes[ClassEdit_OverlayPath], "nochange"))
    {
        strcopy(ClassPlayerCache[client][Class_OverlayPath], PLATFORM_MAX_PATH, attributes[ClassEdit_OverlayPath]);
        numChanges++;
    }

    // Nvgs.
    if (attributes[ClassEdit_Nvgs] > -1)
    {
        ClassPlayerCache[client][Class_Nvgs] = bool:attributes[ClassEdit_Nvgs];
        numChanges++;
    }

    // Napalm time.
    if (attributes[ClassEdit_NapalmTime] > -1.0)
    {
        ClassPlayerCache[client][Class_NapalmTime] = attributes[ClassEdit_NapalmTime];
        numChanges++;
    }

    // Immunity mode.
    if (attributes[ClassEdit_ImmunityMode] != Immunity_Invalid)
    {
        ClassPlayerCache[client][Class_ImmunityMode] = attributes[ClassEdit_ImmunityMode];
        numChanges++;
    }

    // Immunity amount.
    if (attributes[ClassEdit_ImmunityAmount] > -1)
    {
        ClassPlayerCache[client][Class_ImmunityAmount] = attributes[ClassEdit_ImmunityAmount];
        numChanges++;
    }

    // Immunity cooldown.
    if (attributes[ClassEdit_ImmunityCooldown] > -1)
    {
        ClassPlayerCache[client][Class_ImmunityCooldown] = attributes[ClassEdit_ImmunityCooldown];
        numChanges++;
    }

    // No fall damage.
    if (attributes[ClassEdit_NoFallDamage] > -1)
    {
        ClassPlayerCache[client][Class_NoFallDamage] = bool:attributes[ClassEdit_NoFallDamage];
        numChanges++;
    }

    // Health regen interval.
    if (attributes[ClassEdit_RegenInterval] > -1.0)
    {
        ClassPlayerCache[client][Class_HealthRegenInterval] = attributes[ClassEdit_RegenInterval];
        numChanges++;
    }

    // Health regen amount.
    if (attributes[ClassEdit_RegenAmount] > -1)
    {
        ClassPlayerCache[client][Class_HealthRegenAmount] = attributes[ClassEdit_RegenAmount];
        numChanges++;
    }

    // Infect gain.
    if (attributes[ClassEdit_InfectGain] > -1)
    {
        ClassPlayerCache[client][Class_HealthInfectGain] = attributes[ClassEdit_InfectGain];
        numChanges++;
    }

    // Kill bonus.
    if (attributes[ClassEdit_KillBonus] > -1)
    {
        ClassPlayerCache[client][Class_KillBonus] = attributes[ClassEdit_KillBonus];
        numChanges++;
    }

    // Speed.
    if (attributes[ClassEdit_Speed] > -1.0)
    {
        ClassPlayerCache[client][Class_Speed] = attributes[ClassEdit_Speed];
        numChanges++;
    }

    // Knock back.
    if (attributes[ClassEdit_KnockBack] > ZR_CLASS_KNOCKBACK_IGNORE)
    {
        ClassPlayerCache[client][Class_KnockBack] = attributes[ClassEdit_KnockBack];
        numChanges++;
    }

    // Jump height.
    if (attributes[ClassEdit_JumpHeight] > -1.0)
    {
        ClassPlayerCache[client][Class_JumpHeight] = attributes[ClassEdit_JumpHeight];
        numChanges++;
    }

    // Jump distance.
    if (attributes[ClassEdit_JumpDistance] > -1.0)
    {
        ClassPlayerCache[client][Class_JumpDistance] = attributes[ClassEdit_JumpDistance];
        numChanges++;
    }

    LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "ClassEdit", "Applied %d attribute(s).", numChanges);
    return numChanges;
}

/**
 * Restores a player's cache to the original values (from modified cache). A
 * attribute set is used as a mask to determine what attributes to restore.
 *
 * @param client        The client index.
 * @param classindex    Index of class to restore.
 * @param attributes    Attribute mask (ClassEditableAttributes).
 * @return              Number of attributes changed.
 */
VolClassEditRestoreAttributes(client, classindex, const attributes[])
{
    new numChanges;

    // Alpha initial.
    if (attributes[ClassEdit_AlphaInitial] > -1)
    {
        ClassPlayerCache[client][Class_AlphaInitial] = ClassGetAlphaInitial(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Alpha damaged.
    if (attributes[ClassEdit_AlphaDamaged] > -1)
    {
        ClassPlayerCache[client][Class_AlphaDamaged] = ClassGetAlphaDamaged(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Alpha damage.
    if (attributes[ClassEdit_AlphaDamage] > -1)
    {
        ClassPlayerCache[client][Class_AlphaDamage] = ClassGetAlphaDamage(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Overlay path.
    if (!StrEqual(attributes[ClassEdit_OverlayPath], "nochange"))
    {
        decl String:path[PLATFORM_MAX_PATH];
        ClassGetOverlayPath(classindex, path, sizeof(path), ZR_CLASS_CACHE_MODIFIED);
        strcopy(ClassPlayerCache[client][Class_OverlayPath], PLATFORM_MAX_PATH, path);
        numChanges++;
    }

    // Nvgs.
    if (attributes[ClassEdit_Nvgs] > -1)
    {
        ClassPlayerCache[client][Class_Nvgs] = ClassGetNvgs(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Napalm time.
    if (attributes[ClassEdit_NapalmTime] > -1.0)
    {
        ClassPlayerCache[client][Class_NapalmTime] = ClassGetNapalmTime(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Immunity mode.
    if (attributes[ClassEdit_ImmunityMode] != Immunity_Invalid)
    {
        ClassPlayerCache[client][Class_ImmunityMode] = ClassGetImmunityMode(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Immunity amount.
    if (attributes[ClassEdit_ImmunityAmount] > -1.0)
    {
        ClassPlayerCache[client][Class_ImmunityAmount] = ClassGetImmunityAmount(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // No fall damage.
    if (attributes[ClassEdit_NoFallDamage] > -1)
    {
        ClassPlayerCache[client][Class_NoFallDamage] = ClassGetNoFallDamage(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Health regen interval.
    if (attributes[ClassEdit_RegenInterval] > -1.0)
    {
        ClassPlayerCache[client][Class_HealthRegenInterval] = ClassGetHealthRegenInterval(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Health regen amount.
    if (attributes[ClassEdit_RegenAmount] > -1)
    {
        ClassPlayerCache[client][Class_HealthRegenAmount] = ClassGetHealthRegenAmount(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Infect gain.
    if (attributes[ClassEdit_InfectGain] > -1)
    {
        ClassPlayerCache[client][Class_HealthInfectGain] = ClassGetHealthInfectGain(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Kill bonus.
    if (attributes[ClassEdit_KillBonus] > -1)
    {
        ClassPlayerCache[client][Class_KillBonus] = ClassGetKillBonus(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Speed.
    if (attributes[ClassEdit_Speed] > -1.0)
    {
        ClassPlayerCache[client][Class_Speed] = ClassGetSpeed(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Knock back.
    if (attributes[ClassEdit_KnockBack] > ZR_CLASS_KNOCKBACK_IGNORE)
    {
        ClassPlayerCache[client][Class_KnockBack] = ClassGetKnockback(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Jump height.
    if (attributes[ClassEdit_JumpHeight] > -1.0)
    {
        ClassPlayerCache[client][Class_JumpHeight] = ClassGetJumpHeight(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    // Jump distance.
    if (attributes[ClassEdit_JumpDistance] > -1.0)
    {
        ClassPlayerCache[client][Class_JumpDistance] = ClassGetJumpDistance(classindex, ZR_CLASS_CACHE_MODIFIED);
        numChanges++;
    }

    LogEvent(_, LogType_Normal, LOG_DEBUG, LogModule_Volfeatures, "ClassEdit", "Applied %d attribute(s).", numChanges);
    return numChanges;
}
