diff --git a/FeLib/Include/festring.h b/FeLib/Include/festring.h index 3c1af2a54..5b4d93895 100644 --- a/FeLib/Include/festring.h +++ b/FeLib/Include/festring.h @@ -15,6 +15,7 @@ #include #include +#include #include "felibdef.h" @@ -119,6 +120,7 @@ class festring void ExtractWord(festring&); long GetCheckSum() const; void EnsureOwnsData(bool = false); + static pcre* CompilePCRE(pcre *pcreExistingRegexWorker, cfestring &fsPattern, festring *pfsErrorMsg=NULL); private: static void InstallIntegerMap(); static void DeInstallIntegerMap(); diff --git a/FeLib/Source/festring.cpp b/FeLib/Source/festring.cpp index 6d26138ce..bece51dc3 100644 --- a/FeLib/Source/festring.cpp +++ b/FeLib/Source/festring.cpp @@ -13,6 +13,8 @@ #include #include #include +#include + #include "festring.h" #include "allocate.h" #include "error.h" @@ -713,8 +715,13 @@ festring::sizetype festring::IgnoreCaseFind(cfestring& Where, return NPos; } -/* Replaces all occurances of What in Where after Begin with With */ - +/** + * Replaces all occurances of What in Where after Begin with With + * @param Where + * @param What + * @param With + * @param Begin + */ void festring::SearchAndReplace(festring& Where, cfestring& What, cfestring& With, sizetype Begin) { @@ -907,3 +914,43 @@ void festring::EnsureOwnsData(bool Unique) CreateOwnData(Data, Size); } } + +/** + * + * @param pcreExistingRegexWorker it has to be freed if was already set + * @param fsPattern + * @param bWarnOnError + * @return + */ +pcre* festring::CompilePCRE(pcre *pcreExistingRegexWorker, cfestring &fsPattern, festring *pfsErrorMsg) +{ + if(pcreExistingRegexWorker) + pcre_free(pcreExistingRegexWorker); + + if(fsPattern.IsEmpty()) + return NULL; + + const char *errMsg; + int iErrOffset; + pcreExistingRegexWorker = pcre_compile( + fsPattern.CStr(), + 0, // no options + &errMsg, &iErrOffset, + 0 // default char tables + ); + + if (!pcreExistingRegexWorker){ + festring fsErr; + fsErr<<"Regex validation failed, if ignored will just not work at all.\n" + <) diff --git a/Main/Source/cmdcraft.cpp b/Main/Source/cmdcraft.cpp index 31c67c42d..6262b8faf 100644 --- a/Main/Source/cmdcraft.cpp +++ b/Main/Source/cmdcraft.cpp @@ -100,16 +100,16 @@ bool craftcore::EmptyContentsIfPossible(recipedata& rpd,item* itContainer, bool /** * Why this is important? - * - * Because if the item remain on slot, and 1 turn has not passed, + * + * Because if the item remain on slot, and 1 turn has not passed, * what may happpen on crafting code in case user gives up on crafting action, * it will provide inconsistent inventory contents (something that was sent to hell * but is still on inventory as the turn has not passed to properly send it to hell). - * + * * If all the above is correctly understood as "it is how thing work", * then crafting should be fixed to always pass one turn even if user giveup? * But that doesnt looks good, giveup = cancel, should not pass a turn at all. - * + * * This shall be used for anything related to crafting until something better is coded. */ void craftcore::SendToHellSafely(item* it) @@ -2096,7 +2096,10 @@ struct srpInspect : public recipe{ material* matM = it0->GetMainMaterial(); material* matS = it0->GetSecondaryMaterial(); festring fs; - fs<GetName(DEFINITE)<<" is made of "; + fs << it0->GetName(DEFINITE) + << " (" << game::StoreMatchNameKey(it0) << " or " //used by auto-store items on containers, so here is the place to determine it's value + << game::StoreMatchNameKey(it0,true) << ")" + << " is made of "; if(matM)fs<GetName(UNARTICLED); if(matS){ if(matM)fs<<" and "; //actually, there is only 2nd material if there is main but anyway... diff --git a/Main/Source/command.cpp b/Main/Source/command.cpp index 40e2fea57..92aa8aad6 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -686,6 +686,8 @@ truth commandsystem::PickUp(character* Char) if(game::IsAutoPickupMatch(PileVector[0][c]->GetName(DEFINITE))){ PileVector[0][c]->ClearTag('d'); //intentionally drop tag dismissed for autopickup regex match } + + game::AutoStoreItemInContainer(PileVector[0][c],Char); } ADD_MESSAGE("%s picked up.", PileVector[0][0]->GetName(INDEFINITE, Amount).CStr()); @@ -1145,7 +1147,8 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng break; festring What = ToAddLabel[0]->GetLabel(); - if(game::StringQuestion(What, CONST_S("What would you like to inscribe on this item?"), WHITE, 0, 20, true) == NORMAL_EXIT) + int iMaxChars=150; // item labels can contain user custom hints to let the algorithm use these to perform automatic actions + if(game::StringQuestion(What, CONST_S("What would you like to inscribe on this item?"), WHITE, 0, iMaxChars, true) == NORMAL_EXIT) for(int i=0;iSetLabel(What); } diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index 4e551d084..9bf077bac 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -1324,40 +1324,70 @@ int game::RotateMapNotes() return iMapNotesRotation; } -std::vector afsAutoPickupMatch; -pcre *reAutoPickup=NULL; -void game::UpdateAutoPickUpMatching() //simple matching syntax +/** + * + * @param it + * @param bUnarticled if false is GetNameSingular() + * @return + */ +cchar* game::StoreMatchNameKey(item* it,bool bUnarticled) { - afsAutoPickupMatch.clear(); - - bool bSimple=false; - if(bSimple){ //TODO just drop the simple code? or start the string with something to let it be used instead of regex? tho is cool to let ppl learn regex :) - if(ivanconfig::GetAutoPickUpMatching().GetSize()==0 || ivanconfig::GetAutoPickUpMatching()[0]=='!')return; + static festring fsRet,fsSpace=" ",fsEmpty=""; + static cchar* cToken="+"; + + fsRet=""; + fsRet<GetName(UNARTICLED|UNLABELED); + else + fsRet<GetNameSingular()< afsFullProblems; - afsFullProblems.push_back(festring(errMsg)); - afsFullProblems.push_back(festring()+"offset:"+iErrOffset); - bool bDummy = iosystem::AlertConfirmMsg("regex validation failed, if ignored will just not work at all",afsFullProblems,false); +void game::AutoStoreItemInContainer(item* itToStore,character* C) +{ + if(!C->IsPlayer()) + return; + + static itemvector vit;vit.clear(); + C->GetStack()->FillItemVector(vit); + for(int i=0;i(vit[i]); + if(itc){ + long lRemainingVol = itc->GetStorageVolume() - itc->GetContained()->GetVolume(); + DBG3(lRemainingVol,itToStore->GetVolume(),itc->GetLabel().CStr()); + if(lRemainingVolGetVolume()) + continue; + + if(itc->IsAutoStoreMatch(itToStore->GetName(DEFINITE))){ + itToStore->RemoveFromSlot(); + itc->GetContained()->AddItem(itToStore); + ADD_MESSAGE("%s was safely stored in %s",itToStore->GetName(DEFINITE).CStr(),itc->GetName(DEFINITE).CStr()); + break; + } } } } + +pcre *reAutoPickup=NULL; +void game::UpdateAutoPickUpRegex() +{ + //TODO test regex about: ignoring broken lanterns and bottles, ignore sticks on fire but pickup scrolls on fire + festring fsErr; + reAutoPickup = festring::CompilePCRE(reAutoPickup,ivanconfig::GetAutoPickUpMatching(),&fsErr); + if(!fsErr.IsEmpty()){ + std::vector afsFullProblems; + afsFullProblems.push_back(fsErr); + bool bDummy = iosystem::AlertConfirmMsg("Failed updating auto-pickup regex.",afsFullProblems,false); + } +} bool game::IsAutoPickupMatch(cfestring fsName) { + if(!reAutoPickup) + return false; return pcre_exec(reAutoPickup, 0, fsName.CStr(), fsName.GetSize(), 0, 0, NULL, 0) >= 0; } int game::CheckAutoPickup(square* sqr) @@ -1370,34 +1400,31 @@ int game::CheckAutoPickup(square* sqr) lsquare* lsqr = (lsquare*)sqr; - static bool bDummyInit = [](){UpdateAutoPickUpMatching();return true;}(); + static bool bDummyInit = [](){UpdateAutoPickUpRegex();return true;}(); itemvector iv; lsqr->GetStack()->FillItemVector(iv); int iTot=0; for(int i=0;iGetRoom() && it->GetRoom()->GetMaster())continue; //not from owned rooms - if(it->GetSpoilLevel()>0)continue; + if(it->GetSpoilLevel()>0)continue; //just a guess no one auto-wants it + bool b=false; if(!b && ivanconfig::IsAutoPickupThrownItems() && it->HasTag('t') )b=true; //was thrown - if(!b && !it->HasTag('d')){ - if(reAutoPickup!=NULL){ - if(IsAutoPickupMatch(it->GetName(DEFINITE))){ - b=true; - } - } - } - if(!b){ //TODO use player's perception, in case of a stack of items, to allow random pickup based on item volume (size) where smaller = harder like tiny rings, to compensate for the easiness of not losing a round having to pick up the item interactively - for(int i=0;iGetNameSingular().Find(afsAutoPickupMatch[i].CStr(),0) != festring::NPos){ - b=true; - break; //each simple match loop - } - } + if(!b && !it->HasTag('d')){ //was NOT intentionally dropped (dropping adds such tag) + /** + * TODO ? + * Use player's perception, in case of a stack of items, + * to allow random pickup based on item volume (size) where smaller = harder like tiny rings, + * to compensate for the easiness of not losing a round having to pick up the item interactively. + * But it may just be annnoying... + */ + b = IsAutoPickupMatch(it->GetName(DEFINITE)); } if(b){ it->MoveTo(PLAYER->GetStack()); ADD_MESSAGE("%s picked up.", it->GetName(INDEFINITE).CStr()); + AutoStoreItemInContainer(it,PLAYER); iTot++; } } diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index 3d98f062d..a013f2dc9 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -52,7 +52,12 @@ stringoption ivanconfig::SelectedBkgColor("SelectedBkgColor", &SelectedBkgColorChanger); stringoption ivanconfig::AutoPickUpMatching("AutoPickUpMatching", "Auto pick up regex", - "Automatically pick up items according to a regular expression. To disable something, you can invalidate it with '_' without removing it from the expression (eg. '_dagger'). To disable everything at once, begin this config option with '!'. Due to current constraints on length of options, editing is easier to do externally for now.", //TODO if multiline text editing is implemented, remove the last help statement. + "Automatically pick up items according to a regular expression.\n" + " To disable something, you can invalidate it with '_' without removing it from the expression (eg. '_dagger').\n" + " To disable everything at once, begin this config option with '!'.\n" + " Containers inscribed with eg. '+key+ring+scroll' will auto store such items,\n" + " and you can determine what to inscribe by craft/inspecting the items.\n" + " Due to current constraints on length of options, editing is easier to do externally for now.", //TODO if multiline text editing is implemented, remove the last help statement. "!((book|can|dagger|grenade|horn of|kiwi|key|ring|scroll|wand|whistle)|^(?:(?!(broken|empty)).)*(bottle|vial)|sol stone)", &configsystem::NormalStringDisplayer, &AutoPickUpMatchingChangeInterface, @@ -911,7 +916,7 @@ void ivanconfig::AutoPickUpMatchingChanger(stringoption* O, cfestring& What) if(O!=NULL){ O->Value.Empty(); O->Value<TakeSomethingFrom(Opener, GetName(DEFINITE)); + Success = Stk->TakeSomethingFrom(Opener, fsName); break; case 'p': case 'P': - Success = GetContained()->PutSomethingIn(Opener, GetName(DEFINITE), GetStorageVolume(), GetID()); + Success = Stk->PutSomethingIn(Opener, fsName, volume, ID); break; default: return false; @@ -1404,10 +1400,23 @@ truth itemcontainer::Open(character* Opener) if(Success) Opener->DexterityAction(Opener->OpenMultiplier() * 5); - + return Success; } +/* Returns true if container opens fine else false */ + +truth itemcontainer::Open(character* Opener) +{ + if(IsLocked()) + { + ADD_MESSAGE("%s seems to be locked.", CHAR_NAME(DEFINITE)); + return false; + } + + return OpenGeneric(Opener,GetContained(),GetName(DEFINITE),GetStorageVolume(),ID); +} + void itemcontainer::Save(outputfile& SaveFile) const { lockableitem::Save(SaveFile); @@ -1769,9 +1778,33 @@ materialcontainer::materialcontainer(const materialcontainer& MC) : mybase(MC) itemcontainer::itemcontainer(const itemcontainer& Container) : mybase(Container) { Contained = new stack(0, this, HIDDEN); + pcreAutoStoreRegex = festring::CompilePCRE(NULL,Container.GetLabel()); + bLazyInitPcre=true; + CalculateAll(); } +void itemcontainer::SetLabel(cfestring& What) +{ + item::SetLabel(What); + pcreAutoStoreRegex = festring::CompilePCRE(pcreAutoStoreRegex,GetLabel()); +} + +truth itemcontainer::IsAutoStoreMatch(cfestring fs) { + if(bLazyInitPcre){ + if(!pcreAutoStoreRegex && !GetLabel().IsEmpty()){ + pcreAutoStoreRegex = festring::CompilePCRE(pcreAutoStoreRegex,GetLabel()); + DBGEXEC( if(!pcreAutoStoreRegex){ DBG2("InvalidRegex",GetLabel().CStr()); } ); + } + bLazyInitPcre=false; + } + + if(!pcreAutoStoreRegex) + return false; + + return pcre_exec(pcreAutoStoreRegex, 0, fs.CStr(), fs.GetSize(), 0, 0, NULL, 0) >= 0; +} + oillamp::oillamp(const oillamp& Lamp) : mybase(Lamp), InhabitedByGenie(false) { } diff --git a/igor/CMakeLists.txt b/igor/CMakeLists.txt index b1c0b73bb..810a277ab 100644 --- a/igor/CMakeLists.txt +++ b/igor/CMakeLists.txt @@ -1,5 +1,19 @@ set(IGOR_VERSION 1.203) +include(FindPkgConfig) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PCRE libpcre) + set(PCRE_LIBRARIES ${PCRE_LDFLAGS}) +endif() +if(NOT PCRE_FOUND) + if(MSVC) + find_path(PCRE_INCLUDE_DIR NAMES pcre.h) + find_library(PCRE_LIBRARY pcre) + endif() + find_package(PCRE REQUIRED) + add_definitions(${PCRE_DEFINITIONS}) +endif() + add_executable(igor EXCLUDE_FROM_ALL Source/igor.cpp) -target_include_directories(igor PUBLIC ../Felib/Include) -target_link_libraries(igor FeLib xbrzscale) +target_include_directories(igor PUBLIC ../Felib/Include ${PCRE_INCLUDE_DIRS}) +target_link_libraries(igor FeLib xbrzscale ${PCRE_LIBRARIES}) diff --git a/mihail/CMakeLists.txt b/mihail/CMakeLists.txt index e7c952b1e..c037ff7c6 100644 --- a/mihail/CMakeLists.txt +++ b/mihail/CMakeLists.txt @@ -1,5 +1,19 @@ set(MIHAIL_VERSION 0.91) +include(FindPkgConfig) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PCRE libpcre) + set(PCRE_LIBRARIES ${PCRE_LDFLAGS}) +endif() +if(NOT PCRE_FOUND) + if(MSVC) + find_path(PCRE_INCLUDE_DIR NAMES pcre.h) + find_library(PCRE_LIBRARY pcre) + endif() + find_package(PCRE REQUIRED) + add_definitions(${PCRE_DEFINITIONS}) +endif() + add_executable(mihail EXCLUDE_FROM_ALL Source/mihail.cpp) -target_include_directories(mihail PUBLIC ../Felib/Include) -target_link_libraries(mihail FeLib xbrzscale) +target_include_directories(mihail PUBLIC ../Felib/Include ${PCRE_INCLUDE_DIRS}) +target_link_libraries(mihail FeLib xbrzscale ${PCRE_LIBRARIES})