
//:: Updated for .35 by Jaysyn 2023/03/10

#include "prc_inc_function"
#include "inc_nwnx_funcs"

//:: Test Void
//void main (){}


const int DEBUG_NATURAL_AC_CALCULATION = FALSE;
int MAX_BONUS = GetPRCSwitch(PRC_PNP_SHIFTER_BONUS);
const int MAX_PENALTY = 10;
int NWNX_STR_LIMIT = 100 - MAX_BONUS;

struct _prc_inc_ability_info_struct{
    int nTemplateSTR;
    int nTemplateDEX;
    int nTemplateCON;

    int nShifterSTR;
    int nShifterDEX;
    int nShifterCON;

    int nDeltaSTR;
    int nDeltaDEX;
    int nDeltaCON;

    int nItemSTR;
    int nItemDEX;
    int nItemCON;

    int nExtraSTR;
    int nExtraDEX;
    int nExtraCON;
    
    int nItemDeltaSTR;
    int nItemDeltaDEX;
    int nItemDeltaCON;
};

//TODO: also count item penalties?
struct _prc_inc_ability_info_struct _prc_inc_CountItemAbilities(object oCreature)
{
    struct _prc_inc_ability_info_struct rInfoStruct;
    rInfoStruct.nItemSTR = 0;
    rInfoStruct.nItemDEX = 0;
    rInfoStruct.nItemCON = 0;
    
    object oItem;
    itemproperty iProperty;
    int nSlot;
    for(nSlot = 0; nSlot < NUM_INVENTORY_SLOTS; nSlot++)
    {
        switch (nSlot)
        {
            case INVENTORY_SLOT_CARMOUR:
            case INVENTORY_SLOT_CWEAPON_R:
            case INVENTORY_SLOT_CWEAPON_L:
            case INVENTORY_SLOT_CWEAPON_B:
                break;
            
            default:
            {
                oItem = GetItemInSlot(nSlot, oCreature);
                if (GetIsObjectValid(oItem))
                {
                    iProperty = GetFirstItemProperty(oItem);
                    while (GetIsItemPropertyValid(iProperty))
                    {
                        if (GetItemPropertyType(iProperty) == ITEM_PROPERTY_ABILITY_BONUS &&
                            GetItemPropertyDurationType(iProperty) == DURATION_TYPE_PERMANENT 
                           )
                        {
                            int nSubType = GetItemPropertySubType(iProperty);
                            int nCostTableValue = GetItemPropertyCostTableValue(iProperty);
                            if (nSubType == IP_CONST_ABILITY_STR)
                                rInfoStruct.nItemSTR += nCostTableValue;
                            else if (nSubType == IP_CONST_ABILITY_DEX)
                                rInfoStruct.nItemDEX += nCostTableValue;
                            else if (nSubType == IP_CONST_ABILITY_CON)
                                rInfoStruct.nItemCON += nCostTableValue;
                        }
                        // Next item property.
                        iProperty = GetNextItemProperty(oItem);
                    }
                }
            }
        }
    }
    return rInfoStruct;
}

struct _prc_inc_ability_info_struct _prc_inc_shifter_GetAbilityInfo(object oTemplate, object oShifter)
{
    int bFuncs = GetPRCSwitch(PRC_NWNX_FUNCS);

    //Initialize with item ability bonuses

    struct _prc_inc_ability_info_struct rInfoStruct = _prc_inc_CountItemAbilities(oShifter);
    
    //Get template creature abilities
    
    rInfoStruct.nTemplateSTR = GetAbilityScore(oTemplate, ABILITY_STRENGTH, TRUE);
    rInfoStruct.nTemplateDEX = GetAbilityScore(oTemplate, ABILITY_DEXTERITY, TRUE);
    rInfoStruct.nTemplateCON = GetAbilityScore(oTemplate, ABILITY_CONSTITUTION, TRUE);
    //TODO: merge in "Ability Bonus: Strength" from item property from template hide here (not too important, as not many templates use this)
    //TODO: merge in "Ability Bonus: Dexterity" from item property from template hide here (not too important, as not many templates use this)
    //TODO: merge in "Ability Bonus: Constitution" from item property from template hide here (not too important, as not many templates use this)
    
    //Calculate how they compare to the shifter's abilities

    rInfoStruct.nShifterSTR = GetAbilityScore(oShifter, ABILITY_STRENGTH, TRUE);
    rInfoStruct.nShifterDEX = GetAbilityScore(oShifter, ABILITY_DEXTERITY, TRUE);
    rInfoStruct.nShifterCON = GetAbilityScore(oShifter, ABILITY_CONSTITUTION, TRUE);

    rInfoStruct.nDeltaSTR = rInfoStruct.nTemplateSTR - rInfoStruct.nShifterSTR;
    rInfoStruct.nDeltaDEX = rInfoStruct.nTemplateDEX - rInfoStruct.nShifterDEX;
    rInfoStruct.nDeltaCON = rInfoStruct.nTemplateCON - rInfoStruct.nShifterCON;
    
    //Handle stat boosting items
    if (rInfoStruct.nItemSTR > MAX_BONUS)
        rInfoStruct.nItemSTR = MAX_BONUS;
    else if (rInfoStruct.nItemSTR < -MAX_PENALTY)
        rInfoStruct.nItemSTR = -MAX_PENALTY;

    if (rInfoStruct.nItemDEX > MAX_BONUS)
        rInfoStruct.nItemDEX = MAX_BONUS;
    else if (rInfoStruct.nItemDEX < -MAX_PENALTY)
        rInfoStruct.nItemDEX = -MAX_PENALTY;

    if (rInfoStruct.nItemCON > MAX_BONUS)
        rInfoStruct.nItemCON = MAX_BONUS;
    else if (rInfoStruct.nItemCON < -MAX_PENALTY)
        rInfoStruct.nItemCON = -MAX_PENALTY;
        
    //Handle changes that exceed bonus or penalty caps

    rInfoStruct.nItemDeltaSTR = rInfoStruct.nDeltaSTR + rInfoStruct.nItemSTR;
    if (bFuncs)
    {
        //NWNX boosts aren't capped, so we don't need to handle caps, generally speaking. 
        rInfoStruct.nExtraSTR = 0; 

        //However, due to a Bioware issue, if STR, including bonuses, goes greater than 100,
        //the amount of weight the PC can carry drops to 0. So, cap STR to make sure this doesn't happen.

        if (rInfoStruct.nTemplateSTR > NWNX_STR_LIMIT)
        {
            rInfoStruct.nExtraSTR = rInfoStruct.nTemplateSTR - NWNX_STR_LIMIT;
            rInfoStruct.nTemplateSTR = NWNX_STR_LIMIT;
            rInfoStruct.nDeltaSTR = rInfoStruct.nTemplateSTR - rInfoStruct.nShifterSTR;
        }
    }
    else if (rInfoStruct.nItemDeltaSTR > MAX_BONUS)
        rInfoStruct.nExtraSTR = rInfoStruct.nItemDeltaSTR - MAX_BONUS;
    else if(rInfoStruct.nItemDeltaSTR < -MAX_PENALTY)
        rInfoStruct.nExtraSTR = rInfoStruct.nItemDeltaSTR + MAX_PENALTY;
        
    rInfoStruct.nItemDeltaDEX = rInfoStruct.nDeltaDEX + rInfoStruct.nItemDEX;
    if (bFuncs)
        rInfoStruct.nExtraDEX = 0; //NWNX boosts aren't capped, so we don't need to handle caps
    else if (rInfoStruct.nItemDeltaDEX > MAX_BONUS)
        rInfoStruct.nExtraDEX = rInfoStruct.nItemDeltaDEX - MAX_BONUS;
    else if(rInfoStruct.nItemDeltaDEX < -MAX_PENALTY)
        rInfoStruct.nExtraDEX = rInfoStruct.nItemDeltaDEX + MAX_PENALTY;
        
    rInfoStruct.nItemDeltaCON = rInfoStruct.nDeltaCON + rInfoStruct.nItemCON;
    if (bFuncs)
        rInfoStruct.nExtraCON = 0; //NWNX boosts aren't capped, so we don't need to handle caps
    else if (rInfoStruct.nItemDeltaCON > MAX_BONUS)
        rInfoStruct.nExtraCON = rInfoStruct.nItemDeltaCON - MAX_BONUS;
    else if(rInfoStruct.nItemDeltaCON < -MAX_PENALTY)
        rInfoStruct.nExtraCON = rInfoStruct.nItemDeltaCON + MAX_PENALTY;

    return rInfoStruct;
}

int _prc_inc_GetItemACBonus(object oItem)
{
    int nArmorBonus = 0;
    itemproperty iProp = GetFirstItemProperty(oItem);
    while(GetIsItemPropertyValid(iProp))
    {
        if(GetItemPropertyType(iProp) == ITEM_PROPERTY_AC_BONUS && GetItemPropertyDurationType(iProp) == DURATION_TYPE_PERMANENT)
            nArmorBonus = PRCMax(nArmorBonus, GetItemPropertyCostTableValue(iProp)); //TODO: pick the biggest? the first? stack them?
        iProp = GetNextItemProperty(oItem);
    }    
    return nArmorBonus;
}

int _prc_inc_GetArmorMaxDEXBonus(object oArmor, int nMaxDexACBonus = 100)
{
    if (GetIsObjectValid(oArmor))
    {
        int nArmorAC = GetItemACValue(oArmor) - _prc_inc_GetItemACBonus(oArmor); //Exclude magical AC bonus to figure out armor type
        switch(nArmorAC)
        {
            //TODO: CAN THESE BE LOOKED UP IN A 2DA OR SOMEWHERE?
            case 8: case 7: case 6:
                nMaxDexACBonus = 1; break;
            case 5:
                nMaxDexACBonus = 2; break;
            case 4: case 3:
                nMaxDexACBonus = 4; break;
            case 2:
                nMaxDexACBonus = 6; break;
            case 1:
                nMaxDexACBonus = 8; break;
        }
    }
    return nMaxDexACBonus;
}

struct _prc_inc_ac_info_struct{
    int nArmorBase;
    int nArmorBonus;

    int nShieldBase;
    int nShieldBonus;
    
    int nDodgeBonus;
    int nNaturalBonus;
    int nDeflectionBonus;
    
    int nDEXBonus;
};

struct _prc_inc_ac_info_struct _prc_inc_ACInfo(object oTemplate)
{
    struct _prc_inc_ac_info_struct ac_info;
    
    object oArmorItem = GetItemInSlot(INVENTORY_SLOT_CHEST, oTemplate);
    ac_info.nArmorBonus = _prc_inc_GetItemACBonus(oArmorItem);
    ac_info.nArmorBase = GetItemACValue(oArmorItem) - ac_info.nArmorBonus;
    
    ac_info.nDodgeBonus = GetItemACValue(GetItemInSlot(INVENTORY_SLOT_BOOTS, oTemplate));
    ac_info.nNaturalBonus = GetItemACValue(GetItemInSlot(INVENTORY_SLOT_NECK, oTemplate));

    ac_info.nDeflectionBonus = GetItemACValue(GetItemInSlot(INVENTORY_SLOT_HEAD, oTemplate));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_CLOAK, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_RIGHTRING, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_LEFTRING, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_BELT, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_ARROWS, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_BULLETS, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_BOLTS, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oTemplate)));
    ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate)));

    object oOffHandItem = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oTemplate);
    ac_info.nShieldBase = 0;
    ac_info.nShieldBonus = 0;
    switch (GetBaseItemType(oOffHandItem))
    {
        case BASE_ITEM_SMALLSHIELD:
            ac_info.nShieldBase = 1;
            ac_info.nShieldBonus = GetItemACValue(oOffHandItem) - ac_info.nShieldBase;
            break;
        case BASE_ITEM_LARGESHIELD:
            ac_info.nShieldBase = 2;
            ac_info.nShieldBonus = GetItemACValue(oOffHandItem) - ac_info.nShieldBase;
            break;
        case BASE_ITEM_TOWERSHIELD:
            ac_info.nShieldBase = 3;
            ac_info.nShieldBonus = GetItemACValue(oOffHandItem) - ac_info.nShieldBase;
            break;
        default: //A weapon
            ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(oOffHandItem));
            break;
    }
    
    object oArmsItem = GetItemInSlot(INVENTORY_SLOT_ARMS, oTemplate);
    switch (GetBaseItemType(oArmsItem))
    {
        case BASE_ITEM_BRACER:
            ac_info.nShieldBonus = PRCMax(ac_info.nShieldBonus, GetItemACValue(oArmsItem));
            break;
        case BASE_ITEM_GLOVES:
        default:
            ac_info.nDeflectionBonus = PRCMax(ac_info.nDeflectionBonus, GetItemACValue(oArmsItem));
            break;
    }
    
    if (ac_info.nArmorBonus > 20)
        ac_info.nArmorBonus = 20;
    if (ac_info.nDodgeBonus > 20)
        ac_info.nDodgeBonus = 20;
    if (ac_info.nNaturalBonus > 20)
        ac_info.nNaturalBonus = 20;
    if (ac_info.nDeflectionBonus > 20)
        ac_info.nDeflectionBonus = 20;
    if (ac_info.nShieldBonus > 20)
        ac_info.nShieldBonus = 20;
        
    ac_info.nDEXBonus = PRCMin(GetAbilityModifier(ABILITY_DEXTERITY, oTemplate), _prc_inc_GetArmorMaxDEXBonus(oArmorItem));
    //TODO: make sure this isn't < 0?
    
    return ac_info;
}

//Estimate natural AC of the creature oTemplate
int _prc_inc_CreatureNaturalAC(object oTemplate)
{
    struct _prc_inc_ac_info_struct ac_info = _prc_inc_ACInfo(oTemplate);

    //TODO: GetAC(oTemplate) often returns an AC different (usually higher) than the combat debugging log indicates it should be.
        //Note that combat debugging doesn't report DEX bonus, Monk WIS bonus, etc.; where does this come in?
    int nNaturalAC = GetAC(oTemplate)
                   - 10                           // Adjust for base AC
                   - ac_info.nDEXBonus               // And Dex bonus
                   - ac_info.nArmorBase           // Etc...
                   - ac_info.nArmorBonus
                   - ac_info.nDodgeBonus
                   - ac_info.nNaturalBonus
                   - ac_info.nDeflectionBonus
                   - ac_info.nShieldBase
                   - ac_info.nShieldBonus;
    
    //TODO:
        //Subtract +4 Dodge bonus if template has Haste?
        //Subtract +1 AC / each 5 points of the Tumble skill?
        //Subtract Monk AC from level progression?
        //Subtract WIS AC if Monk/Ninja, etc.?
        //Make sure nNaturalAC is not < 0 (it was for me once using the old method of calculation, which is why I created this new one)
    
    if (DEBUG_NATURAL_AC_CALCULATION || DEBUG)
    {
        DoDebug("_prc_inc_CreatureNaturalAC: total ac: " + IntToString(GetAC(oTemplate)));
        DoDebug("_prc_inc_CreatureNaturalAC: base ac: " + IntToString(10));
        DoDebug("_prc_inc_CreatureNaturalAC: armor base ac: " + IntToString(ac_info.nArmorBase));
        DoDebug("_prc_inc_CreatureNaturalAC: armor bonus ac: " + IntToString(ac_info.nArmorBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: shield base ac: " + IntToString(ac_info.nShieldBase));
        DoDebug("_prc_inc_CreatureNaturalAC: shield bonus ac: " + IntToString(ac_info.nShieldBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: dodge bonus ac: " + IntToString(ac_info.nDodgeBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: natural bonus ac: " + IntToString(ac_info.nNaturalBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: deflection bonus ac: " + IntToString(ac_info.nDeflectionBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: dex ac: " + IntToString(ac_info.nDEXBonus));
        DoDebug("_prc_inc_CreatureNaturalAC: calculated natural ac: " + IntToString(nNaturalAC));
    }
        
    //TODO: combat debugging shows actual natural AC (as well as other type); compare with that to debug.
    
    return nNaturalAC;
}

int _prc_inc_GetFeatDeathAttackLevel(int nFeat)
{
    switch(nFeat)
    {
        case FEAT_PRESTIGE_DEATH_ATTACK_1: return 1;
        case FEAT_PRESTIGE_DEATH_ATTACK_2: return 2;
        case FEAT_PRESTIGE_DEATH_ATTACK_3: return 3;
        case FEAT_PRESTIGE_DEATH_ATTACK_4: return 4;
        case FEAT_PRESTIGE_DEATH_ATTACK_5: return 5;
        case FEAT_PRESTIGE_DEATH_ATTACK_6: return 6;
        case FEAT_PRESTIGE_DEATH_ATTACK_7: return 7;
        case FEAT_PRESTIGE_DEATH_ATTACK_8: return 8;
        case FEAT_PRESTIGE_DEATH_ATTACK_9: return 9;
        case FEAT_PRESTIGE_DEATH_ATTACK_10: return 10;
        case FEAT_PRESTIGE_DEATH_ATTACK_11: return 11;
        case FEAT_PRESTIGE_DEATH_ATTACK_12: return 12;
        case FEAT_PRESTIGE_DEATH_ATTACK_13: return 13;
        case FEAT_PRESTIGE_DEATH_ATTACK_14: return 14;
        case FEAT_PRESTIGE_DEATH_ATTACK_15: return 15;
        case FEAT_PRESTIGE_DEATH_ATTACK_16: return 16;
        case FEAT_PRESTIGE_DEATH_ATTACK_17: return 17;
        case FEAT_PRESTIGE_DEATH_ATTACK_18: return 18;
        case FEAT_PRESTIGE_DEATH_ATTACK_19: return 19;
        case FEAT_PRESTIGE_DEATH_ATTACK_20: return 20;
    }
    return 0;
}

int _prc_inc_GetHasFeat(object oTemplate, int nFeat)
{
    //If oTemplate has the feat FEAT_SNEAK_ATTACK_10, GetHasFeat() always says
    //it has FEAT_PRESTIGE_DEATH_ATTACK_1 through FEAT_PRESTIGE_DEATH_ATTACK_20,
    //whether it actually does or not. Work around this as follows:
    int nSuppress=0;
    int FEAT_SNEAK_ATTACK_10 = 353;    
    if(GetHasFeat(FEAT_SNEAK_ATTACK_10, oTemplate))
    {
        int nFeatDeathAttackLevel = _prc_inc_GetFeatDeathAttackLevel(nFeat);
        if(nFeatDeathAttackLevel)
        {
            int nActualDeathAttackLevel = 0;
            nActualDeathAttackLevel += (GetLevelByClass(CLASS_TYPE_ASSASSIN, oTemplate) + 1) / 2;
            //TODO: Add other classes here? OR use GetTotalSneakAttackDice(), etc. from prc_inc_sneak instead?
            if(nFeatDeathAttackLevel > nActualDeathAttackLevel)
                nSuppress = 1;
        }
    }

    return GetHasFeat(nFeat, oTemplate) && !nSuppress;
}

int _prc_inc_shifting_GetIsCreatureHarmless(object oTemplate)
{
    return GetChallengeRating(oTemplate) < 1.0;
}

int _prc_inc_shifting_CharacterLevelRequirement(object oTemplate)
{
    return GetPRCSwitch(PNP_SHFT_USECR) ? FloatToInt(GetChallengeRating(oTemplate)) : GetHitDice(oTemplate);
}

int _prc_inc_shifting_ShifterLevelRequirement(object oTemplate)
{
    int nRacialType = MyPRCGetRacialType(oTemplate);
    int nSize = PRCGetCreatureSize(oTemplate);
    int nLevelRequired = 0;

    // Size tests
    if(nSize >= CREATURE_SIZE_HUGE)
        nLevelRequired = PRCMax(nLevelRequired, 7);
    if(nSize == CREATURE_SIZE_LARGE)
        nLevelRequired = PRCMax(nLevelRequired, 3);
    if(nSize == CREATURE_SIZE_MEDIUM)
        nLevelRequired = PRCMax(nLevelRequired, 1);
    if(nSize == CREATURE_SIZE_SMALL)
        nLevelRequired = PRCMax(nLevelRequired, 1);
    if(nSize <= CREATURE_SIZE_TINY)
        nLevelRequired = PRCMax(nLevelRequired, 3);

    // Type tests
    if(nRacialType == RACIAL_TYPE_OUTSIDER)
        nLevelRequired = PRCMax(nLevelRequired, 9);
    if(nRacialType == RACIAL_TYPE_ELEMENTAL)
        nLevelRequired = PRCMax(nLevelRequired, 9);
    if(nRacialType == RACIAL_TYPE_CONSTRUCT)
        nLevelRequired = PRCMax(nLevelRequired, 8);
    if(nRacialType == RACIAL_TYPE_UNDEAD)
        nLevelRequired = PRCMax(nLevelRequired, 8);
    if(nRacialType == RACIAL_TYPE_DRAGON)
        nLevelRequired = PRCMax(nLevelRequired, 7);
    if(nRacialType == RACIAL_TYPE_ABERRATION)
        nLevelRequired = PRCMax(nLevelRequired, 6);
    if(nRacialType == RACIAL_TYPE_OOZE)
        nLevelRequired = PRCMax(nLevelRequired, 6);
    if(nRacialType == RACIAL_TYPE_MAGICAL_BEAST)
        nLevelRequired = PRCMax(nLevelRequired, 5);
    if(nRacialType == RACIAL_TYPE_GIANT)
        nLevelRequired = PRCMax(nLevelRequired, 4);
    if(nRacialType == RACIAL_TYPE_VERMIN)
        nLevelRequired = PRCMax(nLevelRequired, 4);
    if(nRacialType == RACIAL_TYPE_BEAST)
        nLevelRequired = PRCMax(nLevelRequired, 3);
    if(nRacialType == RACIAL_TYPE_ANIMAL)
        nLevelRequired = PRCMax(nLevelRequired, 2);
    if(nRacialType == RACIAL_TYPE_HUMANOID_MONSTROUS)
        nLevelRequired = PRCMax(nLevelRequired, 2);
    if(nRacialType == RACIAL_TYPE_DWARF              ||
       nRacialType == RACIAL_TYPE_ELF                ||
       nRacialType == RACIAL_TYPE_GNOME              ||
       nRacialType == RACIAL_TYPE_HUMAN              ||
       nRacialType == RACIAL_TYPE_HALFORC            ||
       nRacialType == RACIAL_TYPE_HALFELF            ||
       nRacialType == RACIAL_TYPE_HALFLING           ||
       nRacialType == RACIAL_TYPE_HUMANOID_ORC       ||
       nRacialType == RACIAL_TYPE_HUMANOID_REPTILIAN
       )
        nLevelRequired = PRCMax(nLevelRequired, 1);
        
    return nLevelRequired;
}

int _prc_inc_shifting_GetCanFormCast(object oTemplate)
{
    int nRacialType = MyPRCGetRacialType(oTemplate);

    // Need to have hands, and the ability to speak

    switch (nRacialType)
    {
        case RACIAL_TYPE_ABERRATION:
        case RACIAL_TYPE_ANIMAL:
        case RACIAL_TYPE_BEAST:
        case RACIAL_TYPE_MAGICAL_BEAST:
        case RACIAL_TYPE_VERMIN:
        case RACIAL_TYPE_OOZE:
//        case RACIAL_TYPE_PLANT:
            // These forms can't cast spells
            return FALSE;
        case RACIAL_TYPE_DWARF:
        case RACIAL_TYPE_ELF:
        case RACIAL_TYPE_GNOME:
        case RACIAL_TYPE_HALFLING:
        case RACIAL_TYPE_HALFELF:
        case RACIAL_TYPE_HALFORC:
        case RACIAL_TYPE_HUMAN:
        case RACIAL_TYPE_CONSTRUCT:
        case RACIAL_TYPE_DRAGON:
        case RACIAL_TYPE_HUMANOID_GOBLINOID:
        case RACIAL_TYPE_HUMANOID_MONSTROUS:
        case RACIAL_TYPE_HUMANOID_ORC:
        case RACIAL_TYPE_HUMANOID_REPTILIAN:
        case RACIAL_TYPE_ELEMENTAL:
        case RACIAL_TYPE_FEY:
        case RACIAL_TYPE_GIANT:
        case RACIAL_TYPE_OUTSIDER:
        case RACIAL_TYPE_SHAPECHANGER:
        case RACIAL_TYPE_UNDEAD:
            // Break and go return TRUE at the end of the function
            break;

        default:{
            if(DEBUG) DoDebug("prc_inc_shifting: _GetCanFormCast(): Unknown racial type: " + IntToString(nRacialType));
        }
    }

    return TRUE;
}

string _prc_inc_AbilityTypeString(int nAbilityType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_abilities", "Name", nAbilityType)));
}

string _prc_inc_AlignmentGroupString(int nAlignmentGroup)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_aligngrp", "Name", nAlignmentGroup)));
}

string _prc_inc_BonusFeatTypeString(int nBonusFeatType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_feats", "Name", nBonusFeatType)));
}

string _prc_inc_ClassTypeString(int nClassType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("classes", "Name", nClassType)));
}

string _prc_inc_CostTableEntryString(int nCostTable, int nCostTableValue)
{
    string sCostTableName = Get2DACache("iprp_costtable", "Name", nCostTable);
    if(sCostTableName == "" || sCostTableName == "****")
        return "??? (" + IntToString(nCostTable) + " / " + IntToString(nCostTableValue) + ")";
    string sCostTableEntry = Get2DACache(sCostTableName, "Name", nCostTableValue);
    if(sCostTableEntry == "" || sCostTableEntry == "****")
        return "??? (" + sCostTableName + " / " + IntToString(nCostTableValue) + ")";
    return GetStringByStrRef(StringToInt(sCostTableEntry));
}

string _prc_inc_DamageTypeString(int nDamageType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_damagetype", "Name", nDamageType)));
}

string _prc_inc_ImmunityTypeString(int nImmunityType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_immunity", "Name", nImmunityType)));
}

string _prc_inc_OnHitSpellTypeString(int nOnHitSpellType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_onhitspell", "Name", nOnHitSpellType)));
}

string _prc_inc_OnHitTypeString(int nOnHitSpellType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_onhit", "Name", nOnHitSpellType)));
}

string _prc_inc_OnMonsterHitTypeString(int nOnMonsterHitType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_monsterhit", "Name", nOnMonsterHitType)));
}

string _prc_inc_SavingThrowElementTypeString(int nSavingThrowType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_saveelement", "Name", nSavingThrowType)));
}

string _prc_inc_SavingThrowTypeString(int nSavingThrowType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_savingthrow", "Name", nSavingThrowType)));
}

string _prc_inc_SkillTypeString(int nSkillType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("skills", "Name", nSkillType)));
}

string _prc_inc_SpecialWalkTypeString(int nWalkType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_walk", "Name", nWalkType)));
}

string _prc_inc_SpellSchoolTypeString(int nSpellSchoolType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_spellshl", "Name", nSpellSchoolType)));
}

string _prc_inc_SpellTypeString(int nOnHitSpellType)
{
    return GetStringByStrRef(StringToInt(Get2DACache("iprp_spells", "Name", nOnHitSpellType)));
}

string _prc_inc_VisualEffectString(int nVisualEffect)
{
    //TODO: Look up in 2da (which one?)
    switch(nVisualEffect)
    {
        case ITEM_VISUAL_ACID:
            return "Acid";
        case ITEM_VISUAL_COLD:
            return "Cold";
        case ITEM_VISUAL_ELECTRICAL:
            return "Electrical";
        case ITEM_VISUAL_FIRE:
            return "Fire";
        case ITEM_VISUAL_SONIC:
            return "Sonic";
        case ITEM_VISUAL_HOLY:
            return "Holy";
        case ITEM_VISUAL_EVIL:
            return "Evil";
    }
    return "???";
}

string _prc_inc_ItemPropertyString(itemproperty iprop)
{
    int nType = GetItemPropertyType(iprop);
    int nSubType = GetItemPropertySubType(iprop);
    int nDurationType = GetItemPropertyDurationType(iprop);
    int nParam1 = GetItemPropertyParam1(iprop);
    int nParam1Value = GetItemPropertyParam1Value(iprop);
    int nCostTable = GetItemPropertyCostTable(iprop);
    int nCostTableValue = GetItemPropertyCostTableValue(iprop);
    string sType = IntToString(nType);
    string sSubType = IntToString(nSubType);
    string sDurationType = IntToString(nDurationType);
    string sParam1 = IntToString(nParam1);
    string sParam1Value = IntToString(nParam1Value);
    string sCostTable = IntToString(nCostTable);
    string sCostTableValue = IntToString(nCostTableValue);
    string sResult = 
        "Typ: " + sType + "; " 
        + "SubTyp: " + sSubType + "; "
        + "DurTyp: " + sDurationType + "; "
        + "Parm: " + sParam1 + "; "
        + "ParmVal: " + sParam1Value + "; "
        + "CTab: " + sCostTable + "; "
        + "CVal: " + sCostTableValue;
    string sTypeName = GetStringByStrRef(StringToInt(Get2DACache("itempropdef", "Name", nType)));
    switch (nType)
    {
        //TODO: these are all the possible cases; need to handle more of them.
        //DONE case ITEM_PROPERTY_ABILITY_BONUS:
        //DONE case ITEM_PROPERTY_AC_BONUS:
        // case ITEM_PROPERTY_AC_BONUS_VS_ALIGNMENT_GROUP:
        // case ITEM_PROPERTY_AC_BONUS_VS_DAMAGE_TYPE:
        // case ITEM_PROPERTY_AC_BONUS_VS_RACIAL_GROUP:
        // case ITEM_PROPERTY_AC_BONUS_VS_SPECIFIC_ALIGNMENT:
        //DONE case ITEM_PROPERTY_ENHANCEMENT_BONUS:
        //DONE case ITEM_PROPERTY_ENHANCEMENT_BONUS_VS_ALIGNMENT_GROUP:
        // case ITEM_PROPERTY_ENHANCEMENT_BONUS_VS_RACIAL_GROUP:
        // case ITEM_PROPERTY_ENHANCEMENT_BONUS_VS_SPECIFIC_ALIGNEMENT: 
        // case ITEM_PROPERTY_DECREASED_ENHANCEMENT_MODIFIER
        //DONE case ITEM_PROPERTY_BASE_ITEM_WEIGHT_REDUCTION
        //DONE case ITEM_PROPERTY_BONUS_FEAT:
        // case ITEM_PROPERTY_BONUS_SPELL_SLOT_OF_LEVEL_N:
        //DONE case ITEM_PROPERTY_CAST_SPELL:
        //DONE case ITEM_PROPERTY_DAMAGE_BONUS:
        //DONE case ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP:
        //case ITEM_PROPERTY_DAMAGE_BONUS_VS_RACIAL_GROUP:
        //case ITEM_PROPERTY_DAMAGE_BONUS_VS_SPECIFIC_ALIGNMENT:
        //DONE case ITEM_PROPERTY_IMMUNITY_DAMAGE_TYPE:
        // case ITEM_PROPERTY_DECREASED_DAMAGE:
        //DONE case ITEM_PROPERTY_DAMAGE_REDUCTION:
        //DONE case ITEM_PROPERTY_DAMAGE_RESISTANCE:
        //DONE case ITEM_PROPERTY_DAMAGE_VULNERABILITY:
        //DONE case ITEM_PROPERTY_DARKVISION:
        //DONE case ITEM_PROPERTY_DECREASED_ABILITY_SCORE:
        // case ITEM_PROPERTY_DECREASED_AC:
        // case ITEM_PROPERTY_DECREASED_SKILL_MODIFIER: //TODO: e.g. S1-Tomb of Horrors: DesertDragon
        // case ITEM_PROPERTY_ENHANCED_CONTAINER_REDUCED_WEIGHT:
        //DONE case ITEM_PROPERTY_EXTRA_MELEE_DAMAGE_TYPE:
        // case ITEM_PROPERTY_EXTRA_RANGED_DAMAGE_TYPE:
        //DONE case ITEM_PROPERTY_HASTE:
        // case ITEM_PROPERTY_HOLY_AVENGER:
        //DONE case ITEM_PROPERTY_IMMUNITY_MISCELLANEOUS:
        //DONE case ITEM_PROPERTY_IMPROVED_EVASION:
        //DONE case ITEM_PROPERTY_SPELL_RESISTANCE:
        //DONE case ITEM_PROPERTY_SAVING_THROW_BONUS:
        //DONE case ITEM_PROPERTY_SAVING_THROW_BONUS_SPECIFIC:
        //DONE case ITEM_PROPERTY_KEEN:
        //DONE case ITEM_PROPERTY_LIGHT:
        // case ITEM_PROPERTY_MIGHTY:
        // case ITEM_PROPERTY_MIND_BLANK:
        // case ITEM_PROPERTY_NO_DAMAGE:
        //DONE case ITEM_PROPERTY_ON_HIT_PROPERTIES:
        //DONE case ITEM_PROPERTY_DECREASED_SAVING_THROWS:
        //DONE case ITEM_PROPERTY_DECREASED_SAVING_THROWS_SPECIFIC:
        //DONE case ITEM_PROPERTY_REGENERATION:
        //DONE case ITEM_PROPERTY_SKILL_BONUS:
        //DONE case ITEM_PROPERTY_IMMUNITY_SPECIFIC_SPELL:
        //DONE case ITEM_PROPERTY_IMMUNITY_SPELL_SCHOOL:
        // case ITEM_PROPERTY_THIEVES_TOOLS:
        //DONE case ITEM_PROPERTY_ATTACK_BONUS:
        //DONE case ITEM_PROPERTY_ATTACK_BONUS_VS_ALIGNMENT_GROUP:
        // case ITEM_PROPERTY_ATTACK_BONUS_VS_RACIAL_GROUP:
        // case ITEM_PROPERTY_ATTACK_BONUS_VS_SPECIFIC_ALIGNMENT:
        // case ITEM_PROPERTY_DECREASED_ATTACK_MODIFIER:
        // case ITEM_PROPERTY_UNLIMITED_AMMUNITION:
        // case ITEM_PROPERTY_USE_LIMITATION_ALIGNMENT_GROUP:
        // case ITEM_PROPERTY_USE_LIMITATION_CLASS:
        // case ITEM_PROPERTY_USE_LIMITATION_RACIAL_TYPE:
        // case ITEM_PROPERTY_USE_LIMITATION_SPECIFIC_ALIGNMENT:
        // case ITEM_PROPERTY_USE_LIMITATION_TILESET:
        //DONE case ITEM_PROPERTY_REGENERATION_VAMPIRIC:
        // case ITEM_PROPERTY_TRAP:
        //DONE case ITEM_PROPERTY_TRUE_SEEING:
        //DONE case ITEM_PROPERTY_ON_MONSTER_HIT:
        //DONE case ITEM_PROPERTY_TURN_RESISTANCE:
        //DONE case ITEM_PROPERTY_MASSIVE_CRITICALS:
        //DONE case ITEM_PROPERTY_FREEDOM_OF_MOVEMENT:
        //DONE case ITEM_PROPERTY_MONSTER_DAMAGE:
        //DONE case ITEM_PROPERTY_IMMUNITY_SPELLS_BY_LEVEL:
        //DONE case ITEM_PROPERTY_SPECIAL_WALK:
        // case ITEM_PROPERTY_HEALERS_KIT:
        // case ITEM_PROPERTY_WEIGHT_INCREASE:
        //DONE case ITEM_PROPERTY_ONHITCASTSPELL:
        //DONE case ITEM_PROPERTY_VISUALEFFECT:
        // case ITEM_PROPERTY_ARCANE_SPELL_FAILURE:
        
        //Completely ignore
        case ITEM_PROPERTY_BASE_ITEM_WEIGHT_REDUCTION:
            return "";

        //Property name only
        case ITEM_PROPERTY_DARKVISION:
        case ITEM_PROPERTY_FREEDOM_OF_MOVEMENT:
        case ITEM_PROPERTY_HASTE:
        case ITEM_PROPERTY_IMPROVED_EVASION:
        case ITEM_PROPERTY_KEEN:
        case ITEM_PROPERTY_TRUE_SEEING:
            return sTypeName;

        //Interpret cost table information
        case ITEM_PROPERTY_AC_BONUS:
        case ITEM_PROPERTY_ATTACK_BONUS:
        case ITEM_PROPERTY_ENHANCEMENT_BONUS:
        case ITEM_PROPERTY_IMMUNITY_SPECIFIC_SPELL:
        case ITEM_PROPERTY_IMMUNITY_SPELLS_BY_LEVEL:
        case ITEM_PROPERTY_LIGHT:
        case ITEM_PROPERTY_MASSIVE_CRITICALS:
        case ITEM_PROPERTY_MONSTER_DAMAGE:
        case ITEM_PROPERTY_REGENERATION:
        case ITEM_PROPERTY_SPELL_RESISTANCE:
        case ITEM_PROPERTY_TURN_RESISTANCE:
            return sTypeName + ": " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        
        //Interpret cost table information; interpret subtype as damage type
        case ITEM_PROPERTY_DAMAGE_BONUS:
        case ITEM_PROPERTY_DAMAGE_RESISTANCE:
        case ITEM_PROPERTY_DAMAGE_VULNERABILITY:
        case ITEM_PROPERTY_IMMUNITY_DAMAGE_TYPE:
            return sTypeName + ": " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue) + " " + _prc_inc_DamageTypeString(nSubType);

        //Interpret cost table information; interpret subtype as racial group
        case ITEM_PROPERTY_ATTACK_BONUS_VS_ALIGNMENT_GROUP:
        case ITEM_PROPERTY_ENHANCEMENT_BONUS_VS_ALIGNMENT_GROUP:
            return sTypeName + ": " + _prc_inc_AlignmentGroupString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);

        //Special handling
        case ITEM_PROPERTY_ABILITY_BONUS:
        case ITEM_PROPERTY_DECREASED_ABILITY_SCORE:
            return sTypeName + ": " + _prc_inc_AbilityTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_BONUS_FEAT:
            return sTypeName + ": " + _prc_inc_BonusFeatTypeString(nSubType);
        case ITEM_PROPERTY_CAST_SPELL:
            return sTypeName + ": " + _prc_inc_SpellTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP:
            return sTypeName + ": " + _prc_inc_AlignmentGroupString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue) + " " + _prc_inc_DamageTypeString(nParam1Value);
        case ITEM_PROPERTY_DAMAGE_REDUCTION:
            return sTypeName + ": " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue) + " / " + IntToString(StringToInt(sSubType)+1);
        case ITEM_PROPERTY_EXTRA_MELEE_DAMAGE_TYPE:
        case ITEM_PROPERTY_EXTRA_RANGED_DAMAGE_TYPE:
            return sTypeName + ": " + _prc_inc_DamageTypeString(nSubType);
        case ITEM_PROPERTY_IMMUNITY_MISCELLANEOUS:
            return sTypeName + ": " + _prc_inc_ImmunityTypeString(nSubType);
        case ITEM_PROPERTY_IMMUNITY_SPELL_SCHOOL:
            return sTypeName + ": " + _prc_inc_SpellSchoolTypeString(nSubType);
        case ITEM_PROPERTY_ON_HIT_PROPERTIES:
            return sTypeName + ": " + _prc_inc_OnHitTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_ONHITCASTSPELL:
            return sTypeName + ": " + _prc_inc_OnHitSpellTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_ON_MONSTER_HIT:
            return sTypeName + ": " + _prc_inc_OnMonsterHitTypeString(nSubType) + " " + IntToString(nCostTableValue+1);
        case ITEM_PROPERTY_SKILL_BONUS:
            return sTypeName + ": " + _prc_inc_SkillTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_SPECIAL_WALK:
            return sTypeName + ": " + _prc_inc_SpecialWalkTypeString(nSubType);            
        case ITEM_PROPERTY_REGENERATION_VAMPIRIC:
            return sTypeName + ": " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
        case ITEM_PROPERTY_VISUALEFFECT:
            return sTypeName + ": " + _prc_inc_VisualEffectString(nSubType);

        case ITEM_PROPERTY_DECREASED_SAVING_THROWS_SPECIFIC:
        case ITEM_PROPERTY_SAVING_THROW_BONUS_SPECIFIC:
            return sTypeName + ": " + _prc_inc_SavingThrowTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);

        case ITEM_PROPERTY_DECREASED_SAVING_THROWS:
        case ITEM_PROPERTY_SAVING_THROW_BONUS:
            return sTypeName + ": " + _prc_inc_SavingThrowElementTypeString(nSubType) + " " + _prc_inc_CostTableEntryString(nCostTable, nCostTableValue);
    }
    return sTypeName + " (" + sResult + ")";
}

string _prc_inc_EffectString(effect eEffect)
{
    int nType = GetEffectType(eEffect);
    int nSubType = GetEffectSubType(eEffect);
    int nDurationType = GetEffectDurationType(eEffect);
    int nSpellId = GetEffectSpellId(eEffect);
    string sType = IntToString(nType);
    string sSubType = IntToString(nSubType);
    string sDurationType = IntToString(nDurationType);
    string sSpellId = IntToString(nSpellId);

    //Decode type if possible
    //TODO: look up in 2da (which one?) instead of having a big switch statement
    switch (nType)
    {
        case EFFECT_TYPE_INVALIDEFFECT               : sType = "EFFECT_TYPE_INVALIDEFFECT"; break;
        case EFFECT_TYPE_DAMAGE_RESISTANCE           : sType = "EFFECT_TYPE_DAMAGE_RESISTANCE"; break;
        //case EFFECT_TYPE_ABILITY_BONUS             : sType = "EFFECT_TYPE_ABILITY_BONUS"; break;
        case EFFECT_TYPE_REGENERATE                  : sType = "EFFECT_TYPE_REGENERATE"; break;
        //case EFFECT_TYPE_SAVING_THROW_BONUS        : sType = "EFFECT_TYPE_SAVING_THROW_BONUS"; break;
        //case EFFECT_TYPE_MODIFY_AC                 : sType = "EFFECT_TYPE_MODIFY_AC"; break;
        //case EFFECT_TYPE_ATTACK_BONUS              : sType = "EFFECT_TYPE_ATTACK_BONUS"; break;
        case EFFECT_TYPE_DAMAGE_REDUCTION            : sType = "EFFECT_TYPE_DAMAGE_REDUCTION"; break;
        //case EFFECT_TYPE_DAMAGE_BONUS              : sType = "EFFECT_TYPE_DAMAGE_BONUS"; break;
        case EFFECT_TYPE_TEMPORARY_HITPOINTS         : sType = "EFFECT_TYPE_TEMPORARY_HITPOINTS"; break;
        //case EFFECT_TYPE_DAMAGE_IMMUNITY           : sType = "EFFECT_TYPE_DAMAGE_IMMUNITY"; break;
        case EFFECT_TYPE_ENTANGLE                    : sType = "EFFECT_TYPE_ENTANGLE"; break;
        case EFFECT_TYPE_INVULNERABLE                : sType = "EFFECT_TYPE_INVULNERABLE"; break;
        case EFFECT_TYPE_DEAF                        : sType = "EFFECT_TYPE_DEAF"; break;
        case EFFECT_TYPE_RESURRECTION                : sType = "EFFECT_TYPE_RESURRECTION"; break;
        case EFFECT_TYPE_IMMUNITY                    : sType = "EFFECT_TYPE_IMMUNITY"; break;
        //case EFFECT_TYPE_BLIND                     : sType = "EFFECT_TYPE_BLIND"; break;
        case EFFECT_TYPE_ENEMY_ATTACK_BONUS          : sType = "EFFECT_TYPE_ENEMY_ATTACK_BONUS"; break;
        case EFFECT_TYPE_ARCANE_SPELL_FAILURE        : sType = "EFFECT_TYPE_ARCANE_SPELL_FAILURE"; break;
        //case EFFECT_TYPE_MOVEMENT_SPEED            : sType = "EFFECT_TYPE_MOVEMENT_SPEED"; break;
        case EFFECT_TYPE_AREA_OF_EFFECT              : sType = "EFFECT_TYPE_AREA_OF_EFFECT"; break;
        case EFFECT_TYPE_BEAM                        : sType = "EFFECT_TYPE_BEAM"; break;
        //case EFFECT_TYPE_SPELL_RESISTANCE          : sType = "EFFECT_TYPE_SPELL_RESISTANCE"; break;
        case EFFECT_TYPE_CHARMED                     : sType = "EFFECT_TYPE_CHARMED"; break;
        case EFFECT_TYPE_CONFUSED                    : sType = "EFFECT_TYPE_CONFUSED"; break;
        case EFFECT_TYPE_FRIGHTENED                  : sType = "EFFECT_TYPE_FRIGHTENED"; break;
        case EFFECT_TYPE_DOMINATED                   : sType = "EFFECT_TYPE_DOMINATED"; break;
        case EFFECT_TYPE_PARALYZE                    : sType = "EFFECT_TYPE_PARALYZE"; break;
        case EFFECT_TYPE_DAZED                       : sType = "EFFECT_TYPE_DAZED"; break;
        case EFFECT_TYPE_STUNNED                     : sType = "EFFECT_TYPE_STUNNED"; break;
        case EFFECT_TYPE_SLEEP                       : sType = "EFFECT_TYPE_SLEEP"; break;
        case EFFECT_TYPE_POISON                      : sType = "EFFECT_TYPE_POISON"; break;
        case EFFECT_TYPE_DISEASE                     : sType = "EFFECT_TYPE_DISEASE"; break;
        case EFFECT_TYPE_CURSE                       : sType = "EFFECT_TYPE_CURSE"; break;
        case EFFECT_TYPE_SILENCE                     : sType = "EFFECT_TYPE_SILENCE"; break;
        case EFFECT_TYPE_TURNED                      : sType = "EFFECT_TYPE_TURNED"; break;
        case EFFECT_TYPE_HASTE                       : sType = "EFFECT_TYPE_HASTE"; break;
        case EFFECT_TYPE_SLOW                        : sType = "EFFECT_TYPE_SLOW"; break;
        case EFFECT_TYPE_ABILITY_INCREASE            : sType = "EFFECT_TYPE_ABILITY_INCREASE"; break;
        case EFFECT_TYPE_ABILITY_DECREASE            : sType = "EFFECT_TYPE_ABILITY_DECREASE"; break;
        case EFFECT_TYPE_ATTACK_INCREASE             : sType = "EFFECT_TYPE_ATTACK_INCREASE"; break;
        case EFFECT_TYPE_ATTACK_DECREASE             : sType = "EFFECT_TYPE_ATTACK_DECREASE"; break;
        case EFFECT_TYPE_DAMAGE_INCREASE             : sType = "EFFECT_TYPE_DAMAGE_INCREASE"; break;
        case EFFECT_TYPE_DAMAGE_DECREASE             : sType = "EFFECT_TYPE_DAMAGE_DECREASE"; break;
        case EFFECT_TYPE_DAMAGE_IMMUNITY_INCREASE    : sType = "EFFECT_TYPE_DAMAGE_IMMUNITY_INCREASE"; break;
        case EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE    : sType = "EFFECT_TYPE_DAMAGE_IMMUNITY_DECREASE"; break;
        case EFFECT_TYPE_AC_INCREASE                 : sType = "EFFECT_TYPE_AC_INCREASE"; break;
        case EFFECT_TYPE_AC_DECREASE                 : sType = "EFFECT_TYPE_AC_DECREASE"; break;
        case EFFECT_TYPE_MOVEMENT_SPEED_INCREASE     : sType = "EFFECT_TYPE_MOVEMENT_SPEED_INCREASE"; break;
        case EFFECT_TYPE_MOVEMENT_SPEED_DECREASE     : sType = "EFFECT_TYPE_MOVEMENT_SPEED_DECREASE"; break;
        case EFFECT_TYPE_SAVING_THROW_INCREASE       : sType = "EFFECT_TYPE_SAVING_THROW_INCREASE"; break;
        case EFFECT_TYPE_SAVING_THROW_DECREASE       : sType = "EFFECT_TYPE_SAVING_THROW_DECREASE"; break;
        case EFFECT_TYPE_SPELL_RESISTANCE_INCREASE   : sType = "EFFECT_TYPE_SPELL_RESISTANCE_INCREASE"; break;
        case EFFECT_TYPE_SPELL_RESISTANCE_DECREASE   : sType = "EFFECT_TYPE_SPELL_RESISTANCE_DECREASE"; break;
        case EFFECT_TYPE_SKILL_INCREASE              : sType = "EFFECT_TYPE_SKILL_INCREASE"; break;
        case EFFECT_TYPE_SKILL_DECREASE              : sType = "EFFECT_TYPE_SKILL_DECREASE"; break;
        case EFFECT_TYPE_INVISIBILITY                : sType = "EFFECT_TYPE_INVISIBILITY"; break;
        case EFFECT_TYPE_IMPROVEDINVISIBILITY        : sType = "EFFECT_TYPE_IMPROVEDINVISIBILITY"; break;
        case EFFECT_TYPE_DARKNESS                    : sType = "EFFECT_TYPE_DARKNESS"; break;
        case EFFECT_TYPE_DISPELMAGICALL              : sType = "EFFECT_TYPE_DISPELMAGICALL"; break;
        case EFFECT_TYPE_ELEMENTALSHIELD             : sType = "EFFECT_TYPE_ELEMENTALSHIELD"; break;
        case EFFECT_TYPE_NEGATIVELEVEL               : sType = "EFFECT_TYPE_NEGATIVELEVEL"; break;
        case EFFECT_TYPE_POLYMORPH                   : sType = "EFFECT_TYPE_POLYMORPH"; break;
        case EFFECT_TYPE_SANCTUARY                   : sType = "EFFECT_TYPE_SANCTUARY"; break;
        case EFFECT_TYPE_TRUESEEING                  : sType = "EFFECT_TYPE_TRUESEEING"; break;
        case EFFECT_TYPE_SEEINVISIBLE                : sType = "EFFECT_TYPE_SEEINVISIBLE"; break;
        case EFFECT_TYPE_TIMESTOP                    : sType = "EFFECT_TYPE_TIMESTOP"; break;
        case EFFECT_TYPE_BLINDNESS                   : sType = "EFFECT_TYPE_BLINDNESS"; break;
        case EFFECT_TYPE_SPELLLEVELABSORPTION        : sType = "EFFECT_TYPE_SPELLLEVELABSORPTION"; break;
        case EFFECT_TYPE_DISPELMAGICBEST             : sType = "EFFECT_TYPE_DISPELMAGICBEST"; break;
        case EFFECT_TYPE_ULTRAVISION                 : sType = "EFFECT_TYPE_ULTRAVISION"; break;
        case EFFECT_TYPE_MISS_CHANCE                 : sType = "EFFECT_TYPE_MISS_CHANCE"; break;
        case EFFECT_TYPE_CONCEALMENT                 : sType = "EFFECT_TYPE_CONCEALMENT"; break;
        case EFFECT_TYPE_SPELL_IMMUNITY              : sType = "EFFECT_TYPE_SPELL_IMMUNITY"; break;
        case EFFECT_TYPE_VISUALEFFECT                : sType = "EFFECT_TYPE_VISUALEFFECT"; break;
        case EFFECT_TYPE_DISAPPEARAPPEAR             : sType = "EFFECT_TYPE_DISAPPEARAPPEAR"; break;
        case EFFECT_TYPE_SWARM                       : sType = "EFFECT_TYPE_SWARM"; break;
        case EFFECT_TYPE_TURN_RESISTANCE_DECREASE    : sType = "EFFECT_TYPE_TURN_RESISTANCE_DECREASE"; break;
        case EFFECT_TYPE_TURN_RESISTANCE_INCREASE    : sType = "EFFECT_TYPE_TURN_RESISTANCE_INCREASE"; break;
        case EFFECT_TYPE_PETRIFY                     : sType = "EFFECT_TYPE_PETRIFY"; break;
        case EFFECT_TYPE_CUTSCENE_PARALYZE           : sType = "EFFECT_TYPE_CUTSCENE_PARALYZE"; break;
        case EFFECT_TYPE_ETHEREAL                    : sType = "EFFECT_TYPE_ETHEREAL"; break;
        case EFFECT_TYPE_SPELL_FAILURE               : sType = "EFFECT_TYPE_SPELL_FAILURE"; break;
        case EFFECT_TYPE_CUTSCENEGHOST               : sType = "EFFECT_TYPE_CUTSCENEGHOST"; break;
        case EFFECT_TYPE_CUTSCENEIMMOBILIZE          : sType = "EFFECT_TYPE_CUTSCENEIMMOBILIZE"; break;
    }

    //Decode subtype if possible
    //TODO: look up in 2da (which one?) instead of having a switch statement
    switch (nSubType)
    {
        case SUBTYPE_MAGICAL                         : sSubType = "SUBTYPE_MAGICAL"; break;
        case SUBTYPE_SUPERNATURAL                    : sSubType = "SUBTYPE_SUPERNATURAL"; break;
        case SUBTYPE_EXTRAORDINARY                   : sSubType = "SUBTYPE_EXTRAORDINARY"; break;
    }

    //Decode duration type if possible
    //TODO: look up in 2da (which one?) instead of having a switch statement
    switch (nDurationType)
    {
        case DURATION_TYPE_INSTANT                   : sDurationType = "DURATION_TYPE_INSTANT"; break;
        case DURATION_TYPE_TEMPORARY                 : sDurationType = "DURATION_TYPE_TEMPORARY"; break;
        case DURATION_TYPE_PERMANENT                 : sDurationType = "DURATION_TYPE_PERMANENT"; break;
    }
    
    string sResult = 
        "EFFECT Type: " + sType + "; " 
        + "SubType: " + sSubType + "; "
        + "DurationType: " + sDurationType + "; "
        + "SpellId: " + sSpellId;

    return sResult;
}

void _prc_inc_PrintShapeInfo(object oPC, string sMessage)
{
    if(!GetLocalInt(oPC, "PRC_SuppressChatPrint"))
        SendMessageToPC(oPC, sMessage); //Send to chat window in game
    if(GetLocalInt(oPC, "PRC_EnableLogPrint"))
        PrintString(sMessage); //Write to log file for reference
}

void _prc_inc_PrintClassInfo(string sPrefix, object oPC, object oTemplate, int nClassType)
{
    if (nClassType != CLASS_TYPE_INVALID)
    {
        int nLevel = GetLevelByClass(nClassType, oTemplate);
        string sClassName = _prc_inc_ClassTypeString(nClassType);
        _prc_inc_PrintShapeInfo(oPC, sPrefix + sClassName + " (" + IntToString(nLevel) + ")");
    }
}

void _prc_inc_PrintItemProperty(string sPrefix, object oPC, itemproperty iProp, int bIncludeTemp = FALSE)
{
    int nDurationType = GetItemPropertyDurationType(iProp);
    if(nDurationType == DURATION_TYPE_PERMANENT || (bIncludeTemp && nDurationType == DURATION_TYPE_TEMPORARY))
    {
        string sPropString = _prc_inc_ItemPropertyString(iProp);
        if(sPropString != "")
        {
            if (nDurationType == DURATION_TYPE_TEMPORARY)
                sPropString = GetStringByStrRef(57473+0x01000000) + sPropString; //"TEMPORARY: "
            _prc_inc_PrintShapeInfo(oPC, sPrefix + sPropString);
        }
    }    
}

void _prc_inc_PrintAllItemProperties(string sPrefix, object oPC, object oItem, int bIncludeTemp = FALSE)
{
    itemproperty iProp = GetFirstItemProperty(oItem);
    while(GetIsItemPropertyValid(iProp))
    {
        _prc_inc_PrintItemProperty(sPrefix, oPC, iProp, bIncludeTemp);
        iProp = GetNextItemProperty(oItem);
    }
}

void _prc_inc_PrintEffect(string sPrefix, object oPC, effect eEffect)
{
    if (GetEffectType(eEffect) == EFFECT_TYPE_INVALIDEFFECT)
    {
        //An effect with type EFFECT_TYPE_INVALID is added for each item property
        //They are also added for a couple of other things (Knockdown, summons, etc.)
        //Just skip these
    }
    else
    {
        string sEffectString = _prc_inc_EffectString(eEffect);
        if(sEffectString != "")
            _prc_inc_PrintShapeInfo(oPC, sPrefix + sEffectString);
    }    
}

void _prc_inc_PrintAllEffects(string sPrefix, object oPC, object oItem)
{
    effect eEffect = GetFirstEffect(oItem);
    while(GetIsEffectValid(eEffect))
    {
        _prc_inc_PrintEffect(sPrefix, oPC, eEffect);
        eEffect = GetNextEffect(oItem);
    }
}

//NOTE: THIS FUNCTION HAS A LOT OF CODE IN COMMON WITH _prc_inc_shifting_CreateShifterActiveAbilitiesItem
//TODO: PUT SOME OF IT IN A SHARED FUNCTION THAT THEY BOTH CALL
void _prc_inc_shifting_PrintShifterActiveAbilities(object oPC, object oTemplate)
{
    string sPrefix = GetStringByStrRef(57437+0x01000000); //"Epic Wildshape Spell-Like Abilities:"
    _prc_inc_PrintShapeInfo(oPC, "=== " + sPrefix);
    
    int bPrinted = FALSE;

    object oTemplateHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate);
    itemproperty iProp = GetFirstItemProperty(oTemplateHide);
    while(GetIsItemPropertyValid(iProp))
    {
        if(GetItemPropertyDurationType(iProp) == DURATION_TYPE_PERMANENT && GetItemPropertyType(iProp) == ITEM_PROPERTY_CAST_SPELL)
        {
            _prc_inc_PrintItemProperty("===    ", oPC, iProp);
            bPrinted = TRUE;
        }
        iProp = GetNextItemProperty(oTemplateHide);
    }

    // Loop over shifter_abilitie.2da
    string sNumUses;
    int nSpell, nNumUses, nProps;
    int i = 0;
    while(nSpell = StringToInt(Get2DACache("shifter_abilitie", "Spell", i)))
    {
        // See if the template has this spell
        if(GetHasSpell(nSpell, oTemplate))
        {
            // Determine the number of uses from the 2da
            sNumUses = Get2DACache("shifter_abilitie", "IPCSpellNumUses", i);
            if(sNumUses == "1_USE_PER_DAY")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_1_USE_PER_DAY;
            else if(sNumUses == "2_USES_PER_DAY")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_2_USES_PER_DAY;
            else if(sNumUses == "3_USES_PER_DAY")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_3_USES_PER_DAY;
            else if(sNumUses == "4_USES_PER_DAY")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_4_USES_PER_DAY;
            else if(sNumUses == "5_USES_PER_DAY")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_5_USES_PER_DAY;
            else if(sNumUses == "UNLIMITED_USE")
                nNumUses = IP_CONST_CASTSPELL_NUMUSES_UNLIMITED_USE;
            else{
                if(DEBUG) DoDebug("prc_inc_shifting: _prc_inc_shifting_PrintShifterActiveAbilities(): Unknown IPCSpellNumUses in shifter_abilitie.2da line " + IntToString(i) + ": " + sNumUses);
                nNumUses = -1;
            }

            // Create the itemproperty and print it
            iProp = ItemPropertyCastSpell(StringToInt(Get2DACache("shifter_abilitie", "IPSpell", i)), nNumUses);
            _prc_inc_PrintItemProperty("===    ", oPC, iProp);
            bPrinted = TRUE;
            //TODO: DESTROY iProp?

            // Increment property counter
            nProps += 1;
        }

        // Increment loop counter
        i += 1;
    }
    
    if(!bPrinted)
        _prc_inc_PrintShapeInfo(oPC, "===   " + GetStringByStrRef(57481+0x01000000)); //"None"
}

void _prc_inc_shifting_PrintFeats(object oPC, object oTemplate, int nStartIndex, int nLimitIndex)
{
    //Loop over shifter_feats.2da
    string sFeat;
    int i = nStartIndex;
    while((i < nLimitIndex) && (sFeat = Get2DACache("shifter_feats", "Feat", i)) != "")
    {
        if (_prc_inc_GetHasFeat(oTemplate, StringToInt(sFeat)))
        {
            string sFeatName = GetStringByStrRef(StringToInt(Get2DACache("feat", "Feat", StringToInt(sFeat))));
            _prc_inc_PrintShapeInfo(oPC, "=== " + sFeatName);
        }
        i += 1;
    }
}

void _prc_inc_PrintNaturalAC(object oPC, object oTemplate)
{
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57435+0x01000000) + " " + IntToString(_prc_inc_CreatureNaturalAC(oTemplate))); //Natural AC of form
}

//TODO: TLK Entries
void _prc_inc_PrintDebugItem(object oPC, string oItemType, object oItem)
{
    if (GetIsObjectValid(oItem))
    {
        _prc_inc_PrintShapeInfo(oPC, "====================================");
        _prc_inc_PrintShapeInfo(oPC, "====== " + oItemType);
        _prc_inc_PrintShapeInfo(oPC, "======  NAME: " + GetName(oItem));
        _prc_inc_PrintShapeInfo(oPC, "======  RESREF: " + GetResRef(oItem));
        _prc_inc_PrintShapeInfo(oPC, "======  ITEM PROPERTIES ======");
        _prc_inc_PrintAllItemProperties("=== ", oPC, oItem, TRUE);
        _prc_inc_PrintShapeInfo(oPC, "======  EFFECTS ======");
        _prc_inc_PrintAllEffects("=== ", oPC, oItem);
        _prc_inc_PrintShapeInfo(oPC, "======  OTHER ======");
        if (GetObjectType(oItem) == OBJECT_TYPE_CREATURE)
        {
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(1, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(2, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(3, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(4, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(5, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(6, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(7, oItem));
            _prc_inc_PrintClassInfo("=== ", oPC, oItem, GetClassByPosition(8, oItem));
			
            _prc_inc_PrintShapeInfo(oPC, "------");

            _prc_inc_PrintShapeInfo(oPC, "======  Main hand weapon: " + (GetIsWeaponEffective(oItem, FALSE) ? "Effective" : "Ineffective"));
            _prc_inc_PrintShapeInfo(oPC, "======  Off hand weapon: " + (GetIsWeaponEffective(oItem, TRUE) ? "Effective" : "Ineffective"));
            _prc_inc_PrintShapeInfo(oPC, "======  Immortal: " + (GetImmortal(oItem) ? "Yes" : "No"));
            _prc_inc_PrintShapeInfo(oPC, "------");
                
            _prc_inc_PrintShapeInfo(oPC, "======  Level: " + IntToString(GetHitDice(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  CR: " + FloatToString(GetChallengeRating(oItem), 4, 1));
            _prc_inc_PrintShapeInfo(oPC, "======  Caster Level: " + IntToString(GetCasterLevel(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  XP: " + IntToString(GetXP(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  Alignment: " + IntToString(GetLawChaosValue(oItem)) + " / " + IntToString(GetGoodEvilValue(oItem)));
                //TODO:
                // int GetAlignmentLawChaos(object oCreature);
                // int GetAlignmentGoodEvil(object oCreature);
            _prc_inc_PrintShapeInfo(oPC, "------");

            _prc_inc_PrintShapeInfo(oPC, "======  BAB: " + IntToString(GetBaseAttackBonus(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  HP: " + IntToString(GetCurrentHitPoints(oItem)) + " / " + IntToString(GetMaxHitPoints(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  AC: " + IntToString(GetAC(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  SR: " + IntToString(GetSpellResistance(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "------");

            //TODO: look up names in 2da/TLK?
            _prc_inc_PrintShapeInfo(oPC, "======  STR: " + IntToString(GetAbilityScore(oItem, ABILITY_STRENGTH)) + " (" + IntToString(GetAbilityModifier(ABILITY_STRENGTH, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "======  DEX: " + IntToString(GetAbilityScore(oItem, ABILITY_DEXTERITY)) + " (" + IntToString(GetAbilityModifier(ABILITY_DEXTERITY, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "======  CON: " + IntToString(GetAbilityScore(oItem, ABILITY_CONSTITUTION)) + " (" + IntToString(GetAbilityModifier(ABILITY_CONSTITUTION, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "======  INT: " + IntToString(GetAbilityScore(oItem, ABILITY_INTELLIGENCE)) + " (" + IntToString(GetAbilityModifier(ABILITY_INTELLIGENCE, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "======  WIS: " + IntToString(GetAbilityScore(oItem, ABILITY_WISDOM)) + " (" + IntToString(GetAbilityModifier(ABILITY_WISDOM, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "======  CHA: " + IntToString(GetAbilityScore(oItem, ABILITY_CHARISMA)) + " (" + IntToString(GetAbilityModifier(ABILITY_CHARISMA, oItem)) + ")");
            _prc_inc_PrintShapeInfo(oPC, "------");

            //TODO: look up names in 2da/TLK?
            _prc_inc_PrintShapeInfo(oPC, "======  Fortitude: " + IntToString(GetFortitudeSavingThrow(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  Will: " + IntToString(GetWillSavingThrow(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "======  Reflex: " + IntToString(GetReflexSavingThrow(oItem)));
            _prc_inc_PrintShapeInfo(oPC, "------");

            int i = 0;
            string sSkillName;
            while((sSkillName = Get2DACache("skills", "Name", i)) != "")
            {
                sSkillName = GetStringByStrRef(StringToInt(sSkillName));
                _prc_inc_PrintShapeInfo(oPC, "======  " + sSkillName + ": " + IntToString(GetSkillRank(i, oItem)));
                i += 1;
            }
            _prc_inc_PrintShapeInfo(oPC, "------");

            _prc_inc_PrintShapeInfo(oPC, "======  Gender: " + IntToString(GetGender(oItem))); //TODO: look up values in 2da?
            _prc_inc_PrintShapeInfo(oPC, "======  Size: " + IntToString(GetCreatureSize(oItem))); //TODO: look up values in 2da?
            _prc_inc_PrintShapeInfo(oPC, "======  Race: " + IntToString(GetRacialType(oItem))); //TODO: look up values in 2da?
            _prc_inc_PrintShapeInfo(oPC, "======  Speed: " + IntToString(GetMovementRate(oItem))); //TODO: look up values in 2da?
            _prc_inc_PrintShapeInfo(oPC, "======  Dead: " + (GetIsDead(oItem) ? "Yes" : "No"));
            _prc_inc_PrintShapeInfo(oPC, "======  Tag: " + GetTag(oItem));
            _prc_inc_PrintShapeInfo(oPC, "======  Object Type: " + IntToString(GetObjectType(oItem))); //TODO: look up values in 2da?

            //TODO?:
                //int GetGold(object oTarget=OBJECT_SELF);
                //location GetLocalLocation(object oObject, string sVarName);
                    // vector GetPositionFromLocation(location lLocation);
                    // object GetAreaFromLocation(location lLocation);
                    // float GetFacingFromLocation(location lLocation);
                //int GetCommandable(object oTarget=OBJECT_SELF);
                //int GetIsListening(object oObject);
                //int GetReputation(object oSource, object oTarget);
                //location GetLocation(object oObject);
                //int GetIsPC(object oCreature);
                // int GetIsEnemy(object oTarget, object oSource=OBJECT_SELF);
                // int GetIsFriend(object oTarget, object oSource=OBJECT_SELF);
                // int GetIsNeutral(object oTarget, object oSource=OBJECT_SELF);
                // int GetStealthMode(object oCreature);
                // int GetDetectMode(object oCreature);
                // int GetDefensiveCastingMode(object oCreature);
                // int GetAppearanceType(object oCreature);
                // int GetWeight(object oTarget=OBJECT_SELF); //Gets the weight of an item, or the total carried weight of a creature in tenths of pounds (as per the baseitems.2da).
                // int GetAILevel(object oTarget=OBJECT_SELF);
                // int GetActionMode(object oCreature, int nMode); 
                // int GetArcaneSpellFailure(object oCreature); 
                // int GetLootable( object oCreature );
                // int GetIsCreatureDisarmable(object oCreature);
                // string GetDeity(object oCreature);
                // string GetSubRace(object oTarget);
                // int GetAge(object oCreature);
                //int GetPlotFlag(object oTarget=OBJECT_SELF);
        }
        else
            _prc_inc_PrintShapeInfo(oPC, "======  AC: " + IntToString(GetItemACValue(oItem)));
        _prc_inc_PrintShapeInfo(oPC, "====================================");
    }    
}

//TODO: TLK Entries
void _prc_inc_ShapePrintDebug(object oPC, object oTarget, int bForceLogPrint)
{
    int nSaveValue = GetLocalInt(oPC, "PRC_EnableLogPrint");
    if (bForceLogPrint)
        SetLocalInt(oPC, "PRC_EnableLogPrint", TRUE);
    
    DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "CREATURE", oTarget));
    DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "CREATURE SKIN", GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTarget)));
    DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "RIGHT CREATURE WEAPON", GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oTarget)));
    DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "LEFT CREATURE WEAPON", GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oTarget)));
    DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "SPECIAL CREATURE WEAPON", GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oTarget)));

    int nSlot;
    for(nSlot = 0; nSlot < NUM_INVENTORY_SLOTS; nSlot++)
    {
        switch (nSlot)
        {
            case INVENTORY_SLOT_CARMOUR:
            case INVENTORY_SLOT_CWEAPON_R:
            case INVENTORY_SLOT_CWEAPON_L:
            case INVENTORY_SLOT_CWEAPON_B:
                break;
            
            default:
                DelayCommand(0.0, _prc_inc_PrintDebugItem(oPC, "INVENTORY ITEM " + IntToString(nSlot) + ": ", GetItemInSlot(nSlot, oTarget)));
        }
    }

    if (bForceLogPrint)
        DelayCommand(0.1, SetLocalInt(oPC, "PRC_EnableLogPrint", nSaveValue));
}

//TODO: Add nShifterType parameter so that only applicable information is printed?
void _prc_inc_PrintShape(object oPC, object oTemplate, int bForceLogPrint)
{
    int nSaveValue = GetLocalInt(oPC, "PRC_EnableLogPrint");
    if (bForceLogPrint)
        SetLocalInt(oPC, "PRC_EnableLogPrint", TRUE);
    
    _prc_inc_PrintShapeInfo(oPC, "=================================================");
    
    //Basic information
    
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetName(oTemplate));
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetResRef(oTemplate));
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef((StringToInt(Get2DACache("racialtypes", "Name", MyPRCGetRacialType(oTemplate))))));
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57420+0x01000000) + IntToString(_prc_inc_shifting_ShifterLevelRequirement(oTemplate))); //"Required Shifter Level: "
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57421+0x01000000) + IntToString(_prc_inc_shifting_CharacterLevelRequirement(oTemplate))); //"Required Character Level: "
    _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57436+0x01000000) + FloatToString(GetChallengeRating(oTemplate), 4, 1)); //"Challenge Rating: "

    _prc_inc_PrintShapeInfo(oPC, "==========");

    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(1, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(2, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(3, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(4, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(5, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(6, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(7, oTemplate));
    _prc_inc_PrintClassInfo("=== ", oPC, oTemplate, GetClassByPosition(8, oTemplate));
	
    _prc_inc_PrintShapeInfo(oPC, "==========");

    //Harmlessly invisible?

    if(_prc_inc_shifting_GetIsCreatureHarmless(oTemplate))
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57424+0x01000000)); //"Harmlessly Invisible"
        
    //Able to cast spells without Natural Spell?

    if(!_prc_inc_shifting_GetCanFormCast(oTemplate) && !GetHasFeat(FEAT_PRESTIGE_SHIFTER_NATURALSPELL, oTemplate))
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57477+0x01000000)); //"Cannot cast spells"
    else
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57476+0x01000000)); //"Can cast spells"

    //Natural AC

    _prc_inc_PrintNaturalAC(oPC, oTemplate);

    //STR, DEX, CON
    
    struct _prc_inc_ability_info_struct rInfoStruct = _prc_inc_shifter_GetAbilityInfo(oTemplate, oPC);

    //Extra information related to STR, DEX, CON

    string sExtra, sBonusOrPenalty = GetStringByStrRef(57427+0x01000000);
    sExtra = " (" + sBonusOrPenalty + (rInfoStruct.nDeltaSTR>=0?"+":"") + IntToString(rInfoStruct.nDeltaSTR) + ")";
    _prc_inc_PrintShapeInfo(oPC, "=== " + _prc_inc_AbilityTypeString(0) + " " + IntToString(rInfoStruct.nTemplateSTR) + sExtra);
    sExtra = " (" + sBonusOrPenalty + (rInfoStruct.nDeltaDEX>=0?"+":"") + IntToString(rInfoStruct.nDeltaDEX) + ")";
    _prc_inc_PrintShapeInfo(oPC, "=== " + _prc_inc_AbilityTypeString(1) + " " + IntToString(rInfoStruct.nTemplateDEX) + sExtra);
    sExtra = " (" + sBonusOrPenalty + (rInfoStruct.nDeltaCON>=0?"+":"") + IntToString(rInfoStruct.nDeltaCON) + ")";
    _prc_inc_PrintShapeInfo(oPC, "=== " + _prc_inc_AbilityTypeString(2) + " " + IntToString(rInfoStruct.nTemplateCON) + sExtra);    

    _prc_inc_PrintShapeInfo(oPC, "------");

    int i = 0;
    string sSkillName;
    string sSTRBasedSkills, sDEXBasedSkills, sCONBasedSkills;
    while((sSkillName = Get2DACache("skills", "Name", i)) != "")
    {
        sSkillName = GetStringByStrRef(StringToInt(sSkillName));
        string sSkillKeyAbility = Get2DACache("skills", "KeyAbility", i);
        if (sSkillKeyAbility == "STR")
            sSTRBasedSkills += sSkillName + ", ";
        else if (sSkillKeyAbility == "DEX")
            sDEXBasedSkills += sSkillName + ", ";
        else if (sSkillKeyAbility == "CON")
            sCONBasedSkills += sSkillName + ", ";
        i += 1;
    }
    if (GetStringLength(sSTRBasedSkills))
        sSTRBasedSkills = GetStringLeft(sSTRBasedSkills, GetStringLength(sSTRBasedSkills) - 2); //Remove the final ", "
    if (GetStringLength(sDEXBasedSkills))
        sDEXBasedSkills = GetStringLeft(sDEXBasedSkills, GetStringLength(sDEXBasedSkills) - 2); //Remove the final ", "
    if (GetStringLength(sCONBasedSkills))
        sCONBasedSkills = GetStringLeft(sCONBasedSkills, GetStringLength(sCONBasedSkills) - 2); //Remove the final ", "

    int nSTRBonus = rInfoStruct.nExtraSTR / 2;
    if (nSTRBonus > 0)
    {
        //TODO: cap AB bonus
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60544+0x01000000) + " +" + IntToString(nSTRBonus)); //Attack increase from STR increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60546+0x01000000) + " +" + IntToString(nSTRBonus)); //Damage increase from STR increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60528+0x01000000), "%(SKILLS)", sSTRBasedSkills) + " +" + IntToString(nSTRBonus)); //Skill bonus from STR increase
    }
    else if (nSTRBonus < 0)
    {
        //TODO: cap AB penalty--at what?
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60545+0x01000000) + " " + IntToString(nSTRBonus)); //Attack decrease from STR decrease
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60547+0x01000000) + " " + IntToString(nSTRBonus)); //Damage decrease from STR decrease
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60529+0x01000000), "%(SKILLS)", sSTRBasedSkills) + " " + IntToString(nSTRBonus)); //Skill penalty from STR decrease
    }

    int nDEXBonus = rInfoStruct.nExtraDEX / 2;
    if (nDEXBonus > 0)
    {
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60548+0x01000000) + " +" + IntToString(nDEXBonus)); //AC increase from DEX increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60530+0x01000000), "%(SKILLS)", sDEXBasedSkills) + " +" + IntToString(nDEXBonus)); //Skill bonus from DEX increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60531+0x01000000) + " +" + IntToString(nDEXBonus)); //Saving throw bonus from DEX increase
    }
    else if (nDEXBonus < 0)
    {
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60549+0x01000000) + " " + IntToString(nDEXBonus)); //AC decrease from DEX increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60532+0x01000000), "%(SKILLS)", sDEXBasedSkills) + " " + IntToString(nDEXBonus)); //Skill penalty from DEX decrease
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60533+0x01000000) + " " + IntToString(nDEXBonus)); //Saving throw penalty from DEX decrease
    }

    int nCONBonus = rInfoStruct.nExtraCON / 2;
    if (nCONBonus > 0)
    {
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60534+0x01000000), "%(SKILLS)", sCONBasedSkills) + " +" + IntToString(nCONBonus)); //Skill bonus from CON increase
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60535+0x01000000) + " +" + IntToString(nCONBonus)); //Saving throw bonus from CON increase
        int tempHP = rInfoStruct.nExtraCON * GetHitDice(oPC);
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(57431+0x01000000) + " " + IntToString(tempHP)); //Temporary HP from CON increase
    }
    else if (nCONBonus < 0)
    {
        _prc_inc_PrintShapeInfo(oPC, "=== " + ReplaceString(GetStringByStrRef(60536+0x01000000), "%(SKILLS)", sCONBasedSkills) + " " + IntToString(nCONBonus)); //Skill penalty from CON decrease
        _prc_inc_PrintShapeInfo(oPC, "=== " + GetStringByStrRef(60537+0x01000000) + " " + IntToString(nCONBonus)); //Saving throw penalty from CON decrease
    }

    _prc_inc_PrintShapeInfo(oPC, "==========");
        
    //Hide and creature weapon properties

    object oTemplateCWpR = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oTemplate);
    object oTemplateCWpL = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oTemplate);
    object oTemplateCWpB = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oTemplate);
    object oTemplateHide = GetItemInSlot(INVENTORY_SLOT_CARMOUR, oTemplate);

    if(GetIsObjectValid(oTemplateCWpR))
    {
        string sPrefix = GetStringByStrRef(57432+0x01000000); //"Right Creature Weapon:"
        _prc_inc_PrintShapeInfo(oPC, "=== " + sPrefix);
        _prc_inc_PrintAllItemProperties("===    ", oPC, oTemplateCWpR);
    }

    if(GetIsObjectValid(oTemplateCWpL))
    {
        string sPrefix = GetStringByStrRef(57433+0x01000000); //"Left Creature Weapon:"
        _prc_inc_PrintShapeInfo(oPC, "=== " + sPrefix);
        _prc_inc_PrintAllItemProperties("===    ", oPC, oTemplateCWpL);
    }

    if(GetIsObjectValid(oTemplateCWpB))
    {
        string sPrefix = GetStringByStrRef(57434+0x01000000); //"Special Attack Creature Weapon:"
        _prc_inc_PrintShapeInfo(oPC, "=== " + sPrefix);
        _prc_inc_PrintAllItemProperties("===    ", oPC, oTemplateCWpB);
    }

    if(GetIsObjectValid(oTemplateHide))
        _prc_inc_PrintAllItemProperties("=== ", oPC, oTemplateHide);
        
    //Spell-like abilities
        
    _prc_inc_shifting_PrintShifterActiveAbilities(oPC, oTemplate);
    
    //Feats

    i = 0;
    string sFeat;
    int CHUNK_SIZE = 25; //50 was too big, so use 25

    while((sFeat = Get2DACache("shifter_feats", "Feat", i)) != "")
    {
        DelayCommand(0.0f, _prc_inc_shifting_PrintFeats(oPC, oTemplate, i, i+CHUNK_SIZE));
        i += CHUNK_SIZE;
    }
    DelayCommand(0.0f, _prc_inc_PrintShapeInfo(oPC, "================================================="));

    if (bForceLogPrint)
        DelayCommand(0.1, SetLocalInt(oPC, "PRC_EnableLogPrint", nSaveValue));

    if (GetLocalInt(oPC, "prc_shift_debug"))
        DelayCommand(0.2f, _prc_inc_ShapePrintDebug(oPC, oTemplate, bForceLogPrint));
}
