Files
PRC8/nwn/nwnprc/trunk/include/prc_craft_cv_inc.nss
Jaysyn904 829d9d7999 2026/01/17 Update
Removed a lot of old backup files.
Fixed Eye of Gruumsh's epic bonus feats.
Add Epic marker feat for Eye of Gruumsh.
Added Channeled Pyroburst to prc_desc_fire.2da
Added GetCurrentUnixTimestamp() function.
Moved crafting conversation functions to prc_craft_cv_inc.nss.
Made Midnight Augment work slightly better, still not quite per PnP yet.
Disciple of Asmodeus' Summoned Devils are supposed to be Lawful Evil.
Every instance of ItemPropertySpellImmunitySpecific() in race_skin.nss was misconfigured.
Several instances of ItemPropertyDamageImmunity() in race_skin.nss were misconfigured.
Fixed issue where Blighters were still considered undead after leaving undead wildshape.
PRC8 now supports offline PnP magical crafting.
Disciple of Asmodeus' Dread Night now increases AC instead of Damage, per PnP.
Non-spellcaster Disciples of Asmodeus have a Hellcat duration based on DoA class level.
Hexblade's Dark Companion shouldn't lose Sacntuary when loading from a save.
Claws of the Savage should increase size properly if caster already has claws at time of casting.
2026-01-17 22:50:31 -05:00

1057 lines
37 KiB
Plaintext

//:: prc_craft_cv_inc.nss
#include "prc_craft_inc"
#include "inc_dynconv"
//////////////////////////////////////////////////
/* Constant defintions */
//////////////////////////////////////////////////
//const int STAGE_ = ;
const int STAGE_START = 0;
const int STAGE_SELECT_SUBTYPE = 1;
const int STAGE_SELECT_COSTTABLEVALUE = 2;
const int STAGE_SELECT_PARAM1VALUE = 3;
const int STAGE_CONFIRM = 4;
const int STAGE_BANE = 5;
const int STAGE_CONFIRM_MAGIC = 7;
const int STAGE_APPEARANCE = 8;
const int STAGE_CLONE = 9;
const int STAGE_APPEARANCE_LIST = 10;
const int STAGE_APPEARANCE_VALUE = 11;
const int STAGE_CRAFT_GOLEM = 12;
const int STAGE_CRAFT_GOLEM_HD = 13;
const int STAGE_CRAFT_ALCHEMY = 14;
const int STAGE_CONFIRM_ALCHEMY = 15;
const int STAGE_CRAFT_POISON = 16;
const int STAGE_CONFIRM_POISON = 17;
const int STAGE_CRAFT_LICH = 18;
const int STAGE_CRAFT = 101;
const int STAGE_CRAFT_SELECT = 102;
const int STAGE_CRAFT_MASTERWORK = 103;
const int STAGE_CRAFT_AC = 104;
const int STAGE_CRAFT_MIGHTY = 105;
const int STAGE_CRAFT_CONFIRM = 106;
//const int STAGE_CRAFT = 101;
//const int CHOICE_ = ;
//these must be past the highest 2da entry to be read
const int CHOICE_FORGE = 20001;
const int CHOICE_BOOST = 20002;
const int CHOICE_BACK = 20003;
const int CHOICE_CLEAR = 20004;
const int CHOICE_CONFIRM = 20005;
const int CHOICE_SETNAME = 20006;
const int CHOICE_SETAPPEARANCE = 20007;
const int CHOICE_CLONE = 20008;
const int CHOICE_APPEARANCE_SHOUT = 20009;
const int CHOICE_APPEARANCE_SELECT = 20010;
const int CHOICE_PLUS_1 = 20011;
const int CHOICE_PLUS_10 = 20012;
const int CHOICE_MINUS_1 = 20013;
const int CHOICE_MINUS_10 = 20014;
const int CHOICE_CRAFT = 20101;
//const int NUM_MAX_COSTTABLEVALUES = 70;
//const int NUM_MAX_PARAM1VALUES = 70;
const int HAS_SUBTYPE = 1;
const int HAS_COSTTABLE = 2;
const int HAS_PARAM1 = 4;
const int STRREF_YES = 4752; // "Yes"
const int STRREF_NO = 4753; // "No"
const string PRC_CRAFT_ITEM = "PRC_CRAFT_ITEM";
const string PRC_CRAFT_TYPE = "PRC_CRAFT_TYPE";
const string PRC_CRAFT_SUBTYPE = "PRC_CRAFT_SUBTYPE";
const string PRC_CRAFT_SUBTYPEVALUE = "PRC_CRAFT_SUBTYPEVALUE";
const string PRC_CRAFT_COSTTABLE = "PRC_CRAFT_COSTTABLE";
const string PRC_CRAFT_COSTTABLEVALUE = "PRC_CRAFT_COSTTABLEVALUE";
const string PRC_CRAFT_PARAM1 = "PRC_CRAFT_PARAM1";
const string PRC_CRAFT_PARAM1VALUE = "PRC_CRAFT_PARAM1VALUE";
const string PRC_CRAFT_PROPLIST = "PRC_CRAFT_PROPLIST";
const string PRC_CRAFT_COST = "PRC_CRAFT_COST";
const string PRC_CRAFT_XP = "PRC_CRAFT_XP";
const string PRC_CRAFT_TIME = "PRC_CRAFT_TIME";
//const string PRC_CRAFT_BLUEPRINT = "PRC_CRAFT_BLUEPRINT";
const string PRC_CRAFT_CONVO_ = "PRC_CRAFT_CONVO_";
const string PRC_CRAFT_BASEITEMTYPE = "PRC_CRAFT_BASEITEMTYPE";
const string PRC_CRAFT_AC = "PRC_CRAFT_AC";
const string PRC_CRAFT_MIGHTY = "PRC_CRAFT_MIGHTY";
const string PRC_CRAFT_MATERIAL = "PRC_CRAFT_MATERIAL";
const string PRC_CRAFT_TAG = "PRC_CRAFT_TAG";
const string PRC_CRAFT_LINE = "PRC_CRAFT_LINE";
const string PRC_CRAFT_FILE = "PRC_CRAFT_FILE";
const string PRC_CRAFT_MAGIC_ENHANCE = "PRC_CRAFT_MAGIC_ENHANCE";
const string PRC_CRAFT_MAGIC_ADDITIONAL = "PRC_CRAFT_MAGIC_ADDITIONAL";
const string PRC_CRAFT_MAGIC_EPIC = "PRC_CRAFT_MAGIC_EPIC";
const string PRC_CRAFT_SCRIPT_STATE = "PRC_CRAFT_SCRIPT_STATE";
const string ARTIFICER_PREREQ_RACE = "ARTIFICER_PREREQ_RACE";
const string ARTIFICER_PREREQ_ALIGN = "ARTIFICER_PREREQ_ALIGN";
const string ARTIFICER_PREREQ_CLASS = "ARTIFICER_PREREQ_CLASS";
const string ARTIFICER_PREREQ_SPELL1 = "ARTIFICER_PREREQ_SPELL1";
const string ARTIFICER_PREREQ_SPELL2 = "ARTIFICER_PREREQ_SPELL2";
const string ARTIFICER_PREREQ_SPELL3 = "ARTIFICER_PREREQ_SPELL3";
const string ARTIFICER_PREREQ_SPELLOR1 = "ARTIFICER_PREREQ_SPELLOR1";
const string ARTIFICER_PREREQ_SPELLOR2 = "ARTIFICER_PREREQ_SPELLOR2";
const string ARTIFICER_PREREQ_COMPLETE = "ARTIFICER_PREREQ_COMPLETE";
const int PRC_CRAFT_STATE_NORMAL = 1;
const int PRC_CRAFT_STATE_MAGIC = 2;
const string PRC_CRAFT_HB = "PRC_CRAFT_HB";
const int SORT = TRUE; // If the sorting takes too much CPU, set to FALSE
const int DEBUG_LIST = FALSE;
//////////////////////////////////////////////////
/* Function defintions */
//////////////////////////////////////////////////
void PrintList(object oPC)
{
string tp = "Printing list:\n";
string s = GetLocalString(oPC, "ForgeConvo_List_Head");
if(s == ""){
tp += "Empty\n";
}
else{
tp += s + "\n";
s = GetLocalString(oPC, "ForgeConvo_List_Next_" + s);
while(s != ""){
tp += "=> " + s + "\n";
s = GetLocalString(oPC, "ForgeConvo_List_Next_" + s);
}
}
DoDebug(tp);
}
/**
* Creates a linked list of entries that is sorted into alphabetical order
* as it is built.
* Assumption: Power names are unique.
*
* @param oPC The storage object aka whomever is gaining powers in this conversation
* @param sChoice The choice string
* @param nChoice The choice value
*/
void AddToTempList(object oPC, string sChoice, int nChoice)
{
if(DEBUG_LIST) DoDebug("\nAdding to temp list: '" + sChoice + "' - " + IntToString(nChoice));
if(DEBUG_LIST) PrintList(oPC);
// If there is nothing yet
if(!GetLocalInt(oPC, "PRC_CRAFT_CONVO_ListInited"))
{
SetLocalString(oPC, "PRC_CRAFT_CONVO_List_Head", sChoice);
SetLocalInt(oPC, "PRC_CRAFT_CONVO_List_" + sChoice, nChoice);
SetLocalInt(oPC, "PRC_CRAFT_CONVO_ListInited", TRUE);
}
else
{
// Find the location to instert into
string sPrev = "", sNext = GetLocalString(oPC, "PRC_CRAFT_CONVO_List_Head");
while(sNext != "" && StringCompare(sChoice, sNext) >= 0)
{
if(DEBUG_LIST) DoDebug("Comparison between '" + sChoice + "' and '" + sNext + "' = " + IntToString(StringCompare(sChoice, sNext)));
sPrev = sNext;
sNext = GetLocalString(oPC, "PRC_CRAFT_CONVO_List_Next_" + sNext);
}
// Insert the new entry
// Does it replace the head?
if(sPrev == "")
{
if(DEBUG_LIST) DoDebug("New head");
SetLocalString(oPC, "PRC_CRAFT_CONVO_List_Head", sChoice);
}
else
{
if(DEBUG_LIST) DoDebug("Inserting into position between '" + sPrev + "' and '" + sNext + "'");
SetLocalString(oPC, "PRC_CRAFT_CONVO_List_Next_" + sPrev, sChoice);
}
SetLocalString(oPC, "PRC_CRAFT_CONVO_List_Next_" + sChoice, sNext);
SetLocalInt(oPC, "PRC_CRAFT_CONVO_List_" + sChoice, nChoice);
}
}
/**
* Reads the linked list built with AddToTempList() to AddChoice() and
* deletes it.
*
* @param oPC A PC gaining powers at the moment
*/
void TransferTempList(object oPC)
{
string sChoice = GetLocalString(oPC, "PRC_CRAFT_CONVO_List_Head");
int nChoice = GetLocalInt (oPC, "PRC_CRAFT_CONVO_List_" + sChoice);
DeleteLocalString(oPC, "PRC_CRAFT_CONVO_List_Head");
string sPrev;
if(DEBUG_LIST) DoDebug("Head is: '" + sChoice + "' - " + IntToString(nChoice));
while(sChoice != "")
{
// Add the choice
AddChoice(sChoice, nChoice, oPC);
// Get next
sChoice = GetLocalString(oPC, "PRC_CRAFT_CONVO_List_Next_" + (sPrev = sChoice));
nChoice = GetLocalInt (oPC, "PRC_CRAFT_CONVO_List_" + sChoice);
if(DEBUG_LIST) DoDebug("Next is: '" + sChoice + "' - " + IntToString(nChoice) + "; previous = '" + sPrev + "'");
// Delete the already handled data
DeleteLocalString(oPC, "PRC_CRAFT_CONVO_List_Next_" + sPrev);
DeleteLocalInt (oPC, "PRC_CRAFT_CONVO_List_" + sPrev);
}
DeleteLocalInt(oPC, "PRC_CRAFT_CONVO_ListInited");
}
//Returns the next conversation stage according
// to item property
int GetNextItemPropStage(int nStage, object oPC, int nPropList)
{
nStage++;
if(nStage == STAGE_SELECT_SUBTYPE && !(nPropList & HAS_SUBTYPE))
nStage++;
if(nStage == STAGE_SELECT_COSTTABLEVALUE && !(nPropList & HAS_COSTTABLE))
nStage++;
if(nStage == STAGE_SELECT_PARAM1VALUE && !(nPropList & HAS_PARAM1))
nStage++;
MarkStageNotSetUp(nStage, oPC);
return nStage;
}
//Returns the previous conversation stage according
// to item property
int GetPrevItemPropStage(int nStage, object oPC, int nPropList)
{
nStage--;
if(nStage == STAGE_SELECT_PARAM1VALUE && !(nPropList & HAS_PARAM1))
nStage--;
if(nStage == STAGE_SELECT_COSTTABLEVALUE && !(nPropList & HAS_COSTTABLE))
nStage--;
if(nStage == STAGE_SELECT_SUBTYPE && !(nPropList & HAS_SUBTYPE))
nStage--;
MarkStageNotSetUp(nStage, oPC);
return nStage;
}
//hardcoded to save time/prevent tmi
int SkipLineSpells(int i)
{
switch(i)
{
//i +2
case 3:
case 6:
case 16:
case 22:
case 26:
case 29:
case 38:
case 41:
case 44:
case 58:
case 61:
case 64:
case 70:
case 73:
case 79:
case 82:
case 96:
case 111:
case 116:
case 134:
case 145:
case 165:
case 185:
case 193:
case 202:
case 289:
case 292:
case 295:
case 312:
case 516:
case 1017:
case 1038:
case 1041:
case 1068:
case 1082:
case 1090:
case 1099:
case 1104:
case 1107:
case 1134:
case 1363:
case 1430:
case 1435: i = i + 2; break;
//i +1
case 10:
case 12:
case 19:
case 21:
case 24:
case 32:
case 34:
case 36:
case 47:
case 51:
case 53:
case 56:
case 67:
case 85:
case 91:
case 93:
case 102:
case 107:
case 109:
case 114:
case 124:
case 132:
case 138:
case 141:
case 156:
case 163:
case 181:
case 191:
case 196:
case 199:
case 214:
case 218:
case 220:
case 222:
case 224:
case 235:
case 237:
case 248:
case 252:
case 256:
case 258:
case 263:
case 276:
case 285:
case 306:
case 309:
case 315:
case 325:
case 397:
case 462:
case 475:
case 485:
case 514:
case 949:
case 953:
case 1001:
case 1003:
case 1005:
case 1007:
case 1009:
case 1011:
case 1013:
case 1020:
case 1031:
case 1034:
case 1043:
case 1045:
case 1048:
case 1050:
case 1052:
case 1055:
case 1057:
case 1059:
case 1061:
case 1063:
case 1076:
case 1078:
case 1086:
case 1088:
case 1095:
case 1097:
case 1102:
case 1111:
case 1113:
case 1119:
case 1121:
case 1124:
case 1126:
case 1128:
case 1145:
case 1147:
case 1196:
case 1205:
case 1215:
case 1260:
case 1350: i = i + 1; break;
case 173: i = 179; break;
case 317: i = 321; break;
case 328: i = 345; break;
case 359: i = 360; break;
case 400: i = 450; break;
case 487: i = 514; break;
case 520: i = 538; break;
case 540: i = 899; break;
case 902: i = 903; break;
case 914: i = 928; break;
case 967: i = 1000; break;
case 1366: i = 1369; break;
case 1389: i = 1416; break;
}
return i;
}
//added by MSB - hardcoded to prevent TMI
int SkipLineFeats(int i)
{
switch (i)
{
case 40: i = 99; break;
case 141: i = 201; break;
case 213: i = 257; break;
case 259: i = 260; break;
case 262: i = 264; break;
case 266: i = 343; break;
case 381: i = 394; break;
case 395: i = 24813; break;
}
return i;
}
//hardcoded to save time/prevent tmi
int SkipLineItemprops(int i)
{
switch(i)
{
case 94: i = 100; break;
case 102: i = 133; break;
case 135: i = 150; break;
case 151: i = 200; break;
}
return i;
}
//Adds names to a list based on sTable (2da), delayed recursion
// to avoid TMI
void PopulateList(object oPC, int MaxValue, int bSort, string sTable, object oItem = OBJECT_INVALID, int i = 0)
{
if(GetLocalInt(oPC, "DynConv_Waiting") == FALSE)
return;
if(i <= MaxValue)
{
int bValid = TRUE;
string sTemp = "";
if(sTable == "iprp_spells")
{
i = SkipLineSpells(i);
MaxValue = 1150; //MSB changed this from 540
}
else if(sTable == "IPRP_FEATS")
{
MaxValue = 24819;
i = SkipLineFeats(i);
}
else if(sTable == "itempropdef")
{
i = SkipLineItemprops(i);
bValid = ValidProperty(oItem, i);
if(bValid)
bValid = !GetPRCSwitch("PRC_CRAFT_DISABLE_itempropdef_" + IntToString(i));
}
else if(GetStringLeft(sTable, 6) == "craft_")
bValid = array_get_int(oPC, PRC_CRAFT_ITEMPROP_ARRAY, i);
sTemp = Get2DACache(sTable, "Name", i);
if((sTemp != "") && bValid)//this is going to kill
{
if(sTable == "iprp_spells")
{
AddToTempList(oPC, ActionString(GetStringByStrRef(StringToInt(sTemp))), i);
}
else
{
if(bSort)
AddToTempList(oPC, ActionString(GetStringByStrRef(StringToInt(sTemp))), i);
else
AddChoice(ActionString(GetStringByStrRef(StringToInt(sTemp))), i, oPC);
}
}
if(!(i % 100) && i) //i != 0, i % 100 == 0
//following line is for debugging
//FloatingTextStringOnCreature(sTable, oPC, FALSE);
FloatingTextStringOnCreature("*Tick*", oPC, FALSE);
}
else
{
if(bSort) TransferTempList(oPC);
DeleteLocalInt(oPC, "DynConv_Waiting");
FloatingTextStringOnCreature("*Done*", oPC, FALSE);
return;
}
DelayCommand(0.01, PopulateList(oPC, MaxValue, bSort, sTable, oItem, i + 1));
}
//use heartbeat
void ApplyProperties(object oPC, object oItem, itemproperty ip, int nCost, int nXP, string sFile, int nLine)
{
if(DEBUG) DoDebug("ApplyProperties: Starting with nCost=" + IntToString(nCost) +
", and Crafter GP =" + IntToString(GetGold(oPC)));
if(GetGold(oPC) < nCost)
{
FloatingTextStringOnCreature("Crafting: Insufficient gold!", oPC);
return;
}
int nHD = GetHitDice(oPC);
int nMinXP = nHD * (nHD - 1) * 500;
int nCurrentXP = GetXP(oPC);
if((nCurrentXP - nMinXP) < nXP)
{
FloatingTextStringOnCreature("Crafting: Insufficient XP!", oPC);
return;
}
if(GetItemPossessor(oItem) != oPC)
{
FloatingTextStringOnCreature("Crafting: You do not have the item!", oPC);
return;
}
if(DEBUG) DoDebug("ApplyProperties: Passed validation, about to apply properties");
if(nLine == -1)
IPSafeAddItemProperty(oItem, ip, 0.0, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING);
else if(nLine == -2)
{ //clone item
CopyItem(oItem, oPC, TRUE);
}
else
{
string sPropertyType = Get2DACache(sFile, "PropertyType", nLine);
if(sPropertyType == "M")
{ //checking required spells
if(!CheckCraftingSpells(oPC, sFile, nLine, TRUE))
{
FloatingTextStringOnCreature("Crafting: Required spells not available!", oPC);
return;
}
}
else if(sPropertyType == "P")
{
if(!CheckCraftingPowerPoints(oPC, sFile, nLine, TRUE))
{
FloatingTextStringOnCreature("Crafting: Insufficient power points!", oPC);
return;
}
}
if(DEBUG) DoDebug("ApplyProperties: Calling ApplyItemProps()");
ApplyItemProps(oItem, sFile, nLine);
}
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_BREACH), oPC);
// With this conditional logic:
if(GetLocalInt(oPC, "PRC_CRAFT_RESTORED"))
{
AssignCommand(oPC, ClearAllActions());
if(DEBUG) DoDebug("ApplyProperties: About to call TakeGoldFromCreature() with cost=" + IntToString(nCost));
AssignCommand(oPC, TakeGoldFromCreature(nCost, oPC, TRUE));
SpendXP(oPC, nXP);
DeleteLocalInt(oPC, "PRC_CRAFT_RESTORED");
}
else
{
AssignCommand(oPC, ClearAllActions());
if(DEBUG) DoDebug("ApplyProperties: About to call TakeGoldFromCreature() with cost=" + IntToString(nCost));
AssignCommand(oPC, TakeGoldFromCreature(nCost, oPC, TRUE));
if(DEBUG) DoDebug("ApplyProperties: TakeGoldFromCreature() completed, new gold =" + IntToString(GetGold(oPC)));
SpendXP(oPC, nXP);
if(DEBUG) DoDebug("ApplyProperties: XP deduction completed, new XP=" + IntToString(GetXP(oPC)));
}
//if(DEBUG) DoDebug("ApplyProperties: About to deduct gold ("+IntToString(nCost)+") and XP ("+IntToString(nCost)+").");
//TakeGoldFromCreature(nCost, oPC, TRUE);
//SetXP(oPC, GetXP(oPC) - nXP);
if(DEBUG) DoDebug("ApplyProperties: Completed successfully");
}
//use heartbeat
void CreateGolem(object oPC, int nCost, int nXP, string sFile, int nLine)
{
if(GetGold(oPC) < nCost)
{
FloatingTextStringOnCreature("Crafting: Insufficient gold!", oPC);
return;
}
int nHD = GetHitDice(oPC);
int nMinXP = nHD * (nHD - 1) * 500;
int nCurrentXP = GetXP(oPC);
if((nCurrentXP - nMinXP) < nXP)
{
FloatingTextStringOnCreature("Crafting: Insufficient XP!", oPC);
return;
}
string sPropertyType = Get2DACache(sFile, "CasterType", nLine);
if(sPropertyType == "M")
{ //checking required spells
if(!CheckCraftingSpells(oPC, sFile, nLine, TRUE))
{
FloatingTextStringOnCreature("Crafting: Required spells not available!", oPC);
return;
}
}
else if(sPropertyType == "P")
{
if(!CheckCraftingPowerPoints(oPC, sFile, nLine, TRUE))
{
FloatingTextStringOnCreature("Crafting: Insufficient power points!", oPC);
return;
}
}
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_BREACH), oPC);
TakeGoldFromCreature(nCost, oPC, TRUE);
SetXP(oPC, GetXP(oPC) - nXP);
}
int ArtificerPrereqCheck(object oPC, string sFile, int nLine, int nCost)
{
string sTemp, sSub, sSpell;
int nRace, nAlignGE, nAlignLC, nClass, i, j, bBreak, nLength, nPosition, nTemp;
int nSpell1, nSpell2, nSpell3, nSpellOR1, nSpellOR2;
int nDays = nCost / 1000; //one set of UMD checks per "day" spent crafting
if(nCost % 1000) nDays++;
sTemp = Get2DACache(sFile, "PrereqMisc", nLine);
sSpell = Get2DACache(sFile, "Spells", nLine);
if(sTemp == "")
{
bBreak = TRUE;
nRace = -1;
nAlignGE = -1;
nAlignLC = -1;
nClass = -1;
}
nLength = GetStringLength(sTemp);
for(i = 0; i < 5; i++)
{
if(bBreak)
break;
nPosition = FindSubString(sTemp, "_");
sSub = (nPosition == -1) ? sTemp : GetStringLeft(sTemp, nPosition);
nLength -= (nPosition + 1);
if(sSub == "*")
nTemp = -1;
else
nTemp = StringToInt(sSub);
switch(i)
{
case 0:
{
nRace = (MyPRCGetRacialType(oPC) == nTemp) ? -1 : nTemp;
break;
}
case 1:
{
//can't emulate feat requirement
break;
}
case 2:
{
nAlignGE = -1;
if(sSub == "G") nAlignGE = (GetAlignmentGoodEvil(oPC) == ALIGNMENT_GOOD) ? -1 : ALIGNMENT_GOOD;
else if(sSub == "E") nAlignGE = (GetAlignmentGoodEvil(oPC) == ALIGNMENT_EVIL) ? -1 : ALIGNMENT_EVIL;
else if(sSub == "N") nAlignGE = (GetAlignmentGoodEvil(oPC) == ALIGNMENT_NEUTRAL) ? -1 : ALIGNMENT_NEUTRAL;
break;
}
case 3:
{
nAlignLC = -1;
if(sSub == "L") nAlignLC = (GetAlignmentLawChaos(oPC) == ALIGNMENT_LAWFUL) ? -1 : ALIGNMENT_LAWFUL;
if(sSub == "C") nAlignLC = (GetAlignmentLawChaos(oPC) == ALIGNMENT_CHAOTIC) ? -1 : ALIGNMENT_CHAOTIC;
if(sSub == "N") nAlignLC = (GetAlignmentLawChaos(oPC) == ALIGNMENT_NEUTRAL) ? -1 : ALIGNMENT_NEUTRAL;
break;
}
case 4:
{
nClass = (GetLevelByClass(nTemp, oPC)) ? -1 : nTemp;
break;
}
}
sTemp = GetSubString(sTemp, nPosition + 1, nLength);
}
if(sSpell == "")
{
nSpell1 = -1;
nSpell2 = -1;
nSpell3 = -1;
nSpellOR1 = -1;
nSpellOR2 = -1;
}
else
{
for(i = 0; i < 5; i++)
{
nPosition = FindSubString(sTemp, "_");
sSub = (nPosition == -1) ? sTemp : GetStringLeft(sTemp, nPosition);
nLength -= (nPosition + 1);
if(sSub == "*")
nTemp = -1;
else
{
nTemp = StringToInt(sSub);
switch(i)
{
case 0:
{ //storing the spell level and assuming it's a valid number
nSpell1 = (PRCGetHasSpell(nTemp, oPC)) ? -1 : StringToInt(Get2DACache("spells", "Innate", nTemp)) + 20;
break;
}
case 1:
{
nSpell2 = (PRCGetHasSpell(nTemp, oPC)) ? -1 : StringToInt(Get2DACache("spells", "Innate", nTemp)) + 20;
break;
}
case 2:
{
nSpell3 = (PRCGetHasSpell(nTemp, oPC)) ? -1 : StringToInt(Get2DACache("spells", "Innate", nTemp)) + 20;
break;
}
case 3:
{
nSpellOR1 = (PRCGetHasSpell(nTemp, oPC)) ? -1 : StringToInt(Get2DACache("spells", "Innate", nTemp)) + 20;
break;
}
case 4:
{
nSpellOR2 = (PRCGetHasSpell(nTemp, oPC)) ? -1 : StringToInt(Get2DACache("spells", "Innate", nTemp)) + 20;
break;
}
}
}
sTemp = GetSubString(sTemp, nPosition + 1, nLength);
}
}
int bTake10 = GetHasFeat(FEAT_SKILL_MASTERY_ARTIFICER, oPC) ? 10 : -1;
for(i = 0; i <= nDays; i++) //with extra last-ditch roll
{
if((nRace == -1) &&
(nAlignGE == -1) &&
(nAlignLC == -1) &&
(nClass == -1) &&
(nSpell1 == -1) &&
(nSpell2 == -1) &&
(nSpell3 == -1) &&
(nSpellOR1 == -1) &&
(nSpellOR2 == -1)
)
return TRUE;
if(nRace != -1) nRace = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, 25, bTake10)) ? -1 : nRace;
if(nAlignGE != -1) nAlignGE = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, 30, bTake10)) ? -1 : nAlignGE;
if(nAlignLC != -1) nAlignLC = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, 30, bTake10)) ? -1 : nAlignLC;
if(nClass != -1) nClass = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, 21, bTake10)) ? -1 : nClass;
if(nSpell1 != -1) nSpell1 = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, nSpell1, bTake10)) ? -1 : nSpell1;
if(nSpell2 != -1) nSpell2 = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, nSpell2, bTake10)) ? -1 : nSpell2;
if(nSpell3 != -1) nSpell3 = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, nSpell3, bTake10)) ? -1 : nSpell3;
if(nSpellOR1 != -1) nSpellOR1 = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, nSpellOR1, bTake10)) ? -1 : nSpellOR1;
if(nSpellOR2 != -1) nSpellOR2 = (GetPRCIsSkillSuccessful(oPC, SKILL_USE_MAGIC_DEVICE, nSpellOR2, bTake10)) ? -1 : nSpellOR2;
}
if((nRace == -1) &&
(nAlignGE == -1) &&
(nAlignLC == -1) &&
(nClass == -1) &&
(nSpell1 == -1) &&
(nSpell2 == -1) &&
(nSpell3 == -1) &&
(nSpellOR1 == -1) &&
(nSpellOR2 == -1)
)
return TRUE;
else
return FALSE; //made all the UMD rolls allocated and still failed
}
// Save crafting state during crafting
void SaveCraftingState(object oPC)
{
if(DEBUG) DoDebug("DEBUG: SaveCraftingState called for " + GetName(oPC));
if(GetLocalInt(oPC, PRC_CRAFT_HB))
{
if(DEBUG) DoDebug("DEBUG: Player is crafting, saving state...");
// Get the crafting item
object oItem = GetLocalObject(oPC, PRC_CRAFT_ITEM);
// Save item identification info
SQLocalsPlayer_SetString(oPC, "crafting_item_name", GetName(oItem));
SQLocalsPlayer_SetString(oPC, "crafting_item_tag", GetTag(oItem));
SQLocalsPlayer_SetInt(oPC, "crafting_item_basetype", GetBaseItemType(oItem));
// Save UUID if not already saved
string sItemUUID = SQLocalsPlayer_GetString(oPC, "crafting_item_uuid");
if(sItemUUID == "")
{
sItemUUID = GetObjectUUID(oItem);
SQLocalsPlayer_SetString(oPC, "crafting_item_uuid", sItemUUID);
if(DEBUG) DoDebug("DEBUG: Generated and saved new UUID: " + sItemUUID);
}
// Get crafting cost values
int nCostDiff = GetLocalInt(oPC, PRC_CRAFT_COST);
int nXPDiff = GetLocalInt(oPC, PRC_CRAFT_XP);
int nRounds = GetLocalInt(oPC, PRC_CRAFT_TIME);
// Save all basic crafting parameters
SQLocalsPlayer_SetInt(oPC, "crafting_cost", nCostDiff);
SQLocalsPlayer_SetInt(oPC, "crafting_xp", nXPDiff);
SQLocalsPlayer_SetInt(oPC, "crafting_rounds", nRounds);
SQLocalsPlayer_SetString(oPC, "crafting_file", GetLocalString(oPC, PRC_CRAFT_FILE));
SQLocalsPlayer_SetInt(oPC, "crafting_line", GetLocalInt(oPC, PRC_CRAFT_LINE));
SQLocalsPlayer_SetInt(oPC, "crafting_active", 1);
// Save item property components for reconstruction
SQLocalsPlayer_SetInt(oPC, "crafting_ip_type", GetLocalInt(oPC, "PRC_CRAFT_IP_TYPE"));
SQLocalsPlayer_SetInt(oPC, "crafting_ip_subtype", GetLocalInt(oPC, "PRC_CRAFT_IP_SUBTYPE"));
SQLocalsPlayer_SetInt(oPC, "crafting_ip_costtable", GetLocalInt(oPC, "PRC_CRAFT_IP_COSTTABLE"));
SQLocalsPlayer_SetInt(oPC, "crafting_ip_param1", GetLocalInt(oPC, "PRC_CRAFT_IP_PARAM1"));
// Save item property parameters
SQLocalsPlayer_SetInt(oPC, "crafting_type", GetLocalInt(oPC, PRC_CRAFT_TYPE));
SQLocalsPlayer_SetString(oPC, "crafting_subtype", GetLocalString(oPC, PRC_CRAFT_SUBTYPE));
SQLocalsPlayer_SetInt(oPC, "crafting_subtypevalue", GetLocalInt(oPC, PRC_CRAFT_SUBTYPEVALUE));
SQLocalsPlayer_SetString(oPC, "crafting_costtable", GetLocalString(oPC, PRC_CRAFT_COSTTABLE));
SQLocalsPlayer_SetInt(oPC, "crafting_costtablevalue", GetLocalInt(oPC, PRC_CRAFT_COSTTABLEVALUE));
SQLocalsPlayer_SetString(oPC, "crafting_param1", GetLocalString(oPC, PRC_CRAFT_PARAM1));
SQLocalsPlayer_SetInt(oPC, "crafting_param1value", GetLocalInt(oPC, PRC_CRAFT_PARAM1VALUE));
SQLocalsPlayer_SetInt(oPC, "crafting_proplist", GetLocalInt(oPC, PRC_CRAFT_PROPLIST));
// Save item properties
SQLocalsPlayer_SetInt(oPC, "crafting_baseitemtype", GetLocalInt(oPC, PRC_CRAFT_BASEITEMTYPE));
SQLocalsPlayer_SetInt(oPC, "crafting_time", nRounds);
SQLocalsPlayer_SetInt(oPC, "crafting_material", GetLocalInt(oPC, PRC_CRAFT_MATERIAL));
SQLocalsPlayer_SetInt(oPC, "crafting_mighty", GetLocalInt(oPC, PRC_CRAFT_MIGHTY));
SQLocalsPlayer_SetInt(oPC, "crafting_ac", GetLocalInt(oPC, PRC_CRAFT_AC));
SQLocalsPlayer_SetString(oPC, "crafting_tag", GetLocalString(oPC, PRC_CRAFT_TAG));
// Save magic crafting variables
SQLocalsPlayer_SetInt(oPC, "crafting_enhancement", GetLocalInt(oPC, PRC_CRAFT_MAGIC_ENHANCE));
SQLocalsPlayer_SetInt(oPC, "crafting_additional", GetLocalInt(oPC, PRC_CRAFT_MAGIC_ADDITIONAL));
SQLocalsPlayer_SetInt(oPC, "crafting_epic", GetLocalInt(oPC, PRC_CRAFT_MAGIC_EPIC));
// Save system state variables
SQLocalsPlayer_SetInt(oPC, "crafting_script_state", GetLocalInt(oPC, PRC_CRAFT_SCRIPT_STATE));
SQLocalsPlayer_SetInt(oPC, "crafting_token", GetLocalInt(oPC, PRC_CRAFT_TOKEN));
if(DEBUG) DoDebug("DEBUG: Crafting state saved - rounds: " + IntToString(nRounds));
}
}
/* // Save crafting state on logout, pause concentration checks & time tracking
void SaveCraftingState(object oPC)
{
if(GetLocalInt(oPC, "PRC_CRAFT_HB"))
{
// Get all crafting parameters
object oItem = GetLocalObject(oPC, "PRC_CRAFT_ITEM");
int nCost = GetLocalInt(oPC, "PRC_CRAFT_COST");
int nXP = GetLocalInt(oPC, "PRC_CRAFT_XP");
int nRounds = GetLocalInt(oPC, "PRC_CRAFT_ROUNDS");
string sFile = GetLocalString(oPC, "PRC_CRAFT_FILE");
int nLine = GetLocalInt(oPC, "PRC_CRAFT_LINE");
// Save all parameters to database
SQLocalsPlayer_SetObject(oPC, "crafting_item", oItem);
SQLocalsPlayer_SetInt(oPC, "crafting_cost", nCost);
SQLocalsPlayer_SetInt(oPC, "crafting_xp", nXP);
SQLocalsPlayer_SetInt(oPC, "crafting_rounds", nRounds);
SQLocalsPlayer_SetString(oPC, "crafting_file", sFile);
SQLocalsPlayer_SetInt(oPC, "crafting_line", nLine);
SQLocalsPlayer_SetInt(oPC, "crafting_active", 1);
// Save logout time using PRC time system
struct time tLogoutTime = GetTimeAndDate();
SetPersistantLocalTime(oPC, "crafting_logout_time", tLogoutTime);
// Remove concentration monitoring and clear heartbeat
RemoveEventScript(oPC, EVENT_VIRTUAL_ONDAMAGED, "prc_od_conc");
DeleteLocalInt(oPC, "PRC_CRAFT_HB");
}
} */
object GetItemByUUID(object oPC, string sUUID)
{
if(DEBUG) DoDebug("DEBUG: Searching for item with UUID: " + sUUID);
// Check player's inventory
object oItem = GetFirstItemInInventory(oPC);
while(GetIsObjectValid(oItem))
{
if(GetObjectUUID(oItem) == sUUID)
{
if(DEBUG) DoDebug("DEBUG: Found item in player inventory: " + GetName(oItem));
return oItem;
}
oItem = GetNextItemInInventory(oPC);
}
// Check equipped slots
int i;
for(i = 0; i < NUM_INVENTORY_SLOTS; i++)
{
oItem = GetItemInSlot(i, oPC);
if(GetIsObjectValid(oItem) && GetObjectUUID(oItem) == sUUID)
{
if(DEBUG) DoDebug("DEBUG: Found equipped item: " + GetName(oItem));
return oItem;
}
}
// Check the craft storage chest
object oChest = GetCraftChest();
if(GetIsObjectValid(oChest))
{
oItem = GetFirstItemInInventory(oChest);
while(GetIsObjectValid(oItem))
{
if(GetObjectUUID(oItem) == sUUID)
{
if(DEBUG) DoDebug("DEBUG: Found item in craft chest: " + GetName(oItem));
return oItem;
}
oItem = GetNextItemInInventory(oChest);
}
}
// Check temporary craft chest
object oTempChest = GetTempCraftChest();
if(GetIsObjectValid(oTempChest))
{
oItem = GetFirstItemInInventory(oTempChest);
while(GetIsObjectValid(oItem))
{
if(GetObjectUUID(oItem) == sUUID)
{
if(DEBUG) DoDebug("DEBUG: Found item in temp craft chest: " + GetName(oItem));
return oItem;
}
oItem = GetNextItemInInventory(oTempChest);
}
}
if(DEBUG) DoDebug("DEBUG: Item not found with UUID: " + sUUID);
return OBJECT_INVALID;
}
void CraftingHB(object oPC, object oItem, itemproperty ip, int nCost, int nXP, string sFile, int nLine, int nRounds)
{
// Save current timestamp for offline progress calculation
int nCurrentTime = GetCurrentUnixTimestamp();
if(nCurrentTime > 0)
{
SQLocalsPlayer_SetInt(oPC, "crafting_last_timestamp", nCurrentTime);
}
// Set the heartbeat flag
SetLocalInt(oPC, PRC_CRAFT_HB, 1);
// Set database flag for persistence tracking
SQLocalsPlayer_SetInt(oPC, "crafting_active", 1);
// Update local variables for heartbeat logic
SetLocalInt(oPC, PRC_CRAFT_TIME, nRounds);
SetLocalInt(oPC, PRC_CRAFT_COST, nCost);
SetLocalInt(oPC, PRC_CRAFT_XP, nXP);
SetLocalString(oPC, PRC_CRAFT_FILE, sFile);
SetLocalInt(oPC, PRC_CRAFT_LINE, nLine);
// Store the item property components for reconstruction
if(GetIsItemPropertyValid(ip))
{
SetLocalInt(oPC, "PRC_CRAFT_IP_TYPE", GetItemPropertyType(ip));
SetLocalInt(oPC, "PRC_CRAFT_IP_SUBTYPE", GetItemPropertySubType(ip));
SetLocalInt(oPC, "PRC_CRAFT_IP_COSTTABLE", GetItemPropertyCostTableValue(ip));
SetLocalInt(oPC, "PRC_CRAFT_IP_PARAM1", GetItemPropertyParam1Value(ip));
}
// Save current crafting state continuously for persistence
if(GetPRCSwitch(PRC_CRAFTING_TIME_SCALE) > 1)
{
// Store the CURRENT rounds value in database
SQLocalsPlayer_SetInt(oPC, "crafting_rounds", nRounds);
SaveCraftingState(oPC);
}
if(GetBreakConcentrationCheck(oPC))
{
FloatingTextStringOnCreature("Crafting: Concentration lost!", oPC);
DeleteLocalInt(oPC, PRC_CRAFT_HB);
RemoveEventScript(oPC, EVENT_VIRTUAL_ONDAMAGED, "prc_od_conc");
// Clear database state
SQLocalsPlayer_SetInt(oPC, "crafting_active", 0);
return;
}
if(nRounds == 0 || GetPCPublicCDKey(oPC) == "") //default to zero time if single player
{
if(DEBUG) DoDebug("DEBUG: Crafting completion - nCost: " + IntToString(nCost) + ", nLine: " + IntToString(nLine));
RemoveEventScript(oPC, EVENT_VIRTUAL_ONDAMAGED, "prc_od_conc");
if(GetLevelByClass(CLASS_TYPE_ARTIFICER, oPC))
{
if(!ArtificerPrereqCheck(oPC, sFile, nLine, nCost))
{
FloatingTextStringOnCreature("Crafting Failed!", oPC);
DeleteLocalInt(oPC, PRC_CRAFT_HB);
TakeGoldFromCreature(nCost, oPC, TRUE);
SetXP(oPC, PRCMax(GetXP(oPC) - nXP, GetHitDice(oPC) * (GetHitDice(oPC) - 1) * 500));
SQLocalsPlayer_SetInt(oPC, "crafting_active", 0);
return;
}
}
FloatingTextStringOnCreature("Crafting Complete!", oPC);
if(DEBUG) DoDebug("DEBUG: Entering ApplyProperties() >> nCost: " + IntToString(nCost) + ", nLine: " + IntToString(nLine));
ApplyProperties(oPC, oItem, ip, nCost, nXP, sFile, nLine);
DeleteLocalInt(oPC, PRC_CRAFT_HB);
if(GetLocalInt(oPC, "PRC_CRAFT_REMOVE_MASTERWORK"))
{
RemoveMasterworkProperties(oItem);
DeleteLocalInt(oPC, "PRC_CRAFT_REMOVE_MASTERWORK");
}
// Clear database state on completion
SQLocalsPlayer_SetInt(oPC, "crafting_active", 0);
}
else
{
if(DEBUG) DoDebug("DEBUG: Continuing CraftingHB() >> nCost: " + IntToString(nCost) + ", nLine: " + IntToString(nLine));
FloatingTextStringOnCreature("Crafting: " + IntToString(nRounds) + " round(s) remaining", oPC);
DelayCommand(6.0, CraftingHB(oPC, oItem, ip, nCost, nXP, sFile, nLine, nRounds - 1));
}
}
//;: void main(){}