#include "colors_inc"
#include "inc_helper_funcs"
#include "inc_pc_functions"
#include "inc_mysql_tables"
#include "nwnx_structs"
#include "zzdlg_main_inc"

// Firearm Upgrade System Include File
// Created by Zunath on July 23, 2011


///////////////
// VARIABLES //
///////////////

// Name of the script which handles the FUSE menu
const string FUS_WORKSHOP_MENU = "fus_menu";

// Keeps track of the firearm object being modified.
const string FUS_FIREARM_OBJECT = "FUS_FIREARM_OBJECT";

// Keeps track of the firearm's current upgrade level for a specific statistic.
const string FUS_FIREARM_UPGRADE_LEVEL = "FUS_FIREARM_UPGRADE_LEVEL";

// Keeps track of the upgrade confirmation button.
const string FUS_CHAT_UPGRADE_CONFIRMATION = "FUS_CHAT_UPGRADE_CONFIRMATION";

// Keeps track of the chosen node ID
const string FUS_CHAT_NODE_ID = "FUS_CHAT_NODE_ID";

// Stored on the firearm object. Tracks the current level of all upgrades.
const string FUS_FIREARM_UPGRADES_LEVELS = "FUS_FIREARM_UPGRADES_LEVELS";

///////////////
// 2DA FILES //
///////////////

// The magazine capacity 2DA file
const string FUS_2DA_MAGAZINE_SIZE = "iprp_magcapcost";

// The firepower 2DA file
const string FUS_2DA_FIREPOWER = "iprp_firepowcost";

//////////////////////
// SYSTEM CONSTANTS //
//////////////////////

// Number of upgrade categories. If you add a new category of upgrades this number needs to be updated!
const int FUS_NUMBER_OF_UPGRADE_CATEGORIES = 6;

// These determine which information to grab for each upgrade page.
const int FUS_UPGRADE_TYPE_FIREPOWER = 1;
const int FUS_UPGRADE_TYPE_MAGAZINE_SIZE = 2;
const int FUS_UPGRADE_TYPE_RELOAD_SPEED = 3;
const int FUS_UPGRADE_TYPE_AMMO_TYPE = 4;
const int FUS_UPGRADE_TYPE_FIRING_MODES = 5;
const int FUS_UPGRADE_TYPE_CRITICAL_RATING = 6;


//////////////////
// MYSQL TABLES //
//////////////////

// Global table which tracks maximum upgrades for all firearms.
const string FUS_MYSQL_GLOBAL = "fus_global";
// Lookup table for components
const string FUS_MYSQL_COMPONENTS = "fus_components";
// Prefix used for individual firearm tables. These tables appear as FUS_MYSQL_PREFIX + Item Resref in the DBMS.
const string FUS_MYSQL_PREFIX = "fus_";

/////////////
// STRUCTS //
/////////////

struct FUS_UpgradeInfoStruct
{
    int iUpgradeLevel;                                                      // Current upgrade level
    int iMaxLevel;                                                          // Maximum upgrade level
    int iGoldCost;                                                          // Amount of gold needed to make upgrade
    string sUpgradeName;                                                    // The name of the upgrade being done
    int iComponent1, iComponent2, iComponent3, iComponent4, iComponent5;    // Component ID numbers
    string sFirearmName;                                                    // Name of the firearm
    string sDescription;                                                    // The upgrade's description. Details what the upgrade actually does.

};

////////////////
// PROTOTYPES //
////////////////

// Returns all information regarding a firearm's upgrade as a FUS_UpgradeInfoStruct
// oFirearm = The firearm to retrieve information from
// sUpgradeName = Optional field which allows you to pass the upgrade name in as a parameter
struct FUS_UpgradeInfoStruct FUS_GetUpgradeInformation(object oFirearm, int iUpgradeType);

// Returns a list of the components needed to upgrade an item. This is displayed in the upgrade menu.
// stUpgradeInfo = The struct which holds upgrade information (FUS_UpgradeInfoStruct)
string FUS_BuildComponentList(struct FUS_UpgradeInfoStruct stUpgradeInfo);

// Call this on the OnDisturbed event of your workbench placeable.
// When an item is added to its inventory, the item will be returned to the PC that put it inside.
// Then a conversation will begin which will allow the player to perform upgrades to the item.
// During this process the item in question will be cursed to prevent exploits. Once the upgrading is complete
// it will return to normal.
void FUS_OnWorkbenchDisturbed();

// Returns the upgrade data for a particular firearm's upgrade ID and level.
// This data is used for a number of things depending on the upgrade type.
// For example, firepower uses this to determine what to set the new value at.
string FUS_GetUpgradeData(object oFirearm, int iLevel, int iUpgradeID);

// Performs the upgrade to the firearm being modified. Checks are made to ensure the player has enough
// gold and the appropriate materials.
// oPC = The player object speaking
// iUpgradeID = The upgrade ID
void FUS_DoUpgrade(object oPC, int iUpgradeID, struct FUS_UpgradeInfoStruct stUpgradeInfo);

// Removes all temporary variables that are temporarily stored on the PC
void FUS_ClearTemporaryVariables(object oPC);

// Returns the upgrade header for the menu. This header displays information regarding the firearm's current
// upgrade stats, price to upgrade, components needed, and so on.
// oPC = The player object
// iUpgradeID = FUS_UPGRADE_TYPE_*
string FUS_CreateUpgradePageHeader(object oPC, int iUpgradeID);

// Returns the ID number of an item property's value from the lookup table.
// iValue = The value of the item property
// iUpgradeType = FUS_UPGRADE_TYPE_*
// sValueInteger = If set TRUE, sValue is treated as an integer. If set FALSE, it's treated as a string.
int FUS_GetItemPropertyID(string sValue, int iUpgradeType, int sValueInteger = FALSE);

///////////////
// FUNCTIONS //
///////////////

struct FUS_UpgradeInfoStruct FUS_GetUpgradeInformation(object oFirearm, int iUpgradeType)
{
    struct FUS_UpgradeInfoStruct stUpgradeInfo;
    string sResref = GetResRef(oFirearm);
    string sUpgradeType, sUpgradeName;
    string sDescription;

    stUpgradeInfo.sFirearmName = GetName(oFirearm, TRUE);

    // Grab the name of the upgrade column
    switch(iUpgradeType)
    {
        case FUS_UPGRADE_TYPE_FIREPOWER:       sUpgradeType = "Firepower";
                                               sUpgradeName = "Firepower";
                                               sDescription = "Increases firepower to ";
                                               break;
        case FUS_UPGRADE_TYPE_MAGAZINE_SIZE:   sUpgradeType = "MagazineSize";
                                               sUpgradeName = "Magazine Size";
                                               sDescription = "Increases magazine size to ";
                                               break;
        case FUS_UPGRADE_TYPE_RELOAD_SPEED:    sUpgradeType = "ReloadSpeed";
                                               sUpgradeName = "Reload Speed";
                                               sDescription = "Increases reload speed to ";
                                               break;
        case FUS_UPGRADE_TYPE_AMMO_TYPE:       sUpgradeType = "AmmoType";
                                               sUpgradeName = "Additional Ammo Types";
                                               sDescription = "Enables firing of ammo type: ";
                                               break;
        case FUS_UPGRADE_TYPE_FIRING_MODES:    sUpgradeType = "FiringModes";
                                               sUpgradeName = "Additional Firing Modes";
                                               sDescription = "Enables firing mode: ";
                                               break;
        case FUS_UPGRADE_TYPE_CRITICAL_RATING: sUpgradeType = "CriticalRate";
                                               sUpgradeName = "Critical Rating";
                                               sDescription = "Increases critical rating to ";
                                               break;
    }

    // Store upgrade name and maximum upgrade level possible

    string sSQL = "SELECT MAX(Level) FROM " + FUS_MYSQL_PREFIX + sResref + " WHERE UpgradeType='" + IntToString(iUpgradeType) + "';";
    SQLExecDirect(sSQL);
    if(SQLFetch() == SQL_SUCCESS)
    {
        stUpgradeInfo.iMaxLevel = StringToInt(SQLGetData(1));
    }

    // Store upgrade name
    stUpgradeInfo.sUpgradeName = sUpgradeName;

    // Store upgrade level
    stUpgradeInfo.iUpgradeLevel = GetLocalArrayInt(oFirearm, FUS_FIREARM_UPGRADES_LEVELS, iUpgradeType);

    // Store gold cost, component resrefs, and component names
    sSQL = "SELECT Gold, Component1, Component2, Component3, Component4, Component5 FROM " +
           FUS_MYSQL_PREFIX + sResref + " WHERE Level=" + IntToString(stUpgradeInfo.iUpgradeLevel+1) + " AND UpgradeType=" + IntToString(iUpgradeType);
    SQLExecDirect(sSQL);

    if(SQLFetch() == SQL_SUCCESS)
    {
        stUpgradeInfo.iGoldCost = StringToInt(SQLGetData(1));
        stUpgradeInfo.iComponent1 = StringToInt(SQLGetData(2));
        stUpgradeInfo.iComponent2 = StringToInt(SQLGetData(3));
        stUpgradeInfo.iComponent3 = StringToInt(SQLGetData(4));
        stUpgradeInfo.iComponent4 = StringToInt(SQLGetData(5));
        stUpgradeInfo.iComponent5 = StringToInt(SQLGetData(6));
    }

    // Store upgrade description (actual values are set when the menu header is built
    stUpgradeInfo.sDescription = sDescription;

    return stUpgradeInfo;
}

string FUS_BuildComponentList(struct FUS_UpgradeInfoStruct stUpgradeInfo)
{
    int bRequiresComponents = FALSE;
    string sComponentList;

    string sSQL = "SELECT ID, ComponentName FROM `" + FUS_MYSQL_COMPONENTS + "` WHERE ID=" + IntToString(stUpgradeInfo.iComponent1) + " OR ID=" + IntToString(stUpgradeInfo.iComponent2) + " OR ID=" + IntToString(stUpgradeInfo.iComponent3) +" OR ID=" + IntToString(stUpgradeInfo.iComponent4) +" OR ID=" + IntToString(stUpgradeInfo.iComponent5) + " ORDER BY ID";
    SQLExecDirect(sSQL);

    while(SQLFetch() == SQL_SUCCESS)
    {
        int iDBID = StringToInt(SQLGetData(1));
        bRequiresComponents = TRUE;

        if(iDBID == stUpgradeInfo.iComponent1)
        {
            sComponentList += SQLGetData(2) + "\n";
        }
        if(iDBID == stUpgradeInfo.iComponent2)
        {
            sComponentList += SQLGetData(2) + "\n";
        }
        if(iDBID == stUpgradeInfo.iComponent3)
        {
            sComponentList += SQLGetData(2) + "\n";
        }
        if(iDBID == stUpgradeInfo.iComponent4)
        {
            sComponentList += SQLGetData(2) + "\n";
        }
        if(iDBID == stUpgradeInfo.iComponent5)
        {
            sComponentList += SQLGetData(2) + "\n";
        }
    }

    // Only display the "Components Required" message if there's components actually needed.
    if(bRequiresComponents)
    {
        sComponentList = ColorTokenGreen() + "Components Required:\n\n" + ColorTokenEnd() + sComponentList;
    }

    return sComponentList;
}

void FUS_OnWorkbenchDisturbed()
{
    object oPC = GetLastDisturbed();
    object oWorkshop = OBJECT_SELF;
    object oItem = GetInventoryDisturbItem();
    int iType = GetInventoryDisturbType();
    string sName = GetName(oItem);
    string sResref = GetResRef(oItem);

    if(iType == INVENTORY_DISTURB_TYPE_ADDED)
    {
        object oCopy = CopyItemSafe(oItem, oPC, TRUE);
        DestroyObject(oItem);

        // Does the table exist? If so we can fire the upgrade menu.
        if(DoesMySQLTableExist(FUS_MYSQL_PREFIX + sResref))
        {
            // Clean variables just in case
            FUS_ClearTemporaryVariables(oPC);

            SetLocalObject(oPC, FUS_FIREARM_OBJECT, oCopy);
            SetItemCursedFlag(oCopy, TRUE);
            _dlgStart(oPC, oWorkshop, FUS_WORKSHOP_MENU, 1, 1, 1);
        }
        // Otherwise the PC can't upgrade that firearm (or other item)
        else
        {
            FloatingTextStringOnCreature(ColorTokenRed() + "You cannot upgrade that item.", oPC, FALSE);
        }
    }
}

void FUS_ClearTemporaryVariables(object oPC)
{
    DeleteLocalInt(oPC, FUS_CHAT_UPGRADE_CONFIRMATION);
    DeleteLocalObject(oPC, FUS_FIREARM_OBJECT);
    DeleteLocalInt(oPC, FUS_CHAT_NODE_ID);
}

string FUS_CreateUpgradePageHeader(object oPC, int iUpgradeID)
{
    object oFirearm = GetLocalObject(oPC, FUS_FIREARM_OBJECT);
    struct FUS_UpgradeInfoStruct stUpgradeInfo = FUS_GetUpgradeInformation(oFirearm, iUpgradeID);
    string sGold;

    if(stUpgradeInfo.iUpgradeLevel >= stUpgradeInfo.iMaxLevel)
    {
        sGold = ColorTokenRed() + "N/A";
    }
    else
    {
        sGold = IntToString(stUpgradeInfo.iGoldCost);
    }

    string sHeader = ColorTokenGreen() + "Firearm: " + ColorTokenEnd() + GetName(oFirearm, TRUE) + "\n";
    sHeader += ColorTokenGreen() + "Upgrade: " + ColorTokenEnd() + stUpgradeInfo.sUpgradeName + "\n\n";
    sHeader += ColorTokenGreen() + "Level: " + ColorTokenEnd() + IntToString(stUpgradeInfo.iUpgradeLevel) + " / " + ColorTokenRed() + IntToString(stUpgradeInfo.iMaxLevel) + ColorTokenEnd() + "\n";
    sHeader += ColorTokenGreen() + "Price to Upgrade: " + ColorTokenEnd() + sGold + "\n";
    sHeader += ColorTokenGreen() + "Money: " + ColorTokenWhite() + IntToString(GetGold(oPC)) + ColorTokenEnd() + "\n\n";

    // Ensure the upgrade isn't at its max already - this prevents unnecessary calls to the database
    if(stUpgradeInfo.iUpgradeLevel < stUpgradeInfo.iMaxLevel)
    {
        // Show the description and component list only if the upgrade isn't maxed out yet
        if(stUpgradeInfo.iUpgradeLevel < stUpgradeInfo.iMaxLevel)
        {
            sHeader += ColorTokenGreen() + "Next Upgrade: " + ColorTokenEnd() + ColorTokenWhite() + stUpgradeInfo.sDescription + FUS_GetUpgradeData(oFirearm, stUpgradeInfo.iUpgradeLevel+1, iUpgradeID) + "\n\n" + ColorTokenEnd();
            sHeader += FUS_BuildComponentList(stUpgradeInfo);
        }
    }
    return sHeader;
}

string FUS_GetUpgradeData(object oFirearm, int iLevel, int iUpgradeID)
{
    string sResref = GetResRef(oFirearm);
    string sData;
    string sTable = FUS_MYSQL_PREFIX + sResref;
    string sSQL = "SELECT UpgradeInfo FROM " + sTable + " WHERE Level=" + IntToString(iLevel) + " AND UpgradeType=" + IntToString(iUpgradeID);
    SQLExecDirect(sSQL);

    if(SQLFetch() == SQL_SUCCESS)
    {
        sData = SQLGetData(1);
    }

    return sData;
}

int FUS_GetItemPropertyID(string sValue, int iUpgradeType, int sValueInteger = FALSE)
{
    int iValue = StringToInt(sValue);
    string sColumn;

    if(iUpgradeType == FUS_UPGRADE_TYPE_FIREPOWER) sColumn = "Firepower";
    else if(iUpgradeType == FUS_UPGRADE_TYPE_MAGAZINE_SIZE) sColumn = "MagazineSize";
    else if(iUpgradeType == FUS_UPGRADE_TYPE_RELOAD_SPEED) sColumn = "ReloadSpeed";
    else if(iUpgradeType == FUS_UPGRADE_TYPE_AMMO_TYPE) sColumn = "AmmoType";
    else if(iUpgradeType == FUS_UPGRADE_TYPE_FIRING_MODES) sColumn = "FiringModes";
    else if(iUpgradeType == FUS_UPGRADE_TYPE_CRITICAL_RATING) sColumn = "CriticalRating";

    // Return -1 on error
    if(sColumn == "") return -1;

    if(sValueInteger)
    {
        return StringToInt(GetMySQLData(FUS_MYSQL_LOOKUP, "ID", iValue, sColumn));
    }
    else
    {
        return StringToInt(GetMySQLDataKeyString(FUS_MYSQL_LOOKUP, "ID", sValue, sColumn));
    }
}

void FUS_DoUpgrade(object oPC, int iUpgradeID, struct FUS_UpgradeInfoStruct stUpgradeInfo)
{
    object oComponent1, oComponent2, oComponent3, oComponent4, oComponent5;
    string sComponentName1, sComponentName2, sComponentName3, sComponentName4, sComponentName5;
    string sComponentResref1, sComponentResref2, sComponentResref3, sComponentResref4, sComponentResref5;

    int iGold = GetGold(oPC);

    // Check their gold just in case they dropped it while the purchase option was available
    if(iGold < stUpgradeInfo.iGoldCost)
    {
        SendMessageToPC(oPC, ColorTokenRed() + "You do not have enough money to make that purchase." + ColorTokenEnd());
        return;
    }

    // Check for required components in PC's inventory
    object oItem = GetFirstItemInInventory(oPC);
    int bComp1Found, bComp2Found, bComp3Found, bComp4Found, bComp5Found;

    // Grab the names and resrefs of the components needed.
    string sSQL = "SELECT ID, ComponentName, ComponentResref FROM `" + FUS_MYSQL_COMPONENTS + "` WHERE ID=" + IntToString(stUpgradeInfo.iComponent1) + " OR ID=" + IntToString(stUpgradeInfo.iComponent2) + " OR ID=" + IntToString(stUpgradeInfo.iComponent3) +" OR ID=" + IntToString(stUpgradeInfo.iComponent4) +" OR ID=" + IntToString(stUpgradeInfo.iComponent5) + " ORDER BY ID";
    SQLExecDirect(sSQL);

    while(SQLFetch() == SQL_SUCCESS)
    {
        int iCompID = StringToInt(SQLGetData(1));

        if(iCompID == stUpgradeInfo.iComponent1)
        {
            sComponentName1 = SQLGetData(2);
            sComponentResref1 = SQLGetData(3);
        }
        if(iCompID == stUpgradeInfo.iComponent2)
        {
            sComponentName2 = SQLGetData(2);
            sComponentResref2 = SQLGetData(3);
        }
        if(iCompID == stUpgradeInfo.iComponent3)
        {
            sComponentName3 = SQLGetData(2);
            sComponentResref3 = SQLGetData(3);
        }
        if(iCompID == stUpgradeInfo.iComponent4)
        {
            sComponentName4 = SQLGetData(2);
            sComponentResref4 = SQLGetData(3);
        }
        if(iCompID == stUpgradeInfo.iComponent5)
        {
            sComponentName5 = SQLGetData(2);
            sComponentResref5 = SQLGetData(3);
        }
    }

    while(GetIsObjectValid(oItem))
    {
        string sResref = GetResRef(oItem);

        // First component
        if(sComponentResref1 == sResref && oComponent1 == OBJECT_INVALID && sComponentResref1 != "")
        {
            oComponent1 = oItem;
            bComp1Found = TRUE;
        }
        // Second component
        else if(sComponentResref2 == sResref && oComponent2 == OBJECT_INVALID && sComponentResref2 != "")
        {
            oComponent2 = oItem;
            bComp2Found = TRUE;
        }
        // Thid component
        else if(sComponentResref3 == sResref && oComponent3 == OBJECT_INVALID && sComponentResref3 != "")
        {
            oComponent3 = oItem;
            bComp3Found = TRUE;
        }
        // Fourth component
        else if(sComponentResref4 == sResref && oComponent4 == OBJECT_INVALID && sComponentResref4 != "")
        {
            oComponent4 = oItem;
            bComp4Found = TRUE;
        }
        // Fifth component
        else if(sComponentResref5 == sResref && oComponent5 == OBJECT_INVALID && sComponentResref5 != "")
        {
            oComponent5 = oItem;
            bComp5Found = TRUE;
        }

        oItem = GetNextItemInInventory(oPC);
    }

    // Remove the materials only if they are all found in the PC's inventory. Otherwise print out an error message
    int bAllComponentsFound = TRUE;

    if(sComponentResref1 != "" && bComp1Found == FALSE) bAllComponentsFound = FALSE;
    else if(sComponentResref2 != "" && bComp2Found == FALSE) bAllComponentsFound = FALSE;
    else if(sComponentResref3 != "" && bComp3Found == FALSE) bAllComponentsFound = FALSE;
    else if(sComponentResref4 != "" && bComp4Found == FALSE) bAllComponentsFound = FALSE;
    else if(sComponentResref5 != "" && bComp5Found == FALSE) bAllComponentsFound = FALSE;

    // Destroy all components since they were all found
    if(bAllComponentsFound)
    {
        if(GetIsObjectValid(oComponent1)) DestroyObject(oComponent1);
        if(GetIsObjectValid(oComponent2)) DestroyObject(oComponent2);
        if(GetIsObjectValid(oComponent3)) DestroyObject(oComponent3);
        if(GetIsObjectValid(oComponent4)) DestroyObject(oComponent4);
        if(GetIsObjectValid(oComponent5)) DestroyObject(oComponent5);
    }
    // Do nothing and print an error message detailing which components are missing
    else
    {
        string sError = ColorTokenRed() + "You are missing the following components:\n";

        if(sComponentName1 != "" && !bComp1Found) sError += "\n" + sComponentName1;
        if(sComponentName2 != "" && !bComp2Found) sError += "\n" + sComponentName2;
        if(sComponentName3 != "" && !bComp3Found) sError += "\n" + sComponentName3;
        if(sComponentName4 != "" && !bComp4Found) sError += "\n" + sComponentName4;
        if(sComponentName5 != "" && !bComp5Found) sError += "\n" + sComponentName5;
        sError += ColorTokenEnd();

        SendMessageToPC(oPC, sError);
        return;
    }

    // Remove the gold from the game
    AssignCommand(oPC, TakeGoldFromCreature(stUpgradeInfo.iGoldCost, oPC, TRUE));

    object oFirearm = GetLocalObject(oPC, FUS_FIREARM_OBJECT);
    string sResref = GetResRef(oFirearm);
    int iUpgradeType = GetLocalInt(oPC, FUS_CHAT_NODE_ID);
    string sUpgradeData = FUS_GetUpgradeData(oFirearm, stUpgradeInfo.iUpgradeLevel + 1, iUpgradeType);
    stUpgradeInfo.iUpgradeLevel = stUpgradeInfo.iUpgradeLevel + 1;

    // Safety check to make sure the weapon doesn't go beyond its max level
    if(stUpgradeInfo.iUpgradeLevel > stUpgradeInfo.iMaxLevel)
    {
        SendMessageToPC(oPC, ColorTokenRed() + "You cannot upgrade that any further." + ColorTokenEnd());
        return;
    }

    // Update the upgrade level
    SetLocalArrayInt(oFirearm, FUS_FIREARM_UPGRADES_LEVELS, iUpgradeID, stUpgradeInfo.iUpgradeLevel);

    // Different actions have to be taken depending on what's being upgraded on the firearm
    switch(iUpgradeID)
    {
        // Firepower - Check the lookup table for the new firepower value, and then set the
        // firepower item property on the firearm to the new value
        case FUS_UPGRADE_TYPE_FIREPOWER:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, TRUE);
            itemproperty ipFirepower = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_FIREPOWER, 0, 33, iIPID, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipFirepower, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);
            break;
        }
        // Magazine Size - Check the lookup table for the new magazine size value and then set the
        // magazine size item property on the firearm to the new value
        case FUS_UPGRADE_TYPE_MAGAZINE_SIZE:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, TRUE);
            itemproperty ipMagazineSize = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_MAGAZINE, 0, 28, iIPID, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipMagazineSize, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);
            break;
        }
        // Reload Speed - Check the lookup table for ID values. Codes are as follows:
        // VERY_SLOW: Very slow reload speed
        // SLOW: Slow reload speed
        // MEDIUM: Medium reload speed
        // FAST: Fast reload speed
        // VERY_FAST: Very fast reload speed
        case FUS_UPGRADE_TYPE_RELOAD_SPEED:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, FALSE);
            itemproperty ipMagazineSize = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_RELOAD_SPEED, iIPID, 0, 0, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipMagazineSize, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);
            break;
        }
        // Ammo Types - Check the lookup table for ID values. Codes are as follows:
        // ENHANCED: Ability to use enhanced ammo type
        // INCENDIARY: Ability to use incendiary ammo type
        case FUS_UPGRADE_TYPE_AMMO_TYPE:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, FALSE);
            itemproperty ipBonusAmmoTypes = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_ADDITIONAL_AMMO_CLASSES, iIPID, 0, 0, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipBonusAmmoTypes, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, FALSE);
            break;
        }
        // Firing Modes - Check the lookup table for ID values. Codes are as follows:
        // THREE_ROUND_BURST: Ability to use three round burst firing mode
        case FUS_UPGRADE_TYPE_FIRING_MODES:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, FALSE);
            itemproperty ipFiringMode = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_RATE_OF_FIRE, iIPID, 0, 0, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipFiringMode, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);
            break;
        }
        // Critical Rating - Set the critical rating variable on the firearm to the new value.
        // Range is from 1-50
        case FUS_UPGRADE_TYPE_CRITICAL_RATING:
        {
            int iIPID = FUS_GetItemPropertyID(sUpgradeData, iUpgradeID, TRUE);
            itemproperty ipCriticalRating = ItemPropertyDirect(ITEM_PROPERTY_FIREARM_CRITICAL_RATING, 0, 34, iIPID, 0, 0);
            IPSafeAddItemProperty(oFirearm, ipCriticalRating, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE);
            break;
        }
    }
}

// Error checking
//void main(){}