//Dependencies: ats_inc_common, ats_const_mat, ats_const_common, ats_inc_material, ats_inc_cr_get /* ATS_FindComponents function written to combine the redundant ATS_CheckCraftComponentsOnPlayer and ATS_RemoveCraftMaterials functions simply adding a flag to indicate whether or not to remove the components when they're found. Later improved it to also allow it to handle removing components based on risk instead of all or nothing, for use in ats_at_cr_fail. Remove = 0 find them, report them as found, but don't remove. Remove = 1 find them, remove them all, report them as removed. Remove = 2 compute losses based on consumption risk, find and remove them. Also added functionality to speed the inventory search, making non-matching items fail much more quickly, reducing TMIs further by reducing the failing iterations to 2 instructions each. The first pass through, a master search string is built (AllTags) which includes all the tags for needed items, concatenated into one huge string. At the end of that pass, it cycles through the inventory in a quick search for the next matching tag, once it finds one it sends it through the main loop for another pass, figuring out which it was, and whether or not all the needed items of that type have been found or not. To increase efficiency further, each time an item is found, and no more of that item type are needed, we rebuild the search string to hone the search for the remaining items needed. Once the search string is blank, all items have been found, and we drop out of the loop completely. This functionality adds a half dozen or so instructions to the passes where an item matches, but it reduces the hundreds of passes where an item doesn't match to 2 instructions each. The more cluttered the PC's inventory, the more efficient it becomes. Written by: Keith Westrate using Mojo's original code for the redundant functions. */ int ATS_FindComponents(object oPlayer, string sCraftTag, int iMaterialType, int Remove) { string sDyeTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_DYES, iMaterialType); int iDyeAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_DYES, iMaterialType); string sFlowerTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_FLOWERS, iMaterialType); int iFlowerAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_FLOWERS, iMaterialType); string sClothSTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSS, iMaterialType); int iClothSAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSS, iMaterialType); string sClothMTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSM, iMaterialType); int iClothMAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSM, iMaterialType); string sClothLTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSL, iMaterialType); int iClothLAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSL, iMaterialType); string sIngotTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_INGOTS, iMaterialType); int iIngotAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_INGOTS, iMaterialType); string sGemTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_GEMS, iMaterialType); int iGemAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_GEMS, iMaterialType); string sIdealGemTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_IDEALGEMS, iMaterialType); int iIdealGemAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_IDEALGEMS, iMaterialType); int bFoundAllNeeded = TRUE; int iConsumptionCount = 0; int iConsumptionRisk; string sItemTag; string sCustomTagNeeded; string sCustomBundleTagNeeded; int iBundleAmount = 0; int iRemainingQuantity = 0; int iStackedQuantity = 0; int BuildTags = TRUE; int RebuildTags = FALSE; string AllTags = ""; int iCustomAmountNeeded; int iCustomMaterialCount = GetLocalInt(GetModule(), sCraftTag + "_custom_material"); string sMessage; int i, count; if(Remove == 2) { // if remove is 2, only remove components based on consumption risk iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_DYES, iMaterialType); for(i = 1; i <= iDyeAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iDyeAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " dye"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_FLOWERS, iMaterialType); for(i = 1; i <= iFlowerAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iFlowerAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " flower"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSS, iMaterialType); for(i = 1; i <= iClothSAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iClothSAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " small cloth"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSM, iMaterialType); for(i = 1; i <= iClothMAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iClothMAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " medium cloth"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_CLOTHSL, iMaterialType); for(i = 1; i <= iClothLAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iClothLAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " large cloth"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_INGOTS, iMaterialType); for(i = 1; i <= iIngotAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iIngotAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + ATS_GetMaterialName(iMaterialType) + " ingot"; if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_GEMS, iMaterialType); for(i = 1; i <= iGemAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iGemAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + GetName(GetObjectByTag(sGemTagNeeded)); if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } iConsumptionCount = 0; iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_IDEALGEMS, iMaterialType); for(i = 1; i <= iIdealGemAmountNeeded; ++i) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iIdealGemAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt the // craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + GetName(GetObjectByTag(sIdealGemTagNeeded)); if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } } for(i = 1; i <= iCustomMaterialCount; ++i) { sCustomTagNeeded = ATS_GetComponentTagFromRecipe(sCraftTag, CINT_COMPONENT_ID_CUSTOM, i); iCustomAmountNeeded = ATS_GetComponentAmountFromRecipe(sCraftTag, CINT_COMPONENT_ID_CUSTOM, i); if(Remove == 2) { // remove only percentage of components based on risk% iConsumptionRisk = ATS_GetConsumptionRiskFromRecipe(sCraftTag, CINT_COMPONENT_ID_CUSTOM, i); iConsumptionCount = 0; for(count = 1; count <= iCustomAmountNeeded; ++count) { if(d100(1) <= iConsumptionRisk) ++iConsumptionCount; } iCustomAmountNeeded = iConsumptionCount; if(iConsumptionCount > 0) { // since we knew the PC had enough components to attempt // the craft, assume we'll find them and remove them sMessage = "You have lost " + IntToString(iConsumptionCount) + " " + GetName(GetObjectByTag(sCustomTagNeeded)); if(iConsumptionCount > 1) sMessage += "s"; sMessage += "."; SendMessageToPC(oPlayer, sMessage); } } SetLocalString(oPlayer, "ats_customneeded_" + IntToString(i) + "_tag", sCustomTagNeeded); SetLocalInt(oPlayer, "ats_customneeded_" + IntToString(i) + "_amount", iCustomAmountNeeded); } // Iterate through the specified player's // item repository object oItemOnPlayer = GetFirstItemInInventory(oPlayer); while ( oItemOnPlayer != OBJECT_INVALID ) { // Check Dyes if(iDyeAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sDyeTagNeeded; if ( GetTag(oItemOnPlayer) == sDyeTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iIngotAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iDyeAmountNeeded; iDyeAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sDyeTagNeeded, oPlayer, iRemainingQuantity)); } if(iDyeAmountNeeded <= 0) RebuildTags = TRUE; } } // Check Flowers if(iFlowerAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sFlowerTagNeeded; if ( GetTag(oItemOnPlayer) == sFlowerTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iIngotAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iFlowerAmountNeeded; iFlowerAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sFlowerTagNeeded, oPlayer, iRemainingQuantity)); } if(iFlowerAmountNeeded <= 0) RebuildTags = TRUE; } } // Check Small Cloths if(iClothSAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sClothSTagNeeded; if ( GetTag(oItemOnPlayer) == sClothSTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iClothSAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iClothSAmountNeeded; iClothSAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sClothSTagNeeded, oPlayer, iRemainingQuantity)); } if(iClothSAmountNeeded <= 0) RebuildTags = TRUE; } } // Check Medium Cloths if(iClothMAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sClothMTagNeeded; if ( GetTag(oItemOnPlayer) == sClothMTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iClothMAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iClothMAmountNeeded; iClothMAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sClothMTagNeeded, oPlayer, iRemainingQuantity)); } if(iClothMAmountNeeded <= 0) RebuildTags = TRUE; } } // Check Large Cloths if(iClothLAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sClothLTagNeeded; if ( GetTag(oItemOnPlayer) == sClothLTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iClothLAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iClothLAmountNeeded; iClothLAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sClothLTagNeeded, oPlayer, iRemainingQuantity)); } if(iClothLAmountNeeded <= 0) RebuildTags = TRUE; } } // Check Ingots if(iIngotAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sIngotTagNeeded; if ( GetTag(oItemOnPlayer) == sIngotTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iIngotAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iIngotAmountNeeded; iIngotAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sIngotTagNeeded, oPlayer, iRemainingQuantity)); } if(iIngotAmountNeeded <= 0) RebuildTags = TRUE; } } //Check gems if(iGemAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sGemTagNeeded; if ( GetTag(oItemOnPlayer) == sGemTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iGemAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iGemAmountNeeded; iGemAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sGemTagNeeded, oPlayer, iRemainingQuantity)); } if(iGemAmountNeeded <= 0) RebuildTags = TRUE; } } //Check ideal gems if(iIdealGemAmountNeeded > 0) { // At least one is needed, add tag to search string if ( BuildTags ) AllTags += sIdealGemTagNeeded; if ( GetTag(oItemOnPlayer) == sIdealGemTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iIdealGemAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iIdealGemAmountNeeded; iIdealGemAmountNeeded -= iStackedQuantity; if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sIdealGemTagNeeded, oPlayer, iRemainingQuantity)); } if(iIdealGemAmountNeeded <= 0) RebuildTags = TRUE; } } // Check custom materials for(i = 1; i <= iCustomMaterialCount; ++i) { iCustomAmountNeeded = GetLocalInt(oPlayer, "ats_customneeded_" + IntToString(i) + "_amount"); if(iCustomAmountNeeded > 0) { sCustomTagNeeded = GetLocalString(oPlayer, "ats_customneeded_" + IntToString(i) + "_tag"); sCustomBundleTagNeeded = GetStringLeft(sCustomTagNeeded, 11) + "B" + GetStringRight(sCustomTagNeeded, GetStringLength(sCustomTagNeeded) - 12); // At least one item of this type still needed, add // both bundle tag and item tag to search string if ( BuildTags ) AllTags += ( sCustomTagNeeded + sCustomBundleTagNeeded ); sItemTag = GetTag(oItemOnPlayer); if(GetStringLeft(sItemTag, 16) == sCustomBundleTagNeeded) { iBundleAmount = StringToInt(GetStringRight(sItemTag, GetStringLength(sItemTag) - 17)); iRemainingQuantity = 0; if(iCustomAmountNeeded < iBundleAmount) iRemainingQuantity = iBundleAmount - iCustomAmountNeeded; iCustomAmountNeeded -= iBundleAmount; if(iCustomAmountNeeded < 0) iCustomAmountNeeded = 0; SetLocalInt(oPlayer, "ats_customneeded_" + IntToString(i) + "_amount", iCustomAmountNeeded); if(Remove) { DestroyObject(oItemOnPlayer); for( i = 0; i < iRemainingQuantity; ++i) { ATS_CreateItemOnPlayer(ATS_GetResRefFromTag(sCustomTagNeeded), oPlayer); } } } else if ( sItemTag == sCustomTagNeeded ) { iStackedQuantity = GetNumStackedItems(oItemOnPlayer); iRemainingQuantity = 0; if(iCustomAmountNeeded < iStackedQuantity) iRemainingQuantity = iStackedQuantity - iCustomAmountNeeded; iCustomAmountNeeded -= iStackedQuantity; if(iCustomAmountNeeded < 0) iCustomAmountNeeded = 0; SetLocalInt(oPlayer, "ats_customneeded_" + IntToString(i) + "_amount", iCustomAmountNeeded); if(Remove) { DestroyObject(oItemOnPlayer); if(iRemainingQuantity > 0) DelayCommand(0.5, ATS_CreateItemOnPlayer(sCustomTagNeeded, oPlayer, iRemainingQuantity)); } } if(iCustomAmountNeeded <= 0) RebuildTags = TRUE; // found enough of this item } } BuildTags = FALSE; // Make sure it's only built once, unless an item is no longer needed if(AllTags == "") break; // No more items needed, so skip the rest of the inventory search do { // fast search inventory for next item that has a tag that matches one of the needed items oItemOnPlayer = GetNextItemInInventory(oPlayer); } while(GetIsObjectValid(oItemOnPlayer) && FindSubString(AllTags,GetStringLeft(GetTag(oItemOnPlayer),16)) == -1); if(RebuildTags) { // an item is no longer needed, so rebuild the search string during the next pass BuildTags = TRUE; RebuildTags = FALSE; AllTags = ""; } } if(iDyeAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iFlowerAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iClothSAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iClothMAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iClothLAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iIngotAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iGemAmountNeeded > 0) bFoundAllNeeded = FALSE; else if(iIdealGemAmountNeeded > 0) bFoundAllNeeded = FALSE; else { for(i = 1; i <= iCustomMaterialCount; ++i) { iCustomAmountNeeded = GetLocalInt(oPlayer, "ats_customneeded_" + IntToString(i) + "_amount"); if(iCustomAmountNeeded > 0) { bFoundAllNeeded = FALSE; break; } } } return bFoundAllNeeded; } // wrapper for the function to only find the components, not remove them int ATS_CheckCraftComponentsOnPlayer(object oPlayer, string sCraftTag, int iMaterialType) { return ATS_FindComponents(oPlayer, sCraftTag, iMaterialType, FALSE); } // wrapper for the function to find AND remove the components int ATS_RemoveCraftMaterials(object oPlayer, string sCraftTag, int iMaterialType) { return ATS_FindComponents(oPlayer, sCraftTag, iMaterialType, TRUE); }