diff --git a/.devsPrefs/AquariusPower/.gitignore.AquariusPower b/.devsPrefs/AquariusPower/.gitignore.AquariusPower index 04bc83fd9..de0e92646 100644 --- a/.devsPrefs/AquariusPower/.gitignore.AquariusPower +++ b/.devsPrefs/AquariusPower/.gitignore.AquariusPower @@ -82,3 +82,6 @@ dbgmsg.cpp /SDL2-2.0.4/ build build.* +.kdev_include_paths +/.devsPrefs/AquariusPower/nbproject/private/* +/nbproject diff --git a/.devsPrefs/AquariusPower/TODO.txt b/.devsPrefs/AquariusPower/TODO.txt new file mode 100644 index 000000000..ce3bcdc27 --- /dev/null +++ b/.devsPrefs/AquariusPower/TODO.txt @@ -0,0 +1,46 @@ +FIX-CRITICAL + +FIX-IMPORTANT +- validate the existance of all generated savegame files, it is stored in the .sav file, would crash if missing so better just promptly look for them... +- disappearing pet angel wont drop all equipped items (the ones we give'm and would not normally disappear...), namely torso armor at least + +FIX-MINOR +- by remembering the previously selected entry list, sometimes the wrong savegame is being loaded, not the "apparently" selected one... +- skeleton dog shouldn't pee..., but could say "tries to pee but nothing happens" :) +- pet with poly ring and polycontrol ring, polymorphing into ghost then into bunny, when back to human will not re-equip +- deny showitemsunder if lantern is on the wall? (amulet of phasing) +- when felist is open, still showing tiny dungeon animated behind... +- the last entry in the savegame list, even after imported, may still show as being in the previous version and after importing a new player name may be used and so a new savegame created, quite a mess... (quite confuse this one, may be a unique bug that only happens when importing some specific random rare old saves...) + +IMPROVEMENTS +- mine::WillExplode() if stepper is trying to pickup, check if levitating and 25+ dexterity (or only dexterity but harder like x2) to let it be disarmed and safely picked up +- craft dismantle w/o forge should require dagger and hammer if has any meltable + - craft test material should allow broken things too + - craft if sat on a chair should lower craft time too + - craft tools should take damage too, mainly if the worked materials are harder +- felist: default remember_selected +- simple pile equal items on "show items under", ex.: if more than 1, simply draw a tiny + sign with 2 drawn white lines + - show items under, begin with the last one, unless if showing them all in lines +- mini map unfold slowly once per frame one dungeon line of squares animation +- remove flag CanBeDestroyed from items that can be crafted? or deny their crafting? or keep as on fumble creates lump? +- show material Str only if in possession of the materials book + - inventory 'i' will be selectable if in possession of the materials book and chosing will show at most 2 lines as filter to the book +- pickup accept many using the same pickup key ',' at least see the other keys cfgs too + +NEW STUFF +- draw the current equipped amulet on the player +- sfx "It smells oddly intelligent here." the good smoke as opposed to "a sinister stench surrounds you" the bad smoke + +================================================================================================================== +DONE: +- add a lsquare token during detect material to highlight the square, the guessed luminance fails at daylight 13:02h +- quick swap weapons hotkey, to also reequip after mining interrupted +- hum?? walk over friendly: chat and wait one turn to let it move +- ?make pet always follow very closely to avoid getting lost from the player? or that is part of the troubles/difficulty? :) +- make pet look at last 3 squares player walked before waiting +- sfx for .*hit.* it is matching white :P +- list item zoom xBRZ, draw the background behind it to make the blending work properly all times! +- craft fluid extraction should not require a specific previous location to resume +- dupPlayerBugFix: check also for duplicity of TrapIDMap as crashing on save +- optionally auto inscribe some map notes like altars, anvil, chair etc +- the materials book could be filtered by typing (felist already can filter anything) diff --git a/.devsPrefs/AquariusPower/nbproject/configurations.xml b/.devsPrefs/AquariusPower/nbproject/configurations.xml new file mode 100644 index 000000000..47597a70f --- /dev/null +++ b/.devsPrefs/AquariusPower/nbproject/configurations.xml @@ -0,0 +1,332 @@ + + + + + + + + + + + felist.h + feloops.h + femath.h + festring.h + fetime.h + graphics.h + hscore.h + + + bitmap.cpp + config.cpp + dbgmsg.cpp + error.cpp + febot.cpp + feio.cpp + felist.cpp + femath.cpp + festring.cpp + fetime.cpp + graphics.cpp + hscore.cpp + rawbit.cpp + save.cpp + specialkeys.cpp + whandler.cpp + + + action.cpp + actions.cpp + actset.cpp + area.cpp + areaset.cpp + bodypart.cpp + bugworkaround.cpp + char.cpp + charset.cpp + charsset.cpp + cmdcraft.cpp + cmdswapweap.cpp + command.cpp + cont.cpp + coreset.cpp + database.cpp + dataset.cpp + definesvalidator.cpp + devcons.cpp + dungeon.cpp + entity.cpp + fluid.cpp + game.cpp + gear.cpp + god.cpp + gods.cpp + godset.cpp + hiteffect.cpp + iconf.cpp + id.cpp + igraph.cpp + item.cpp + itemset.cpp + level.cpp + levelset.cpp + main.cpp + materia.cpp + materias.cpp + materset.cpp + message.cpp + object.cpp + room.cpp + rooms.cpp + roomset.cpp + script.cpp + slot.cpp + slotset.cpp + stack.cpp + trap.cpp + traps.cpp + trapset.cpp + wmapset.cpp + worldmap.cpp + wskill.cpp + wsquare.cpp + wterra.cpp + wterras.cpp + + + + + CMakeLists.txt + Makefile + nbproject/private/launcher.properties + + + ^(nbproject)$ + + audio + fantasyname + FeLib/Source + xbrzscale + Main/Source + Main/Include + FeLib/Include + + Makefile + + + + default + false + false + + + + + + . + ${MAKE} -f Makefile + ${MAKE} -f Makefile clean + + + + . + ${CMAKE} -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${IDE_CC} -DCMAKE_CXX_COMPILER=${IDE_CXX} -DCMAKE_C_FLAGS_DEBUG="-g3 -gdwarf-2" -DCMAKE_CXX_FLAGS_DEBUG="-g3 -gdwarf-2" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON . + + + + + + default + false + false + + + + + + . + ${MAKE} -f Makefile + ${MAKE} -f Makefile clean + + + + DBGMSG + FELIST_WAITKEYUP + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + WIZARD + + + + + . + ${CMAKE} -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${IDE_CC} -DCMAKE_CXX_COMPILER=${IDE_CXX} -DCMAKE_C_FLAGS_DEBUG="-g3 -gdwarf-2" -DCMAKE_CXX_FLAGS_DEBUG="-g3 -gdwarf-2" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.devsPrefs/AquariusPower/nbproject/project.xml b/.devsPrefs/AquariusPower/nbproject/project.xml new file mode 100644 index 000000000..491b5d418 --- /dev/null +++ b/.devsPrefs/AquariusPower/nbproject/project.xml @@ -0,0 +1,36 @@ + + + org.netbeans.modules.cnd.makeproject + + + Ivan.github + + cc,cpp + h + UTF-8 + + + audio + fantasyname + FeLib/Source + xbrzscale + Main/Source + Main/Include + FeLib/Include + + + + Default + 0 + + + myCfg + 0 + + + + false + + + + diff --git a/Doc/Misc/DefinesValidator.txt b/Doc/Misc/DefinesValidator.txt index 0aa1731e3..0b2e9b97e 100644 --- a/Doc/Misc/DefinesValidator.txt +++ b/Doc/Misc/DefinesValidator.txt @@ -6,7 +6,7 @@ or when someone changes a value only at `define.dat` or at some c++ .h file, so 2) compile and install the game. -3) go to that option and toggle it to yes. +3) run any game, go to the console ctrl+` and run the generator 4) the file `definesvalidator.h` will be created at the same path where the `ivan.conf` is located. @@ -15,4 +15,4 @@ or when someone changes a value only at `define.dat` or at some c++ .h file, so 6) compile and install again so that the validator will know what value the c++ `#define` should have, based on the define.dat file. in case c++ has no such define, it will be ignored ex.: the check will be performed only `#ifdef EMISSARY` (currently not used in c++ .h files) -7) run and activate that option to `yes`, it will abort complaining about TORSO value with this message: "Defined TORSO with value 2 from .dat file mismatches hardcoded c++ define value of 1!" +7) run any game, go to the console ctrl+` and run the validator, it will abort complaining about TORSO value with this message: "Defined TORSO with value 2 from .dat file mismatches hardcoded c++ define value of 1!" diff --git a/FeLib/Include/feio.h b/FeLib/Include/feio.h index 0b8a7b2d9..59e6e3d64 100644 --- a/FeLib/Include/feio.h +++ b/FeLib/Include/feio.h @@ -17,6 +17,7 @@ #include "festring.h" class bitmap; +class inputfile; typedef truth (*stringkeyhandler)(int, festring&); typedef void (*bitmapeditor)(bitmap*, truth); @@ -44,6 +45,7 @@ class iosystem col16 = 0xFFFF, truth = true, truth = true, bitmapeditor = 0); static truth IsOnMenu(); + static bool IsInUse(); }; #endif diff --git a/FeLib/Include/felist.h b/FeLib/Include/felist.h index ea9182b25..857ef03d6 100644 --- a/FeLib/Include/felist.h +++ b/FeLib/Include/felist.h @@ -40,9 +40,11 @@ class felist ~felist(); void AddEntry(cfestring&, col16, uint = 0, uint = NO_IMAGE, truth = true); + void SetLastEntryHelp(cfestring Help); void AddDescription(cfestring&, col16 = WHITE); static void SetAllowMouse(bool b); uint Draw(); + uint ScrollToLastPage(bool& JustSelectMoveOnce,bitmap& BackGround,bitmap* Buffer); void SetFirstDrawNoFade(bool b); uint GetMouseSelectedEntry(v2 v2MousePos); void QuickDraw(bitmap*, uint) const; @@ -78,14 +80,24 @@ class felist void SetEntryDrawer(entrydrawer What) { EntryDrawer = What; } static truth isAnyFelistCurrentlyDrawn(); static bool PrepareListItemAltPosBackground(blitdata& rB,bool bAltPosFullBkg); + static void SetListItemAltPosMinY(int iY); static v2 GetCurrentListSelectedItemPos(){return v2SelectedPos;}; static void SetSelectedBkgColor(col16 col){colSelectedBkg=col;} void SetOriginalPos(v2 pos){v2OriginalPos = pos;}; + void ClearFilter(); private: + void PrepareToReturn(); + void ApplyFilter(); + void UpdateFilterDesc(); + void SetFilter(festring Filter); + festring GetFilter(); + uint DrawFiltered(bool& bJustExitTheList); void DrawDescription(bitmap*) const; bool FirstDrawNoFade; std::vector vEntryRect; std::vector Entry; + std::vector EntryBkp; + bool bJustRestoreEntries; std::vector Description; uint PageBegin; uint Maximum; diff --git a/FeLib/Include/graphics.h b/FeLib/Include/graphics.h index a547b165e..68ca890c8 100644 --- a/FeLib/Include/graphics.h +++ b/FeLib/Include/graphics.h @@ -55,7 +55,7 @@ class graphics static void DrawRectangleOutlineAround(bitmap* bmpAt, v2 v2TopLeft, v2 v2Border, col16 color, bool wide); static void BlitDBToScreen(); - static void DrawAboveAll(bitmap* bmpDest); + static void DrawAboveAll(bitmap* bmpBuffer); static void AddDrawAboveAll(drawabove da, int iPriority, const char* desc); static v2 GetRes() { return Res; } diff --git a/FeLib/Include/specialkeys.h b/FeLib/Include/specialkeys.h new file mode 100644 index 000000000..e467e7d8f --- /dev/null +++ b/FeLib/Include/specialkeys.h @@ -0,0 +1,53 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +#ifndef MAIN_INCLUDE_SPECIALKEYS_H_ +#define MAIN_INCLUDE_SPECIALKEYS_H_ + +#include "SDL.h" + +class festring; + +typedef void (*specialkeyhandler)(); + +class specialkeys +{ + public: + enum SKEvent{ + Filter, + ClearStringInput, + CopyToClipboard, + PasteFromClipboard, + FocusedElementHelp, + }; + + static void init(); + + static bool FunctionKeyHandler(SDL_Keycode); + static bool ControlKeyHandler(SDL_Keycode); + static void AddCtrlOrFuncKeyHandler(SDL_Keycode key, specialkeyhandler Handler); + + static cfestring FilterListQuestion(cfestring What); + static void ClearRequest(){Request=-1;} + + static bool ConsumeEvent(SKEvent k){festring fsDummy;return ConsumeEvent(k,fsDummy);} + static bool ConsumeEvent(SKEvent,festring& fsInOut); + static bool IsConsumingEvent(); + static bool IsRequestedEvent(SKEvent e); + static bool HasEvent(){return Request>=0;} + + private: + static int Request; +}; + + +#endif /* MAIN_INCLUDE_SPECIALKEYS_H_ */ diff --git a/FeLib/Include/whandler.h b/FeLib/Include/whandler.h index c5cadbad3..426e251e7 100644 --- a/FeLib/Include/whandler.h +++ b/FeLib/Include/whandler.h @@ -41,8 +41,10 @@ class globalwindowhandler { public: static bool IsKeyPressed(int iSDLScanCode); - static void ResetKeyTimeout(){SetKeyTimeout(0,'.');} + static void ResetKeyTimeout(){SetKeyTimeout(0,iRestWaitKey);} static void CheckKeyTimeout(); + static void SuspendKeyTimeout(); + static void ResumeKeyTimeout(); static truth IsKeyTimeoutEnabled(); static void SetKeyTimeout(int iTimeoutMillis,int iDefaultReturnedKey); static mouseclick ConsumeMouseEvent(); @@ -73,17 +75,28 @@ class globalwindowhandler static void Init(); static void SetQuitMessageHandler(truth (*What)()){ QuitMessageHandler = What; } static ulong UpdateTick() { return Tick = SDL_GetTicks() / 40; } + static void SetFunctionKeyHandler(bool (*What)(SDL_Keycode)){ FunctionKeyHandler = What; } + static void SetControlKeyHandler(bool (*What)(SDL_Keycode)){ ControlKeyHandler = What; } #endif + #ifdef __DJGPP__ static void Init() { } static void SetQuitMessageHandler(truth (*)()) { } static ulong UpdateTick() { return Tick = uclock() * 25 / UCLOCKS_PER_SEC; } #endif + + const static int iRestWaitKey; + private: #ifdef USE_SDL + static int ChkCtrlKey(SDL_Event* Event); static void ProcessMessage(SDL_Event*); + static void ProcessKeyDownMessage(SDL_Event* Event); + static void AddKeyToBuffer(int KeyPressed); static std::vector KeyBuffer; static truth (*QuitMessageHandler)(); + static bool (*FunctionKeyHandler)(SDL_Keycode); + static bool (*ControlKeyHandler)(SDL_Keycode); #endif static truth (*ControlLoop[MAX_CONTROLS])(); static int Controls; diff --git a/FeLib/Source/config.cpp b/FeLib/Source/config.cpp index b00084e70..e18f782a8 100644 --- a/FeLib/Source/config.cpp +++ b/FeLib/Source/config.cpp @@ -140,6 +140,7 @@ void configsystem::Show(void (*BackGroundDrawer)(), truth TruthChange = false; felist List(CONST_S("Which setting do you wish to configure? (* - req. restart)")); + List.AddDescription(CONST_S("")); List.AddDescription(CONST_S("Setting Value")); @@ -155,7 +156,13 @@ void configsystem::Show(void (*BackGroundDrawer)(), { festring Entry = Option[c]->Description; Entry.Capitalize(); - Entry.Resize(60); + int iLim=60; + if(Entry.GetSize()>iLim-1){ + Entry.Resize(iLim-4); + Entry<<"..."; + }else + Entry.Resize(iLim-1); + Entry<<" "; //space between "columns" Option[c]->DisplayValue(Entry); if(fsLastCategory!=Option[c]->fsCategory){ @@ -164,6 +171,7 @@ void configsystem::Show(void (*BackGroundDrawer)(), } List.AddEntry(Entry, LIGHT_GRAY); + List.SetLastEntryHelp(Option[c]->Description); //TODO show all possible values, and each value could have more details, may require cycling thru them all to get all texts... } if(SlaveScreen && ListAttributeInitializer) diff --git a/FeLib/Source/feio.cpp b/FeLib/Source/feio.cpp index 72015a3dd..44b801556 100644 --- a/FeLib/Source/feio.cpp +++ b/FeLib/Source/feio.cpp @@ -58,10 +58,18 @@ waits for keypress. BitmapEditor is a pointer to function that is called during every fade tick. */ +bool bInUse=false; +bool iosystem::IsInUse() +{ + return bInUse; +} + void iosystem::TextScreen(cfestring& Text, v2 Disp, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) { + bInUse=true; + bitmap Buffer(RES, 0); Buffer.ActivateFastFlag(); festring::sizetype c; @@ -110,6 +118,8 @@ void iosystem::TextScreen(cfestring& Text, v2 Disp, else GET_KEY(); } + + bInUse=false; } /* Returns amount of chars cSF in string sSH */ @@ -335,6 +345,8 @@ int iosystem::StringQuestion(festring& Input, truth Fade, truth AllowExit, stringkeyhandler StringKeyHandler) { + bInUse=true; + v2 V(RES.X, 10); ///??????????? bitmap BackUp(V, 0); blitdata B = { &BackUp, @@ -402,8 +414,10 @@ int iosystem::StringQuestion(festring& Input, if(!LastKey) continue; - if(LastKey == KEY_ESC && AllowExit) + if(LastKey == KEY_ESC && AllowExit){ + bInUse=false; return ABORTED; + } if(LastKey == KEY_BACK_SPACE) { @@ -473,6 +487,7 @@ int iosystem::StringQuestion(festring& Input, Input.Resize(LastAlpha + 1); + bInUse=false; return NORMAL_EXIT; } @@ -484,6 +499,8 @@ int iosystem::StringQuestion(festring& Input, long iosystem::NumberQuestion(cfestring& Topic, v2 Pos, col16 Color, truth Fade, truth ReturnZeroOnEsc) { + bInUse=true; + v2 V(RES.X, 10); ///??????????? bitmap BackUp(V, 0); blitdata B = { &BackUp, @@ -539,8 +556,10 @@ long iosystem::NumberQuestion(cfestring& Topic, v2 Pos, col16 Color, if(LastKey == KEY_ESC) { - if(ReturnZeroOnEsc) + if(ReturnZeroOnEsc){ + bInUse=false; return 0; + } break; } @@ -580,6 +599,7 @@ long iosystem::NumberQuestion(cfestring& Topic, v2 Pos, col16 Color, static_cast(LastKey)); } + bInUse=false; return atoi(Input.CStr()); } @@ -600,6 +620,8 @@ long iosystem::ScrollBarQuestion(cfestring& Topic, v2 Pos, col16 Color2, int LeftKey, int RightKey, truth Fade, void (*Handler)(long)) { + bInUse=true; + long BarValue = StartValue; festring Input; truth FirstTime = true; @@ -776,11 +798,15 @@ long iosystem::ScrollBarQuestion(cfestring& Topic, v2 Pos, Input << char(LastKey); } + bInUse=false; return BarValue; } bool AlertConfirmMsg(const char* cMsg,std::vector vfsCritMsgs = std::vector()) -{//TODO this method could be more global +{ + bInUse=true; + + //TODO this method could be more global //TODO calc all line withs to determine the full popup width to not look bad if overflow int iLineHeight=20; v2 v2Border(700,100+(vfsCritMsgs.size()*iLineHeight)); @@ -811,9 +837,12 @@ bool AlertConfirmMsg(const char* cMsg,std::vector vfsCritMsgs = std::v graphics::BlitDBToScreen(); //as the final blit may be from StretchedBuffer - if(GET_KEY() == 'y') + if(GET_KEY() == 'y'){ + bInUse=false; return true; + } + bInUse=false; return false; } diff --git a/FeLib/Source/felist.cpp b/FeLib/Source/felist.cpp index 219e585b5..40228e2fc 100644 --- a/FeLib/Source/felist.cpp +++ b/FeLib/Source/felist.cpp @@ -12,13 +12,17 @@ #include #include +#include +#include +#include "feio.h" #include "felist.h" #include "graphics.h" #include "bitmap.h" #include "whandler.h" #include "rawbit.h" #include "save.h" +#include "specialkeys.h" #include "festring.h" #include "dbgmsgproj.h" @@ -32,6 +36,12 @@ truth felist::isAnyFelistCurrentlyDrawn(){ return FelistCurrentlyDrawn!=NULL; } +int ListItemAltPosBackgroundMinY=0; +void felist::SetListItemAltPosMinY(int iY) +{ + ListItemAltPosBackgroundMinY=iY; +} + bool felist::PrepareListItemAltPosBackground(blitdata& rB,bool bAltPosFullBkg){ if(FelistCurrentlyDrawn==NULL)return false; @@ -45,7 +55,8 @@ bool felist::PrepareListItemAltPosBackground(blitdata& rB,bool bAltPosFullBkg){ // scaled up item pos rB.Dest.X=FelistCurrentlyDrawn->v2OriginalPos.X;//B.Dest.X=5; rB.Dest.Y=rB.Src.Y - v2ItemFinalSize.Y/2; - if(rB.Dest.Y<0)rB.Dest.Y=0; + if(rB.Dest.YSelectable) + ++Selected; + + --Selected; + + uint pb = Selected - Selected % PageLength; + if(PageBegin == pb) + JustRedrawEverythingOnce = true; + else + BackGround.FastBlit(Buffer); + + return pb; +} + +felistentry* RetrieveSelectableEntry(std::vector Entry,uint Selected){ + uint iSel=0; + for(uint i=0;iSelectable)continue; + + if(iSel==Selected) + return Entry[i]; + + iSel++; + } + return NULL; +} + +typedef std::map filtermap; +filtermap FilterMap; + +festring felist::GetFilter() +{ + festring key = Description[0]->String; + filtermap::iterator Iterator = FilterMap.find(key); + return Iterator != FilterMap.end() ? Iterator->second : ""; +} + +void felist::SetFilter(festring Filter) +{ + festring key = Description[0]->String; + filtermap::iterator iter = FilterMap.find(key); + if(iter!=FilterMap.end()) + FilterMap.erase(iter); + FilterMap.insert(std::make_pair(key,Filter)); +} + +void felist::UpdateFilterDesc() +{ + festring Filter=GetFilter(); + + static festring fsF="[Filter '"; + festring fsD = fsF+Filter+"']"; + bool bFound=false; + for(int i=0;iString.CStr()).substr(0,fsF.GetSize())==fsF.CStr()){ + if(Filter.IsEmpty()) + Description.erase(Description.begin()+i); + else + Description[i]->String = fsD; + bFound=true; + break; + } + } + if(!bFound && !Filter.IsEmpty()) + AddDescription(fsD,YELLOW); +} + +void felist::ApplyFilter() +{ + festring Filter=GetFilter(); + + UpdateFilterDesc(); + + if(!Filter.IsEmpty()){ + Entry.clear(); + std::string sFilter=Filter.CStr();DBG1(sFilter); + std::transform(sFilter.begin(), sFilter.end(), sFilter.begin(), ::tolower); + std::string str; + for(int i=0;iSelectable)continue; + + str = EntryBkp[i]->String.CStr(); + std::transform(str.begin(), str.end(), str.begin(), ::tolower); DBG1(str); + if(str.find(sFilter)!=std::string::npos) + Entry.push_back(EntryBkp[i]); + } + DBG3(Filter.CStr(),EntryBkp.size(),Entry.size()); + if(Entry.empty()){ //filter was invalid + Entry=EntryBkp; + SetFilter(""); + UpdateFilterDesc(); + } + }else{ + if(EntryBkp.size()>0) + Entry=EntryBkp; + } +} + +void felist::PrepareToReturn() +{ + UpdateFilterDesc(); + Entry=EntryBkp; //to be ready to proper felist::Empty() with deletion + EntryBkp.clear(); +} + uint felist::Draw() { - uint FlagsChk = Flags; + EntryBkp=Entry; + + specialkeys::ClearRequest(); + + ApplyFilter(); if(Flags & SELECTABLE){ if(PageLength > 26)PageLength=26; //constraint limit from aA to zZ as there is no coded support beyond these keys anyways... @@ -190,7 +317,7 @@ uint felist::Draw() * But this is still a dumb guesser, because it considers all entries will have images. * * The difficulty is because having a fixed page length, even if the contents of each page may differ, - * we are unable to precisely calculate how many entries will fit on each page. + * we are unable to precisely calculate how many entries will fit on each page. TODO right? * * So, opting for the worst case (all are images) is the safest option. */ @@ -199,6 +326,76 @@ uint felist::Draw() } } + for(;;){ + bool bJustExitTheList=false; + uint Return = DrawFiltered(bJustExitTheList); + if(bJustExitTheList){ + PrepareToReturn(); + return Return; + } + + if(Return == ESCAPED || Return == LIST_WAS_EMPTY){ //TODO FELIST_ERROR_BIT? + PrepareToReturn(); + return Return; + } + + if(Return == NOTHING_SELECTED){ //special condition if has filter + if(!GetFilter().IsEmpty()){ + ApplyFilter(); + continue; + }else{ + if(bJustRestoreEntries){ + UpdateFilterDesc(); + Entry=EntryBkp; + continue; + }else{ + PrepareToReturn(); + return NOTHING_SELECTED; + } + } + } + + ////////////////////////// something was chosen /////////////////////// + DBG3(Return,Entry.size(),EntryBkp.size()); + if(!GetFilter().IsEmpty()){ + /** + * the filtered index differs from the original index... + * the matching key will be the entry description/text + */ + + felistentry* fleR = Entry[Return]; + + int iSelB=0; + for(int i=0;iString.CStr()); + if(!EntryBkp[i]->Selectable)continue; + + if(EntryBkp[i]->String==fleR->String){ //TODO there may have 2 items with identical descriptions tho... but user wouldnt be able to distinguish either right? + Return = iSelB; DBG1(Return); + break; + } + + iSelB++; + } + } + + PrepareToReturn(); + return Return; + } + + PrepareToReturn(); + return NOTHING_SELECTED; //currently never reached, safe dummy tho +} + +void felist::ClearFilter() +{ + SetFilter(""); + ApplyFilter(); +} + +uint felist::DrawFiltered(bool& bJustExitTheList) +{ + uint FlagsChk = Flags; + while(Entry.size() && Entry[GetLastEntryIndex()]->String.IsEmpty()) Pop(); @@ -229,7 +426,7 @@ uint felist::Draw() uint c; uint Return, Selectables = 0; - truth JustSelectMoveOnce = false; + truth JustRedrawEverythingOnce = false; for(c = 0; c < Entry.size(); ++c) if(Entry[c]->Selectable) @@ -245,7 +442,6 @@ uint felist::Draw() else PageBegin = 0; - bool bSafeScrollToEnd=false; //page per page bool bWaitKeyUp=false; bool bClearKeyBufferOnce=false; bool bInvM = Flags & INVERSE_MODE; @@ -253,6 +449,9 @@ uint felist::Draw() v2 v2MousePosPrevious=globalwindowhandler::GetMouseLocation(); globalwindowhandler::ConsumeMouseEvent(); //this call is important to clear the last mouse action outside felist int iDrawCount=0; + festring Filter=GetFilter(); + festring fsFilterApplyNew=Filter; + bool bApplyNewFilter=false; for(;;) { if(FlagsChk != Flags)ABORT("flags changed during felist draw %s %s",std::bitset<16>(FlagsChk).to_string().c_str(), std::bitset<16>(Flags).to_string().c_str()); @@ -261,21 +460,21 @@ uint felist::Draw() truth LastEntryVisible = DrawPage(Buffer,&v2FinalPageSize,&vEntryRect);DBGLN; if(FirstDrawNoFade && iDrawCount == 0){ - JustSelectMoveOnce=true; + JustRedrawEverythingOnce=true; } iDrawCount++; if(Flags & FADE) {DBGLN; - if(JustSelectMoveOnce) + if(JustRedrawEverythingOnce) {DBGLN; Buffer->FastBlit(DOUBLE_BUFFER); graphics::BlitDBToScreen(); }else{DBGLN; - Buffer->FadeToScreen(); + Buffer->FadeToScreen();DBGLN; } - JustSelectMoveOnce = false; + JustRedrawEverythingOnce = false; }else{DBGLN; if(Buffer != DOUBLE_BUFFER)ABORT("felist non-fade Buffer != DOUBLE_BUFFER"); graphics::BlitDBToScreen(); @@ -292,17 +491,58 @@ uint felist::Draw() bool bLeftMouseButtonClick=false; bool bMouseButtonClick=false; bool bJustRefreshOnce=false; - if(bSafeScrollToEnd) - Pressed = bInvM ? KEY_PAGE_UP : KEY_PAGE_DOWN; for(;;){ /** * every section here may break the loop and they are prioritized */ - ////////////////////////////// scroll to end by-pass - if(bSafeScrollToEnd) + if( + specialkeys::ConsumeEvent(specialkeys::Filter,fsFilterApplyNew) + || + specialkeys::ConsumeEvent(specialkeys::ClearStringInput,fsFilterApplyNew) + ){ + if(Filter != fsFilterApplyNew){DBGLN; + if(fsFilterApplyNew.IsEmpty()) + bJustRestoreEntries=true; + bApplyNewFilter=true; + }else{ + bJustRefreshOnce=true; + } + break; + }else + if(specialkeys::IsRequestedEvent(specialkeys::CopyToClipboard)){ + if(Flags & SELECTABLE){ + felistentry* fle = RetrieveSelectableEntry(Entry,Selected); + if(fle!=NULL) + specialkeys::ConsumeEvent(specialkeys::CopyToClipboard,fle->String); + }else{ + specialkeys::ClearRequest(); + } + //TODO copy the entiry list if not selectable? nah...? + }else + if(specialkeys::IsRequestedEvent(specialkeys::FocusedElementHelp)){ + festring fs; + felistentry* fle = RetrieveSelectableEntry(Entry,Selected); + if(fle!=NULL){ + if(!fle->Help.IsEmpty()) + fs<Help<<"\n"; + else + fs<String<<"\n"; + fs<<"\n"; + } + fs<< + "[List Help:]\n" + " F1 - show this message\n" + " Ctrl+F - filter entries\n" + " Ctrl+DEL - clear filter\n" + " Home/End/PageUp/PageDown - navigate thru pages\n" + " ESC - exit the list\n" + " SPACE - continue (next page or exit if at last one)\n"; + specialkeys::ConsumeEvent(specialkeys::FocusedElementHelp,fs); + bJustRefreshOnce=true; break; + } /////////////////////////////////////////// MOUSE /////////////////////////////////////// v2 v2MousePos = globalwindowhandler::GetMouseLocation(); @@ -358,7 +598,7 @@ uint felist::Draw() if(bSelChanged){ Selected = iSel; DBG1(iSel); bJustRefreshOnce=true; - JustSelectMoveOnce=true; + JustRedrawEverythingOnce=true; break; } } @@ -371,14 +611,23 @@ uint felist::Draw() if(bClearKeyBufferOnce){ bClearKeyBuffer=true; bClearKeyBufferOnce=false; - } - Pressed = GET_KEY(bClearKeyBuffer);DBG1(Pressed); //see iTimeoutMillis above + }DBGLN; + Pressed = GET_KEY(bClearKeyBuffer);DBG2(Pressed,DefaultAnswer); //see iTimeoutMillis above +// if(specialkeys::HasEvent()){DBGLN; +// bJustRefreshOnce=true; +// break; +// } if(Pressed!=DefaultAnswer) break; - } DBGLN; + if(Pressed == KEY_ESC) // this here grants will be preferred over everything else below + { + Return = ESCAPED; + break; + } + // if(bMouseHovering && !bMouseButtonClick) if(bJustRefreshOnce) continue; @@ -413,23 +662,11 @@ uint felist::Draw() PageBegin -= PageLength; } else - JustSelectMoveOnce = true; + JustRedrawEverythingOnce = true; } else { - for(c = 0, Selected = 0; c < Entry.size(); ++c) - if(Entry[c]->Selectable) - ++Selected; - - --Selected; - - if(PageBegin == Selected - Selected % PageLength) - JustSelectMoveOnce = true; - else - { - BackGround.FastBlit(Buffer); - PageBegin = Selected - Selected % PageLength; - } + PageBegin = ScrollToLastPage(JustRedrawEverythingOnce, BackGround, Buffer); } if(globalwindowhandler::IsLastSDLkeyEventWasKeyUp()) @@ -450,12 +687,12 @@ uint felist::Draw() PageBegin += PageLength; } else - JustSelectMoveOnce = true; + JustRedrawEverythingOnce = true; } else { if(!PageBegin) - JustSelectMoveOnce = true; + JustRedrawEverythingOnce = true; else BackGround.FastBlit(Buffer); @@ -476,7 +713,11 @@ uint felist::Draw() break; } - if(Pressed == KEY_ESC) + if(bApplyNewFilter){DBGLN; + break; + } + + if(Pressed == KEY_ESC) // this here grants will be preferred over everything else below { Return = ESCAPED; break; @@ -493,13 +734,14 @@ uint felist::Draw() if(!bNav && Pressed == KEY_HOME)bNav=true; if(!bNav && Pressed == KEY_END)bNav=true; //TODO ? END key usage is getting complicated, disabled for now: - if(!bNav) {DBGLN; - if(Pressed == KEY_SPACE) //to work stictly as on the help info - if(bInvM ? PageBegin==0 : LastEntryVisible){DBGLN; - Return = NOTHING_SELECTED; - break; - } - } else {DBGLN; + if(Pressed == KEY_SPACE) //to work stictly as on the help info + if(bInvM ? PageBegin==0 : LastEntryVisible){DBGLN; + bJustExitTheList=true; + Return = NOTHING_SELECTED; + break; + } + + if(bNav) { BackGround.FastBlit(Buffer); int iDir = 1; @@ -509,22 +751,18 @@ uint felist::Draw() iDir *= -1; int iPB = PageBegin + iDir*PageLength;DBG1(iPB); - if(iPB<0) //PageBegin is uint ... + if(iPB<0) //BEWARE!!! PageBegin is uint ... iPB=0; /** * overriders * obs.: pgdn and space are default "advance page" action */ - if(LastEntryVisible && bSafeScrollToEnd){DBGLN; - bSafeScrollToEnd=false; - Selected = Selectables-1; - continue; //do nothing - } if(bInvM ? Pressed == KEY_END : Pressed == KEY_HOME) // go to first iPB=0; + bool bSelLast=false; if(bInvM ? Pressed == KEY_HOME : Pressed == KEY_END){DBGLN; // go to last if(Entry.size()<=PageLength){DBGLN; //only one page Selected = Selectables-1; @@ -536,23 +774,24 @@ uint felist::Draw() continue; //do nothing } - bSafeScrollToEnd=true; // will just page down once, as this is the default action, otherwise should `continue;` + DBG6("Before",iPB,Selectables,Selected,PageLength,Entry.size()); + iPB = ScrollToLastPage(JustRedrawEverythingOnce, BackGround, Buffer); DBG6("After",iPB,Selectables,Selected,PageLength,Entry.size()); + bSelLast=true; } // fail safe LAST check - if(iPB >= Selectables){ DBG3("how it happened?",iPB,Selectables); + if(iPB >= Selectables){ DBG6("HowItHappened?",iPB,Selectables,Selected,PageLength,Entry.size()); continue; //do nothing } // apply - PageBegin = iPB; - - DBG3(PageBegin,Pressed,iDir); + PageBegin = iPB; DBG3(PageBegin,Pressed,iDir); - if(Flags & SELECTABLE) - Selected = PageBegin; + if(!bSelLast) + if(Flags & SELECTABLE) + Selected = PageBegin; } - } + };DBGLN; if(!(Flags & FADE)) { @@ -579,6 +818,12 @@ uint felist::Draw() #endif globalwindowhandler::ResetKeyTimeout(); + + if(bApplyNewFilter){ + SetFilter(fsFilterApplyNew); + return NOTHING_SELECTED; + } + return Return; } @@ -590,7 +835,7 @@ bool felist::IsEntryDrawingAtValidPos(bitmap* Buffer,v2 pos){ } truth felist::DrawPage(bitmap* Buffer, v2* pv2FinalPageSize, std::vector* pvEntryRect) const -{ +{ DBGSV2(Pos); uint LastFillBottom = Pos.Y + 23 + Description.size() * 10; DrawDescription(Buffer); @@ -722,8 +967,11 @@ truth felist::DrawPage(bitmap* Buffer, v2* pv2FinalPageSize, std::vectorFill(iTLX, LastFillBottom, iWidth, iHeight=30, BackColor); + int iPg = (PageBegin/PageLength)+1; + int iPgTot = Entry.size()/PageLength + (Entry.size()%PageLength>0 ? 1 : 0); + if(iPgTot==0)iPgTot=1; FONT->Printf(Buffer, v2(Pos.X + 13, LastFillBottom + 10), WHITE, - "- Press PgUp/PgDn/Home/End or SPACE to continue, ESC to exit -"); + "- Page %d/%d (Press F1 to show help info) -",iPg,iPgTot); LastFillBottom += 30; } else @@ -862,6 +1110,11 @@ void felist::AddEntry(cfestring& Str, col16 Color, } } +void felist::SetLastEntryHelp(cfestring Help) +{ + Entry[Entry.size()-1]->Help=Help; +} + void felist::Save(outputfile& SaveFile) const { SaveFile << Entry << Maximum << Selected; diff --git a/FeLib/Source/graphics.cpp b/FeLib/Source/graphics.cpp index 841105049..84dcef848 100644 --- a/FeLib/Source/graphics.cpp +++ b/FeLib/Source/graphics.cpp @@ -586,11 +586,10 @@ struct drawaboveentry{ const char* desc; //to help on development }; std::vector vDrawabove; -void graphics::DrawAboveAll(bitmap* bmpDest) +void graphics::DrawAboveAll(bitmap* bmpBuffer) { - for(int i=0;i +#include +#include +#include +#include +#include + +#include "bitmap.h" +#include "feio.h" +#include "error.h" +#include "festring.h" +#include "graphics.h" +#include "rawbit.h" +#include "specialkeys.h" +#include "whandler.h" + +#include "dbgmsgproj.h" + +/** + * They modify current behavior of anything that accepts such modifications, + * or + * they may override whatever is happening with something else. + */ + +//#undef REQ +//#define REQ(name) \ +// bool b##name##Request=false; \ +// void specialkeys::Clear##name##Request(){b##name##Requested=false;} +//REQ(Filter); +//REQ(FocusedElementHelp); +//REQ(CopyToClipboard); +//REQ(PasteFromClipboard); + +cfestring specialkeys::FilterListQuestion(cfestring what) +{ + festring What=what; + + v2 pos = v2(16, 6); + static festring Topic = "Type this list filter:"; + + int R = iosystem::StringQuestion(What, Topic, pos, WHITE, 0, 30, false /*TODO !bGameIsRunning*/, true, NULL); + + /** + * TODO + * clear the filter text using the background! + * needed when pressing ESC or ENTER and not changing the filter! + * tip: igraph::BlitBackGround(pos, v2(RES.X, 23)); + * also, could may be copy the background from doublebuffer and just paste it back after.... + */ + + // cheap workaround to not look too bad (like input was not accepted) at least + // DOUBLE_BUFFER->Fill(pos,v2(RES.X, 23),BLACK); + FONT->Printf(DOUBLE_BUFFER, pos, BLACK, "%s", Topic.CStr()); + FONT->Printf(DOUBLE_BUFFER, v2(pos.X, pos.Y + 10), BLACK, "%s_", What.CStr()); + + if(R == NORMAL_EXIT){ DBG1(What.CStr()); + return What; + } + + if(R == ABORTED) + return what; + + return cfestring(); +} + +bool specialkeys::IsRequestedEvent(SKEvent e) +{ + return (e==Request); +} + +std::vector afsHelpDialog; +void DrawHelpDialog(bitmap* Buffer) //TODO this kind'o message should be more global to be easier to be used... +{ //TODO create a buffer to not re-draw every loop... unless want to animate it... + if(afsHelpDialog.size()==0)return; + + int iFontLetterWidth=8; + int iLH=15; + + v2 v2Border; + v2Border.Y=afsHelpDialog.size()*iLH+iLH*2; + for(int i=0;iFill(v2TL,v2Border,BLACK); + graphics::DrawRectangleOutlineAround(Buffer, v2TL, v2Border, DARK_GRAY, true); + + v2TL+=v2(iLH,iLH); + + for(int i=0;iPrintf(Buffer, v2(v2TL.X,v2TL.Y+i*iLH), WHITE, "%s", afsHelpDialog[i].CStr()); +} + +bool bConsumingEvent=false; +bool specialkeys::IsConsumingEvent() +{ + return bConsumingEvent; +} + +int specialkeys::Request=-1; +bool specialkeys::ConsumeEvent(SKEvent e,festring& fsInOut){DBGLN; + if(!IsRequestedEvent(e)) + return false; + + bConsumingEvent=true; + + switch(e){ + case ClearStringInput:{ + fsInOut.Empty(); + Request=-1; + };break; + + case Filter:{DBGLN; + globalwindowhandler::SuspendKeyTimeout(); + fsInOut=FilterListQuestion(fsInOut); + globalwindowhandler::ResumeKeyTimeout(); + // bFilterRequest=false; + Request=-1; + };break; + + case CopyToClipboard:{DBGLN; + #ifdef UNIX //TODO for MACOSX can use `pbcopy` see https://github.com/Attnam/ivan/pull/458#discussion_r212823516 + /* TODO + * the executable OS command may allow, thru the input text, any OS commands and that is not good. + * ex.: echo just needs to be broken like: echo -n ''`anyOtherOSCommand`'' |xclip -i //where the input string is "'`anyOtherOSCommand`'" + * so this needs to be done in some other way... + std::ostringstream osStkCmd; + // this will execute an OS command + osStkCmd<<"echo -n '"<0) + fsInOut = buf; + pclose(pipeFile); + }else{ + DBG2("unable to execute popen() with cmd: ",osStkCmd.str()); + } + #endif + //TODO windows (can be hardcoded) + };break; + + case FocusedElementHelp:{DBGLN; + globalwindowhandler::SuspendKeyTimeout(); + + std::stringstream ss(fsInOut.CStr()); + std::string line; + while(std::getline(ss,line,'\n')) + afsHelpDialog.push_back(festring(line.c_str())); + + graphics::BlitDBToScreen();DBGLN; + + GET_KEY(true);DBGLN; + + afsHelpDialog.clear(); + + globalwindowhandler::ResumeKeyTimeout(); + + Request=-1; //it is com + };break; + } + + bConsumingEvent=false; + + return true; +} + +void specialkeys::init() +{ + globalwindowhandler::SetFunctionKeyHandler(specialkeys::FunctionKeyHandler); + globalwindowhandler::SetControlKeyHandler(specialkeys::ControlKeyHandler); + + graphics::AddDrawAboveAll(&DrawHelpDialog,90000,"HelpDialog"); +} + +typedef std::map ckhmap; +ckhmap CkhMap; + +bool specialkeys::FunctionKeyHandler(SDL_Keycode key) +{DBGLN; + switch(key){ //TODO how to not use SDLK_ keys here??? shouldnt anyway???? + case SDLK_F1:DBGLN; + Request=FocusedElementHelp; + return true; + default: + ckhmap::iterator Iterator = CkhMap.find(key); + if(Iterator != CkhMap.end()){ + Iterator->second(); + return true; + } + break; + } + return false; +} + +/** + * add Function or Ctrl+ key handler + */ +void specialkeys::AddCtrlOrFuncKeyHandler(SDL_Keycode key, specialkeyhandler Handler) +{ + ckhmap::iterator Iterator = CkhMap.find(key); + if(Iterator != CkhMap.end()) + ABORT("control key handler already set for key %d",key); + + CkhMap.insert(std::make_pair(key,Handler)); +} + +/** + * Ctrl+FunctionKey is here tho + */ +bool specialkeys::ControlKeyHandler(SDL_Keycode key) +{ + switch(key){ //TODO use SDLK_ keys? + case 'f': + Request=Filter; + return true; + case 'c': + Request=CopyToClipboard; + return true; + case 'v': + Request=PasteFromClipboard; + return true; + case SDLK_DELETE: + Request=ClearStringInput; + return true; + default: + ckhmap::iterator Iterator = CkhMap.find(key); + if(Iterator != CkhMap.end()){ + Iterator->second(); + return true; + } + break; + } + + return false; +} + diff --git a/FeLib/Source/whandler.cpp b/FeLib/Source/whandler.cpp index 1ca48e8db..63522da18 100644 --- a/FeLib/Source/whandler.cpp +++ b/FeLib/Source/whandler.cpp @@ -140,6 +140,8 @@ int globalwindowhandler::ReadKey() std::vector globalwindowhandler::KeyBuffer; truth (*globalwindowhandler::QuitMessageHandler)() = 0; +bool (*globalwindowhandler::FunctionKeyHandler)(SDL_Keycode) = 0; +bool (*globalwindowhandler::ControlKeyHandler)(SDL_Keycode) = 0; void globalwindowhandler::Init() { @@ -273,8 +275,10 @@ int FrameSkipOrDraw(){ //TODO could this be simplified? } } +const int globalwindowhandler::iRestWaitKey = '.'; + int iTimeoutDelay=0; // must init with 0 -int iTimeoutDefaultKey; +int iTimeoutDefaultKey = globalwindowhandler::iRestWaitKey; long keyTimeoutRequestedAt; /** * This is intended to remain active ONLY until the user hits any key. @@ -304,6 +308,16 @@ void globalwindowhandler::CheckKeyTimeout() } } } +int iTimeoutDelayBkp=0; +void globalwindowhandler::SuspendKeyTimeout() +{ + iTimeoutDelayBkp=iTimeoutDelay; + iTimeoutDelay=0; +} +void globalwindowhandler::ResumeKeyTimeout() +{ + iTimeoutDelay=iTimeoutDelayBkp; +} float globalwindowhandler::GetFPS(bool bInsta){ if(bInsta)return fInstaFPS; @@ -365,33 +379,28 @@ int globalwindowhandler::GetKey(truth EmptyBuffer) } else { - bool bHasEvents=PollEvents(&Event)>0; -// bool bHasEvents=false; -// while(SDL_PollEvent(&Event)){ -// ProcessMessage(&Event); -// bHasEvents=true; -// } - - if(!bHasEvents) - { - bool bPlay=true; - + bool bHasFocus=false; #if SDL_MAJOR_VERSION == 1 - if(bPlay && !(SDL_GetAppState() & SDL_APPACTIVE)) + bHasFocus = SDL_GetAppState() & SDL_APPACTIVE; #else - if(bPlay && - !( SDL_GetWindowFlags(graphics::GetWindow()) & (SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS) ) - ) + bHasFocus = SDL_GetWindowFlags(graphics::GetWindow()) & (SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS); #endif - if(!playInBackground) - bPlay=false; - if(bPlay && Controls==0) - bPlay=false; + bool bPlay=true; + + if(bPlay && !bHasFocus && !playInBackground) + bPlay=false; + + if(bPlay && Controls==0) + bPlay=false; - if(bPlay && !ControlLoopsEnabled) - bPlay=false; + if(bPlay && !ControlLoopsEnabled) + bPlay=false; + + bool bHasEvents=PollEvents(&Event)>0; + if(!bHasEvents) + { if(bPlay) { static ulong LastTick = 0; @@ -425,8 +434,13 @@ int globalwindowhandler::GetKey(truth EmptyBuffer) } else { - SDL_WaitEvent(&Event); - ProcessMessage(&Event); + if(bHasFocus){ + iDelayMS=1000/30; //30 FPS on main menu just to not use too much CPU there. If one day it is animated, lower this properly. + SDL_Delay(iDelayMS); + }else{ + SDL_WaitEvent(&Event);DBGLN; + ProcessMessage(&Event);DBGLN; + } } } } @@ -551,16 +565,185 @@ mouseclick globalwindowhandler::ConsumeMouseEvent() //TODO buffer it? return mcR; } -void globalwindowhandler::ProcessMessage(SDL_Event* Event) +int globalwindowhandler::ChkCtrlKey(SDL_Event* Event) { + if(Event->key.keysym.mod & KMOD_CTRL){ //if CTRL is pressed, user expects something else than the normal key, therefore not permissive + if(ControlKeyHandler!=NULL) + ControlKeyHandler(Event->key.keysym.sym); + return iRestWaitKey; //gum TODO 0 should suffice one day... + }DBGLN; + + return Event->key.keysym.sym; +} + +void globalwindowhandler::ProcessKeyDownMessage(SDL_Event* Event) +{DBG4(Event->key.keysym.sym,Event->text.text[0],Event->key.keysym.mod & KMOD_ALT,Event->key.keysym.mod & KMOD_CTRL); + + bLastSDLkeyEventIsKeyUp=false; + + /** + * Events are splitted between SDL_KEYDOWN and SDL_TEXTINPUT. + * + * All managed events must be explicited, + * so, all keyDown events that will be modified must be handled here, + * all other non modified keyDown events will be handled by SDL_TEXTINPUT event type outside here. + * + * More modifiers also means higher priority. + * + * if one or more modifiers are pressed, + * user expects something else than the normal key, + * therefore wont fill the key buffer + * + * Non handled ctrl+alt+... or ctrl+... or alt+... will be ignored. + * Tho, they may be overriden by the OS and never reach here... + */ + + if((Event->key.keysym.mod & KMOD_CTRL) && (Event->key.keysym.mod & KMOD_ALT)){ + switch(Event->key.keysym.sym) + { + case SDLK_e: + /** + * TODO + * exemplify where this is or can be ever used as tests provided no results on Linux, + * is it the windows Explorer key? if so #ifdef WIN32 should be used... + */ + AddKeyToBuffer('\177'); + break; + } + return; + } + + if(Event->key.keysym.mod & KMOD_CTRL){ + if(ControlKeyHandler!=NULL) //this one was completely externalized + ControlKeyHandler(Event->key.keysym.sym); + return; + }else + if(Event->key.keysym.mod & KMOD_ALT){ + switch(Event->key.keysym.sym) + { + case SDLK_RETURN: + case SDLK_KP_ENTER: + graphics::SwitchMode(); + break; + } + return; + } + + // other special non buffered keys + switch(Event->key.keysym.sym) + { + case SDLK_F1: case SDLK_F2: case SDLK_F3: case SDLK_F4: case SDLK_F5: + case SDLK_F6: case SDLK_F7: case SDLK_F8: case SDLK_F9: case SDLK_F10: + case SDLK_F11: case SDLK_F12: case SDLK_F13: case SDLK_F14: case SDLK_F15: + case SDLK_F16: case SDLK_F17: case SDLK_F18: case SDLK_F19: case SDLK_F20: + case SDLK_F21: case SDLK_F22: case SDLK_F23: case SDLK_F24: + if(FunctionKeyHandler!=NULL) + FunctionKeyHandler(Event->key.keysym.sym); + return; //no buffer + + case SDLK_SYSREQ: + case SDLK_PRINTSCREEN: + if(!ScrshotDirectoryName.IsEmpty()) + DOUBLE_BUFFER->Save(ScrshotNameHandler()); + return; //no buffer + } + + ///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////// MODIFIED KEY BUFFER /////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// int KeyPressed = 0; + switch(Event->key.keysym.sym) + { + case SDLK_RETURN: + case SDLK_KP_ENTER: + // both SDL keys are mixed into KEY_ENTER + KeyPressed = KEY_ENTER; //TODO SDL1? old comment tip or deadCode: Event->key.keysym.unicode; + break; + + case SDLK_DOWN: + case SDLK_KP_2: + KeyPressed = KEY_DOWN + 0xE000; + break; + + case SDLK_UP: + case SDLK_KP_8: + KeyPressed = KEY_UP + 0xE000; + break; + + case SDLK_RIGHT: + case SDLK_KP_6: + KeyPressed = KEY_RIGHT + 0xE000; + break; + + case SDLK_LEFT: + case SDLK_KP_4: + KeyPressed = KEY_LEFT + 0xE000; + break; + + case SDLK_HOME: + case SDLK_KP_7: + KeyPressed = KEY_HOME + 0xE000; + break; + + case SDLK_END: + case SDLK_KP_1: + KeyPressed = KEY_END + 0xE000; + break; + case SDLK_PAGEUP: + case SDLK_KP_9: + KeyPressed = KEY_PAGE_UP + 0xE000; + break; + + case SDLK_KP_3: + case SDLK_PAGEDOWN: + KeyPressed = KEY_PAGE_DOWN + 0xE000; + break; + + case SDLK_KP_5: + KeyPressed = iRestWaitKey; + break; + +#if SDL_MAJOR_VERSION == 2 //TODO there is no ESC on SDL1??? but does SDL1 still compiles? anyone uses it yet??? the same question about DJGPP... + case SDLK_ESCAPE: + case SDLK_BACKSPACE: + KeyPressed = Event->key.keysym.sym; + break; +#endif #if SDL_MAJOR_VERSION == 1 - switch(Event->active.type) + default: + KeyPressed = Event->key.keysym.unicode; + + if(!KeyPressed) + return; +#endif + } + AddKeyToBuffer(KeyPressed); +} + +/** + * buffer of yet non processed commands/textInput + */ +void globalwindowhandler::AddKeyToBuffer(int KeyPressed) +{ DBG1(KeyPressed); + if(KeyPressed==0)return; + + if( std::find(KeyBuffer.begin(), KeyBuffer.end(), KeyPressed) == KeyBuffer.end() ) //prevent dups TODO because of fast key-repeat OS feature? should be the last key on buffer only then + KeyBuffer.push_back(KeyPressed); +} + +void globalwindowhandler::ProcessMessage(SDL_Event* Event) +{ + Uint32 type; +#if SDL_MAJOR_VERSION == 1 + type=(Event->active.type); #else - switch(Event->type) + type=(Event->type); #endif + + switch(type) { + #if SDL_MAJOR_VERSION == 1 case SDL_VIDEOEXPOSE: graphics::BlitDBToScreen(); @@ -576,10 +759,10 @@ void globalwindowhandler::ProcessMessage(SDL_Event* Event) } #endif break; + case SDL_QUIT: if(!QuitMessageHandler || QuitMessageHandler()) exit(0); - return; case SDL_MOUSEBUTTONUP: @@ -589,104 +772,24 @@ void globalwindowhandler::ProcessMessage(SDL_Event* Event) mc.pos.Y=Event->button.y; } break; + case SDL_MOUSEWHEEL: mc.wheelY = Event->wheel.y; break; - case SDL_KEYUP: - bLastSDLkeyEventIsKeyUp=true; - break; - case SDL_KEYDOWN: - bLastSDLkeyEventIsKeyUp=false; - switch(Event->key.keysym.sym) - { - case SDLK_RETURN: - case SDLK_KP_ENTER: - if(Event->key.keysym.mod & KMOD_ALT) - { - graphics::SwitchMode(); - return; - } - else - KeyPressed = KEY_ENTER; //Event->key.keysym.unicode; - - break; - case SDLK_DOWN: - case SDLK_KP_2: - KeyPressed = KEY_DOWN + 0xE000; - break; - case SDLK_UP: - case SDLK_KP_8: - KeyPressed = KEY_UP + 0xE000; - break; - case SDLK_RIGHT: - case SDLK_KP_6: - KeyPressed = KEY_RIGHT + 0xE000; - break; - case SDLK_LEFT: - case SDLK_KP_4: - KeyPressed = KEY_LEFT + 0xE000; - break; - case SDLK_HOME: - case SDLK_KP_7: - KeyPressed = KEY_HOME + 0xE000; - break; - case SDLK_END: - case SDLK_KP_1: - KeyPressed = KEY_END + 0xE000; - break; - case SDLK_PAGEUP: - case SDLK_KP_9: - KeyPressed = KEY_PAGE_UP + 0xE000; - break; - case SDLK_KP_3: - case SDLK_PAGEDOWN: - KeyPressed = KEY_PAGE_DOWN + 0xE000; - break; - case SDLK_KP_5: - KeyPressed = '.'; - break; - case SDLK_SYSREQ: - case SDLK_PRINTSCREEN: - if(!ScrshotDirectoryName.IsEmpty()) - { - DOUBLE_BUFFER->Save(ScrshotNameHandler()); - } - return; -#if SDL_MAJOR_VERSION == 2 - /* event are now splitted between SDL_KEYDOWN and SDL_TEXTINPUT, - all managed events must be explicited */ - case SDLK_ESCAPE: - case SDLK_BACKSPACE: - KeyPressed = Event->key.keysym.sym; - break; +#if SDL_MAJOR_VERSION == 2 //BEFORE key up or down + case SDL_TEXTINPUT: DBG2(Event->key.keysym.sym,Event->text.text[0]); + AddKeyToBuffer(Event->text.text[0]); + break; #endif - case SDLK_e: - if(Event->key.keysym.mod & KMOD_ALT - && (Event->key.keysym.mod & KMOD_LCTRL - || Event->key.keysym.mod & KMOD_RCTRL)) - { - KeyPressed = '\177'; - break; - } - default: -#if SDL_MAJOR_VERSION == 1 - KeyPressed = Event->key.keysym.unicode; -#endif + case SDL_KEYUP: DBGLN; + bLastSDLkeyEventIsKeyUp=true; + break; - if(!KeyPressed) - return; - } - if( std::find(KeyBuffer.begin(), KeyBuffer.end(), KeyPressed) == KeyBuffer.end() ) - KeyBuffer.push_back(KeyPressed); - break; -#if SDL_MAJOR_VERSION == 2 - case SDL_TEXTINPUT: - KeyPressed = Event->text.text[0]; - if( std::find(KeyBuffer.begin(), KeyBuffer.end(), KeyPressed) == KeyBuffer.end() ) - KeyBuffer.push_back(KeyPressed); -#endif + case SDL_KEYDOWN: DBGLN; + ProcessKeyDownMessage(Event); + break; } } diff --git a/INSTALL b/INSTALL index 091dd02f1..0a883f333 100644 --- a/INSTALL +++ b/INSTALL @@ -29,7 +29,7 @@ To install IVAN to a custom prefix, pass the additional flag (In particular, simply doing `make DESTDIR=/your/prefix/path install` doesn't work because IVAN needs the prefix information at build-time.) -If config options toggle is too fast, you can add this flag -DFELIST_WAITKEYUP, +If config options toggle is too fast, you can add this flag '-DFELIST_WAITKEYUP', like this: CMAKE_CXX_FLAGS="-DFELIST_WAITKEYUP -DWIZARD" (you may chose what flags to add independently of each other). diff --git a/Main/Include/actions.h b/Main/Include/actions.h index 88313f8f3..505778b34 100644 --- a/Main/Include/actions.h +++ b/Main/Include/actions.h @@ -104,14 +104,18 @@ ACTION(go, action) virtual void Handle(); int GetDirection() const { return Direction; } void SetDirection(int What) { Direction = What; } + void SetDirectionFromRoute(); + void SetRoute(std::vector What){RouteGoOn=What;}; truth IsWalkingInOpen() const { return WalkingInOpen; } void SetIsWalkingInOpen(truth What) { WalkingInOpen = What; } + bool IsRouteMode(){return RouteGoOn.size()>0;} virtual truth TryDisplace(); virtual cchar* GetDescription() const; virtual truth ShowEnvironment() const { return false; } protected: int Direction; truth WalkingInOpen; + std::vector RouteGoOn; }; ACTION(study, action) diff --git a/Main/Include/bugworkaround.h b/Main/Include/bugworkaround.h index 095ac9815..0849644c0 100644 --- a/Main/Include/bugworkaround.h +++ b/Main/Include/bugworkaround.h @@ -1,3 +1,15 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + #ifndef MAIN_INCLUDE_BUGWORKAROUND_H_ #define MAIN_INCLUDE_BUGWORKAROUND_H_ @@ -6,31 +18,42 @@ struct bugWorkaroundDupPlayerCharItem{ character* Char; item* it; }; -class bugWorkaroundDupPlayer{ +class bugfixdp{ public: - static character* BugWorkaroundDupPlayer(character* CharAsked, v2 v2AskedPos); + static void init(); + static character* ValidatePlayerAt(square* sqr); + static bool IsFixing(); + private: + static character* BugWorkaroundDupPlayer(); + static void DevConsCmd(std::string strCmdParams); + static void GatherAllItemInLevel(); - static void ItemWork(character* Char, item* it, bool bFix, const char* cInfo, std::vector* pvItem,bool bSendToHell); + static bool ItemWork(character* Char, item* it, bool bFix, const char* cInfo, std::vector* pvItem,bool bSendToHell); static void FixPlayerDupInv(character* CharChk); - static void CharEquipmentsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); - static void CharInventoryWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); - static void CharBodypartsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); - static void CharAllItemsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); + static int TrapsWork(); + static int CharEquipmentsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); + static int CharInventoryWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); + static int CharBodypartsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); + static int CharAllItemsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem=NULL); static void CharAllItemsInfo(character* CharAsked); static void CharAllItemsCollect(character* CharAsked,std::vector* pvItem); + static character* FindByPlayerID1(v2 ReqPosL,bool bAndFixIt); + static std::vector FindByPlayerFlag(); + static std::vector FindCharactersOnLevel(bool bOnlyPlayers=false); static bool ScanLevelForCharactersAndItemsWork(item*, bool, bool, std::vector*); static void CollectAllItemsOnLevel(std::vector* pvAllItemsOnLevel); static void CollectAllCharactersOnLevel(std::vector* pvCharsOnLevel); static void CollectAllCharactersAndItemsOnLevel(std::vector* pvAllCharAndOrItemsInLevel); static bool FindDupItemOnLevel(item* itWork, bool bIgnoreBodyParts, bool bAbortOnMultiples); + static void ValidateFullLevel(); + static void DupPlayerFix(character*); - static void AlertConfirmFixMsg(const char* cMsg, bool bAbortIfNot); -// static bool isItemOnVector(std::vector* pv, item* e){return (std::find(pv->begin(), pv->end(), e) != pv->end());} -// static bool isCharOnVector(std::vector* pv, character* e){return (std::find(pv->begin(), pv->end(), e) != pv->end());} - static bool Accepted; + static void DrawAlertConfirmFix(bitmap* Buffer); + static bool AlertConfirmFixMsg(const char* cMsg); + static bool IsAlertConfirmFixMsgDraw(); }; diff --git a/Main/Include/char.h b/Main/Include/char.h index f8a362596..61f69ff2b 100644 --- a/Main/Include/char.h +++ b/Main/Include/char.h @@ -421,6 +421,7 @@ class character : public entity, public id virtual void SwitchToDig(item*, v2) { } virtual void SetRightWielded(item*) { } virtual void SetLeftWielded(item*) { } + truth IsAboveUsefulItem(); void GoOn(go*, truth = false); virtual truth CheckKick() const; virtual int OpenMultiplier() const { return 2; } diff --git a/Main/Include/command.h b/Main/Include/command.h index e83a229a7..1e69478c6 100644 --- a/Main/Include/command.h +++ b/Main/Include/command.h @@ -46,8 +46,11 @@ class commandsystem static void SaveSwapWeapons(outputfile& SaveFile); static void LoadSwapWeapons(inputfile& SaveFile); static void ClearSwapWeapons(); + static std::vector GetRouteGoOnCopy(); private: static truth Apply(character*); + static truth ApplyWork(character* Char,item* itOverride=NULL); + static truth ApplyAgain(character* Char); static truth Close(character*); static truth Eat(character*); static truth Drink(character*); @@ -83,6 +86,7 @@ class commandsystem static truth Rest(character*); static truth Sit(character*); static truth ShowMap(character*); + static truth ShowMapWork(character* Char,v2* pv2ChoseLocation=NULL); static truth Go(character*); static truth ShowConfigScreen(character*); static truth ScrollMessagesDown(character*); @@ -110,6 +114,8 @@ class commandsystem static truth LevelTeleport(character*); static truth Possess(character*); static truth Polymorph(character*); +#else + static truth DevConsCmd(character* Char); #endif static truth ToggleRunning(character*); static truth IssueCommand(character*); diff --git a/Main/Include/confdef.h b/Main/Include/confdef.h index 0c64a0cb5..2628b94b0 100644 --- a/Main/Include/confdef.h +++ b/Main/Include/confdef.h @@ -703,6 +703,9 @@ #define BIRCH 22 #define TEAK 23 #define DWARF_BIRCH 24 +#define FORGE 26 +#define FURNACE 27 +#define WORK_BENCH 32 #define SNOW_BOULDER 4 diff --git a/Main/Include/definesvalidator.h b/Main/Include/definesvalidator.h index c26fb6fb0..d0306b671 100644 --- a/Main/Include/definesvalidator.h +++ b/Main/Include/definesvalidator.h @@ -8,8 +8,26 @@ #ifndef _DEFINESVALIDATOR_H_ #define _DEFINESVALIDATOR_H_ -class definesvalidator{ public: static void Validate() { - +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" + +class definesvalidator{ + + public: + static void init(); + static void DevConsCmd(std::string); + static void GenerateDefinesValidator(std::string); + + static void Validate() { std::stringstream ssErrors; std::bitset<32> bsA, bsB; @@ -4534,6 +4552,14 @@ class definesvalidator{ public: static void Validate() { #endif +#ifdef INGOT // DO NOT MODIFY! + bsA = 2; + bsB = INGOT; + if(bsA!=bsB) + ssErrors << "Defined INGOT with value 2 from .dat file mismatches hardcoded c++ define value of " << INGOT << "!" << std::endl; +#endif + + #ifdef INK // DO NOT MODIFY! bsA = 16396; bsB = INK; @@ -8646,6 +8672,14 @@ class definesvalidator{ public: static void Validate() { #endif +#ifdef VIAL // DO NOT MODIFY! + bsA = 1; + bsB = VIAL; + if(bsA!=bsB) + ssErrors << "Defined VIAL with value 1 from .dat file mismatches hardcoded c++ define value of " << VIAL << "!" << std::endl; +#endif + + #ifdef VITRELLOY // DO NOT MODIFY! bsA = 4191; bsB = VITRELLOY; diff --git a/Main/Include/devcons.h b/Main/Include/devcons.h new file mode 100644 index 000000000..7630da669 --- /dev/null +++ b/Main/Include/devcons.h @@ -0,0 +1,36 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +#ifndef MAIN_INCLUDE_DEVCONS_H_ +#define MAIN_INCLUDE_DEVCONS_H_ + +#include + +#include "festring.h" + +#define DEVCMDMSG(fmt,x...) ADD_MESSAGE(" > " fmt,x); + +typedef void (*callcmd)(std::string); + +class devcons{ + static callcmd Find(std::string strCmd); + static void Help(std::string strFilter); + static void runCommand(festring fsCmd); + public: + static void Init(); + static void OpenCommandsConsole(); + static void AddDevCmd(festring fsCmd, callcmd Call, festring fsHelp=festring(), bool bWizardModeOnly=false); + static void SetVar(std::string strParams); + static float GetVar(int iIndex,float fDefaultIf0); +}; + +#endif /* MAIN_INCLUDE_DEVCONS_H_ */ diff --git a/Main/Include/game.h b/Main/Include/game.h index 622a9c279..8b255cfd7 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -288,12 +288,12 @@ class game static v2 PositionQuestion(cfestring&, v2, positionhandler = 0, positionkeyhandler = 0, truth = true); static void LookHandler(v2); static int AskForKeyPress(cfestring&); + static bool IsQuestionMode(); static truth AnimationController(); static gamescript* GetGameScript() { return GameScript; } static void InitScript(); static valuemap& GetGlobalValueMap() { return GlobalValueMap; } static void InitGlobalValueMap(); - static void GenerateDefinesValidator(bool bValidade); static void TextScreen(cfestring&, v2 = ZERO_V2, col16 = 0xFFFF, truth = true, truth = true, bitmapeditor = 0); static void SetCursorPos(v2 What) { CursorPos = What; } static truth DoZoom() { return Zoom; } @@ -327,6 +327,10 @@ class game static void UpdatePlayerAttributeAverage(); static void CallForAttention(v2, int); static character* SearchCharacter(ulong); + static std::vector GetAllCharacters(); + static characteridmap GetCharacterIDMapCopy(); + static std::vector GetAllItems(); + static itemidmap GetItemIDMapCopy(); static item* SearchItem(ulong); static entity* SearchTrap(ulong); static void AddCharacterID(character*, ulong); @@ -366,6 +370,7 @@ class game static truth MassacreListsEmpty(); static void PlayVictoryMusic(); static void PlayDefeatMusic(); + static void SetMapNote(lsquare* lsqrN,festring What); static bool ToggleDrawMapOverlay(); static void SetDrawMapOverlay(bool b); static void RefreshDrawMapOverlay(); @@ -373,6 +378,8 @@ class game static void DrawMapNotesOverlay(bitmap* =NULL); static lsquare* GetHighlightedMapNoteLSquare(); static bool ToggleShowMapNotes(); + static bool CheckAddAutoMapNote(square* =NULL); + static int CheckAutoPickup(square* sqr = NULL); static int RotateMapNotes(); static char MapNoteToken(); @@ -481,7 +488,7 @@ class game static int getDefaultItemsListWidth(){ return iListWidth; } static void AddDebugDrawOverlayFunction(dbgdrawoverlay ddo){vDbgDrawOverlayFunctions.push_back(ddo);} static int GetCurrentDungeonTurnsCount(){return iCurrentDungeonTurn;} - static int GetSaveFileVersion(); + static int GetSaveFileVersionHardcoded(); static void ValidateCommandKeys(char Key1,char Key2,char Key3); private: static void UpdateCameraCoordinate(int&, int, int, int); diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index 8c96611f4..05efa70f1 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -29,18 +29,16 @@ class ivanconfig static long GetShowItemsAtPlayerSquare(){ return ShowItemsAtPlayerSquare.Value; } static long GetStartingWindowWidth() { return iStartingWindowWidth; } static long GetStartingWindowHeight() { return iStartingWindowHeight; } - static int GetBugWorkaroundDupPlayer() { return BugWorkaroundDupPlayer.Value; } static long GetFrameSkip() { return FrameSkip.Value; } static long GetGoOnStopMode() { return GoOnStopMode.Value; } static long GetHoldPosMaxDist() { return HoldPosMaxDist.Value; } - static truth IsSavegameSafely(){ return SavegameSafely.Value; } static truth IsAllowImportOldSavegame(){ return AllowImportOldSavegame.Value; } static long GetAltSilhouette() { return AltSilhouette.Value; } static truth IsHideWeirdHitAnimationsThatLookLikeMiss(){return HideWeirdHitAnimationsThatLookLikeMiss.Value;} - static truth IsGenerateDefinesValidator(){return GenerateDefinesValidator.Value;} static int GetAltSilhouettePreventColorGlitch(){return AltSilhouettePreventColorGlitch.Value;} static int GetShowMap(){return ShowMap.Value;} static truth IsShowMapAtDetectMaterial() { return ShowMapAtDetectMaterial.Value; } + static truth IsTransparentMapLM() { return TransparentMapLM.Value; } static truth IsWaitNeutralsMoveAway() { return WaitNeutralsMoveAway.Value; } static truth IsEnhancedLights() { return EnhancedLights.Value; } static int GetMemorizeEquipmentMode() { return MemorizeEquipmentMode.Value; } @@ -52,6 +50,7 @@ class ivanconfig static truth GetAutoDropLeftOvers() { return AutoDropLeftOvers.Value; } static truth GetLookZoom() { return LookZoom.Value; } static truth IsXBRZScale() { return XBRZScale.Value; } + static truth IsAutoPickupThrownItems() { return AutoPickupThrownItems.Value; } static truth IsAltAdentureInfo() { return AltAdentureInfo.Value; } static int GetXBRZSquaresAroundPlayer() { return XBRZSquaresAroundPlayer.Value; } static int GetStartingDungeonGfxScale() { return iStartingDungeonGfxScale; } @@ -143,7 +142,6 @@ class ivanconfig static void ScalingQualityDisplayer(const cycleoption*, festring&); static truth GraphicsScaleChangeInterface(cycleoption*); static void GraphicsScaleChanger(cycleoption*, long); - static void BugWorkaroundDupPlayerDisplayer(const cycleoption* O, festring& Entry); static void FullScreenModeChanger(truthoption*, truth); #endif @@ -160,8 +158,6 @@ class ivanconfig static void SilhouetteScaleChanger(cycleoption*, long); static void SaveGameSortModeChanger(cycleoption* O, long What); static void XBRZScaleChanger(truthoption*, truth); - static void SavegameSafelyChanger(truthoption* O, truth What); - static void GenerateDefinesValidatorChanger(truthoption* O, truth What); static void ContrastHandler(long); static void VolumeHandler(long); static void BackGroundDrawer(); @@ -186,13 +182,12 @@ class ivanconfig static numberoption FrameSkip; static truthoption ShowFullDungeonName; static truthoption AllowImportOldSavegame; - static truthoption SavegameSafely; static cycleoption ShowItemsAtPlayerSquare; static truthoption HideWeirdHitAnimationsThatLookLikeMiss; - static truthoption GenerateDefinesValidator; static cycleoption AltSilhouettePreventColorGlitch; static cycleoption ShowMap; static truthoption ShowMapAtDetectMaterial; + static truthoption TransparentMapLM; static truthoption WaitNeutralsMoveAway; static truthoption EnhancedLights; @@ -202,6 +197,7 @@ class ivanconfig static truthoption AutoDropLeftOvers; static truthoption LookZoom; static truthoption XBRZScale; + static truthoption AutoPickupThrownItems; static cycleoption SaveGameSortMode; static cycleoption DistLimitMagicMushrooms; @@ -241,7 +237,6 @@ class ivanconfig static truthoption PlaySounds; static truthoption ShowTurn; - static cycleoption BugWorkaroundDupPlayer; static truthoption AllowMouseOnFelist; }; diff --git a/Main/Include/item.h b/Main/Include/item.h index 6b2a14ab6..7f3a378f8 100644 --- a/Main/Include/item.h +++ b/Main/Include/item.h @@ -264,6 +264,9 @@ class item : public object virtual truth IsExplosive() const { return false; } virtual void SetLabel(cfestring& What); virtual cfestring& GetLabel() const { return label; } + bool HasTag(char); + void SetTag(char); + void ClearTag(char tag); virtual void AddName(festring&, int) const; virtual void AddName(festring& a, int b, int c) const {object::AddName(a,b,c);} //required because of AddName(festring&,int) virtual void Save(outputfile&) const; diff --git a/Main/Include/ivandef.h b/Main/Include/ivandef.h index 2a4042dfa..7f8971f2c 100644 --- a/Main/Include/ivandef.h +++ b/Main/Include/ivandef.h @@ -971,6 +971,7 @@ cv2 SILHOUETTE_SIZE(48, 64); // it is TILE_SIZE*3,TILE_SIZE*4 tho.. #define CAN_BE_DESTROYED 16 #define IS_VALUABLE 32 #define CAN_BE_MIRRORED 64 +#define CAN_BE_DETECTED 128 /* NameFlags */ #define USE_AN 1 diff --git a/Main/Include/proto.h b/Main/Include/proto.h index 113140077..d965863f5 100644 --- a/Main/Include/proto.h +++ b/Main/Include/proto.h @@ -77,7 +77,8 @@ class protosystem static character* CreateMonster(int = 1, int = 999999, int = 0); static character* CreateMonster(cfestring&, int = 0, truth = true); static item* CreateItem(cfestring&, truth = true); - static material* CreateMaterial(cfestring&, long = 0, truth = true); + static material* CreateMaterial(cfestring&, long = 0, truth = true, truth = false); + static material* CreateMaterialForDetection(cfestring& What); static void CreateEveryNormalEnemy(charactervector&); #ifdef WIZARD static void CreateEveryCharacter(charactervector&); diff --git a/Main/Source/actions.cpp b/Main/Source/actions.cpp index 70cf01dea..d97127c77 100644 --- a/Main/Source/actions.cpp +++ b/Main/Source/actions.cpp @@ -338,19 +338,37 @@ void dig::Terminate(truth Finished) void go::Save(outputfile& SaveFile) const { action::Save(SaveFile); - SaveFile << Direction << WalkingInOpen; + SaveFile << Direction << WalkingInOpen << RouteGoOn; } void go::Load(inputfile& SaveFile) { action::Load(SaveFile); - SaveFile >> Direction >> WalkingInOpen; + SaveFile >> Direction >> WalkingInOpen >> RouteGoOn; +} + +void go::SetDirectionFromRoute() +{ + v2 next = RouteGoOn.back(); + RouteGoOn.pop_back(); + SetDirection( + game::GetDirectionForVector( + next-Actor->GetPos())); } void go::Handle() { + bool bRouteMode = IsRouteMode(); + if(bRouteMode) + SetDirectionFromRoute(); + GetActor()->EditAP(GetActor()->GetStateAPGain(100)); // gum solution - GetActor()->GoOn(this); + GetActor()->GoOn(this); + + if(GetActor()->GetAction()) //may have been terminated by GoOn() + if(bRouteMode) //was route mode + if(RouteGoOn.size()==0) //currently is the last step + Terminate(false); } void study::Handle() diff --git a/Main/Source/bugworkaround.cpp b/Main/Source/bugworkaround.cpp index 6f6702b24..dcd78ec76 100644 --- a/Main/Source/bugworkaround.cpp +++ b/Main/Source/bugworkaround.cpp @@ -1,65 +1,159 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + #include "char.h" #include "bitmap.h" #include "bugworkaround.h" +#include "devcons.h" #include "game.h" #include "graphics.h" #include "iconf.h" +#include "message.h" +#include "miscitem.h" #include "rawbit.h" #include "stack.h" +#include "trap.h" #include "whandler.h" #include "dbgmsgproj.h" -bool bugWorkaroundDupPlayer::Accepted=false; -void bugWorkaroundDupPlayer::AlertConfirmFixMsg(const char* cMsg, bool bAbortIfNot=true){ - if(bugWorkaroundDupPlayer::Accepted)return; +bool bAlertConfirmFixMsgDraw=false; +bool bugfixdp::IsAlertConfirmFixMsgDraw() +{ + return bAlertConfirmFixMsgDraw; +} + +festring fsLastAlertConfirmFixMsg; +void bugfixdp::DrawAlertConfirmFix(bitmap* Buffer) +{ + if(!bAlertConfirmFixMsgDraw)return; v2 v2Border(700,100); v2 v2TL(RES.X/2-v2Border.X/2,RES.Y/2-v2Border.Y/2); - DOUBLE_BUFFER->Fill(v2TL,v2Border,RED); - graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, v2TL, v2Border, YELLOW, true); - DOUBLE_BUFFER->Fill(v2TL,v2Border,RED); + Buffer->Fill(v2TL,v2Border,DARK_GRAY); + graphics::DrawRectangleOutlineAround(Buffer, v2TL, v2Border, YELLOW, true); v2TL+=v2(16,16); - FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,v2TL.Y ), YELLOW, "%s", cMsg); - FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,v2TL.Y+16), BLUE, "%s", "Please backup your savegame files."); - FONT->Printf(DOUBLE_BUFFER, v2(v2TL.X,v2TL.Y+32), WHITE, "%s", "Try to fix it? (y/...)"); - graphics::BlitDBToScreen(); //as the final blit may be from StretchedBuffer + int iLH=16; + + FONT->Printf(Buffer, v2(v2TL.X,v2TL.Y), YELLOW, "%s", fsLastAlertConfirmFixMsg.CStr()); + + cchar* c1="Please backup your savegame files."; + v2TL.Y+=iLH; + FONT->Printf(Buffer, v2(v2TL.X,v2TL.Y), BLUE, "%s", c1); + + cchar* c2="Confirm? (y/...)"; + v2TL.Y+=iLH; + FONT->Printf(Buffer, v2(v2TL.X,v2TL.Y), WHITE, "%s", c2); +} + +bool bugfixdp::AlertConfirmFixMsg(const char* cMsg){ + fsLastAlertConfirmFixMsg.Empty(); + fsLastAlertConfirmFixMsg<<"PROBLEM! "< vTrapAll; + for(int iY=0;iYGetYSize();iY++){//if(bChangeItemID)break; + for(int iX=0;iXGetXSize();iX++){//if(bChangeItemID)break; + lsquare* lsqr = game::GetCurrentLevel()->GetLSquare({iX,iY}); + lsqr->FillTrapVector(vTrapAll); + } + } + + std::vector vTrapErase; + for(int i=0;iGetTrapID()==tj->GetTrapID()){ + vTrapErase.push_back(tj); + } + } + } + + for(int i=0;i(t)!=NULL) + ((beartrap*)t)->RemoveFromSlot(); + t->GetLSquareUnder()->RemoveTrap(t); + iTot++; + //TODO anything can be done for gas and mines? + } + + return iTot; } -void bugWorkaroundDupPlayer::CharEquipmentsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ +int bugfixdp::CharEquipmentsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ + int iFixedCount=0; for(int i=0;iGetEquipments();i++) - bugWorkaroundDupPlayer::ItemWork(CharAsked,CharAsked->GetEquipment(i),bFix,"CharFix:Equipped",pvItem,bSendToHell); + if(bugfixdp::ItemWork(CharAsked,CharAsked->GetEquipment(i),bFix,"CharFix:Equipped",pvItem,bSendToHell)) + iFixedCount++; + return iFixedCount; } -void bugWorkaroundDupPlayer::CharInventoryWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ +int bugfixdp::CharInventoryWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ + int iFixedCount=0; stack* stk=CharAsked->GetStack(); //inventory for(int i=0;iGetItems();i++) - bugWorkaroundDupPlayer::ItemWork(CharAsked,stk->GetItem(i),bFix,"CharFix:Inventory",pvItem,bSendToHell); + if(bugfixdp::ItemWork(CharAsked,stk->GetItem(i),bFix,"CharFix:Inventory",pvItem,bSendToHell)) + iFixedCount++; + return iFixedCount; } -void bugWorkaroundDupPlayer::CharBodypartsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ +int bugfixdp::CharBodypartsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ + int iFixedCount=0; for(int i=0;iGetBodyParts();i++) - bugWorkaroundDupPlayer::ItemWork(CharAsked,CharAsked->GetBodyPart(i),bFix,"CharFix:BodyPart",pvItem,bSendToHell); + if(bugfixdp::ItemWork(CharAsked,CharAsked->GetBodyPart(i),bFix,"CharFix:BodyPart",pvItem,bSendToHell)) + iFixedCount++; + return iFixedCount; } -void bugWorkaroundDupPlayer::CharAllItemsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ - bugWorkaroundDupPlayer::CharEquipmentsWork(CharAsked, bFix, bSendToHell, pvItem); - bugWorkaroundDupPlayer::CharInventoryWork (CharAsked, bFix, bSendToHell, pvItem); - bugWorkaroundDupPlayer::CharBodypartsWork (CharAsked, bFix, bSendToHell, pvItem); +int bugfixdp::CharAllItemsWork(character* CharAsked, bool bFix, bool bSendToHell, std::vector* pvItem){ + int iFixedCount=0; + iFixedCount+=bugfixdp::CharEquipmentsWork(CharAsked, bFix, bSendToHell, pvItem); + iFixedCount+=bugfixdp::CharInventoryWork (CharAsked, bFix, bSendToHell, pvItem); + iFixedCount+=bugfixdp::CharBodypartsWork (CharAsked, bFix, bSendToHell, pvItem); + return iFixedCount; } -void bugWorkaroundDupPlayer::CharAllItemsInfo(character* CharAsked){ - bugWorkaroundDupPlayer::CharAllItemsWork(CharAsked, false, false, NULL); +void bugfixdp::CharAllItemsInfo(character* CharAsked){ + bugfixdp::CharAllItemsWork(CharAsked, false, false, NULL); } -void bugWorkaroundDupPlayer::CharAllItemsCollect(character* CharAsked,std::vector* pvItem){ - bugWorkaroundDupPlayer::CharAllItemsWork(CharAsked, false, false, pvItem); +void bugfixdp::CharAllItemsCollect(character* CharAsked,std::vector* pvItem){ + bugfixdp::CharAllItemsWork(CharAsked, false, false, pvItem); } -void bugWorkaroundDupPlayer::CollectAllCharactersOnLevel(std::vector* pvCharsOnLevel) +void bugfixdp::CollectAllCharactersOnLevel(std::vector* pvCharsOnLevel) { std::vector pvAllCharAndOrItemsInLevel; ScanLevelForCharactersAndItemsWork(NULL,true,false,&pvAllCharAndOrItemsInLevel); @@ -81,7 +175,7 @@ void bugWorkaroundDupPlayer::CollectAllCharactersOnLevel(std::vector * it may be filled with duplicated items references (pointers) and IDs, * that's the idea, to look for each for DUPs (or multiples) later. */ -void bugWorkaroundDupPlayer::CollectAllItemsOnLevel(std::vector* pvAllItemsOnLevel) +void bugfixdp::CollectAllItemsOnLevel(std::vector* pvAllItemsOnLevel) { std::vector pvAllCharAndOrItemsInLevel; ScanLevelForCharactersAndItemsWork(NULL,true,false,&pvAllCharAndOrItemsInLevel); @@ -90,17 +184,17 @@ void bugWorkaroundDupPlayer::CollectAllItemsOnLevel(std::vector* pvAllIte pvAllItemsOnLevel->push_back(pvAllCharAndOrItemsInLevel[i].it); } -void bugWorkaroundDupPlayer::CollectAllCharactersAndItemsOnLevel(std::vector* pvAllCharAndOrItemsInLevel) +void bugfixdp::CollectAllCharactersAndItemsOnLevel(std::vector* pvAllCharAndOrItemsInLevel) { ScanLevelForCharactersAndItemsWork(NULL,false,false,pvAllCharAndOrItemsInLevel); } -bool bugWorkaroundDupPlayer::FindDupItemOnLevel(item* itWork, bool bIgnoreBodyParts, bool bAbortOnMultiples) +bool bugfixdp::FindDupItemOnLevel(item* itWork, bool bIgnoreBodyParts, bool bAbortOnMultiples) { return ScanLevelForCharactersAndItemsWork(itWork,bIgnoreBodyParts,bAbortOnMultiples,NULL); } -bool bugWorkaroundDupPlayer::ScanLevelForCharactersAndItemsWork( +bool bugfixdp::ScanLevelForCharactersAndItemsWork( item* itWork, bool bIgnoreBodyParts, bool bAbortOnMultiples, // more than dups @@ -111,27 +205,32 @@ bool bugWorkaroundDupPlayer::ScanLevelForCharactersAndItemsWork( int iPointerMatchCount=0; for(int iY=0;iYGetYSize();iY++){//if(bChangeItemID)break; for(int iX=0;iXGetXSize();iX++){//if(bChangeItemID)break; - lsquare* lsqr = game::GetCurrentLevel()->GetLSquare({iX,iY}); + square* sqr = game::GetCurrentArea()->GetSquare({iX,iY}); std::vector vSqrItems; - stack* stk = lsqr->GetStack(); - for(int i=0;iGetItems();i++){//if(bChangeItemID)break; - vSqrItems.push_back(stk->GetItem(i)); + level* lvl = game::GetCurrentLevel(); + if(lvl!=NULL){ + lsquare* lsqr = game::GetCurrentLevel()->GetLSquare({iX,iY}); - if(pvAllCharAndOrItemsInLevel!=NULL){ - bugWorkaroundDupPlayerCharItem ci; - ci.Char=NULL; - ci.it=stk->GetItem(i); - pvAllCharAndOrItemsInLevel->push_back(ci); + stack* stk = lsqr->GetStack(); + for(int i=0;iGetItems();i++){//if(bChangeItemID)break; + vSqrItems.push_back(stk->GetItem(i)); + + if(pvAllCharAndOrItemsInLevel!=NULL){ + bugWorkaroundDupPlayerCharItem ci; + ci.Char=NULL; + ci.it=stk->GetItem(i); + pvAllCharAndOrItemsInLevel->push_back(ci); + } } } - character* SqrChar = lsqr->GetCharacter(); + character* SqrChar = sqr->GetCharacter(); //if(SqrChar!=NULL && SqrChar!=Char){ if(SqrChar!=NULL){ //w/o skipping the Char, this will check for dups inside self Char inventory! std::vector vCharItems; - bugWorkaroundDupPlayer::CharAllItemsCollect(SqrChar,&vCharItems); + bugfixdp::CharAllItemsCollect(SqrChar,&vCharItems); for(int i=0;iGetPos()),vSqrItems[i],itWork,itWork->GetID(),(SqrChar==NULL?0:SqrChar),(SqrChar==NULL?0:SqrChar->GetID())) + #define DBGSQRITEM(msg) DBG7(msg,DBGAV2(sqr->GetPos()),vSqrItems[i],itWork,itWork->GetID(),(SqrChar==NULL?0:SqrChar),(SqrChar==NULL?0:SqrChar->GetID())) #else #define DBGSQRITEM(msg) #endif @@ -185,7 +284,7 @@ bool bugWorkaroundDupPlayer::ScanLevelForCharactersAndItemsWork( return iDupIDCount>0; } -void bugWorkaroundDupPlayer::ItemWork(character* Char, item* itWork, bool bFix, const char* cInfo, std::vector* pvItem, bool bSendToHell){ +bool bugfixdp::ItemWork(character* Char, item* itWork, bool bFix, const char* cInfo, std::vector* pvItem, bool bSendToHell){ if(itWork!=NULL){ if(pvItem!=NULL)pvItem->push_back(itWork); @@ -204,7 +303,7 @@ void bugWorkaroundDupPlayer::ItemWork(character* Char, item* itWork, bool bFix, } if(!bChangeItemID){ - bChangeItemID = bugWorkaroundDupPlayer::FindDupItemOnLevel(itWork,false,true); + bChangeItemID = bugfixdp::FindDupItemOnLevel(itWork,false,true); } if(bChangeItemID){ @@ -221,138 +320,334 @@ void bugWorkaroundDupPlayer::ItemWork(character* Char, item* itWork, bool bFix, if(bSendToHell){ itWork->SendToHell(); - DBG3("SentToHell:ItemsID",DBGI(itWork->GetID()),itWork); + DBG3("CharFix:SentToHell:ItemsID",DBGI(itWork->GetID()),itWork); } + + return bMakeItConsistent; }else{ DBG6(Char,Char->GetID(),cInfo,"CharFix:ItemID",DBGI(itWork->GetID()),itWork); //some helpful info for comparison and understanding } } + + return false; } -character* bugWorkaroundDupPlayer::BugWorkaroundDupPlayer(character* CharAsked, v2 v2AskedPos){ DBG2(CharAsked,DBGAV2(v2AskedPos)); - bool bAllowPlayerBugFix = ivanconfig::GetBugWorkaroundDupPlayer()!=0; - bool bAllowJustMissingPlayerBugFix = ivanconfig::GetBugWorkaroundDupPlayer()==1; - bool bNewPlayerInstanceShallWin = ivanconfig::GetBugWorkaroundDupPlayer()==3; //==2 is old player instance +void bugfixdp::ValidateFullLevel() +{ + DEVCMDMSG("%s","validate full level against dup stuff"); - if(CharAsked!=NULL && !CharAsked->IsPlayer()){ - DBGCHAR(CharAsked,"CharFix:CharAsked:IsNotThePlayer"); - CharAsked=NULL; //deny invalid character + // validate full level against other possible dup items + std::vector vAllItemsOnLevel; + bugfixdp::CollectAllItemsOnLevel(&vAllItemsOnLevel); + for(int i=0;iGetID(); + DupPlayer->_BugWorkaround_PlayerDup(game::CreateNewCharacterID(DupPlayer));DBGLN; // make it consistent as removing it is crashing (also empties inv) + for(int i=0;iGetEquipments();i++){ // clear equipments too + if(DupPlayer->CanUseEquipment(i)){ + item* it = DupPlayer->GetEquipment(i); + if(it!=NULL){ + DupPlayer->SetEquipment(i,NULL); //this leaves untracked objects in memory. TODO really untracked? +// if(SearchItem(it->GetID())!=NULL){ +// RemoveItemID(it->GetID()); //TODO could such item pointer or ID be still referenced somewhere? +// } + DBG5(DupPlayer,"CharFix:EquipmentRemoved",i,DBGI(it->GetID()),it); + } } } + DupPlayer->RemoveFlags(C_PLAYER);DBGLN; + DupPlayer->SetTeam(game::GetTeam(MONSTER_TEAM));DBGLN; + DupPlayer->SetAssignedName("_DupPlayerBug_");DBGLN; //non immersive naming, shall not be, this bug shall be properly fixed one day. - bugWorkaroundDupPlayer::Accepted=false; //init to ask again if needed, so the user knows what is happening + //TODO add other effects to auto-kill it, poison? spill sulf acid? must gas? polymorph into y.bunny? + DupPlayer->LoseConsciousness(100000,false); //may not fall asleep tho + DupPlayer->SetNP(1); //to die soon at least - character* CharPlayer = game::SearchCharacter(1); - if(CharPlayer==NULL){ - std::vector vCharsOnLevel; - bugWorkaroundDupPlayer::CollectAllCharactersOnLevel(&vCharsOnLevel); - for(int i=0;iGetPos())); + iFixedCount=CharBodypartsWork(DupPlayer,true,false);DBGLN; //bodyparts sent to hell would crash!!! TODO only torso? + DEVCMDMSG("fixed bodyparts %d",iFixedCount); + //BEWARE!!! this leads to crash: DupPlayer->Remove(); - if(vCharsOnLevel[i]->GetID()==1){ - DBGCHAR(vCharsOnLevel[i],"CharWithID=1"); - DBG2("CharFix:CharID1FoundAt",DBGAV2(vCharsOnLevel[i]->GetPos())); - } + DEVCMDMSG("fixed dup player '%s' id=%d/%d 0x%X",DupPlayer->GetName(DEFINITE).CStr(),idOld,DupPlayer->GetID(),DupPlayer); - if(vCharsOnLevel[i]->IsPlayer()){ - CharPlayer=vCharsOnLevel[i]; - DBG2("CharFix:PlayerFoundAt",DBGAV2(CharPlayer->GetPos())); - break; - } - } + DBGCHAR(DupPlayer,"CharFix:CharToBeLost"); +} + +void bugfixdp::init() +{ + devcons::AddDevCmd("FixDupPlayer",bugfixdp::DevConsCmd, + "BugFix DUP player (experimental/slow). Ps.: If the player went missing, it will kickin automatically on loading."); +// "[full] BugFix DUP player (experimental/slow). The dup player may just be the polymorphed, otherwise the full optional mode will try harder to fix it. Ps.: If the player went missing, it will kickin automatically on loading."); +} - if(CharPlayer==NULL)ABORT("Player cant be found anywhere"); //OMG... may be this is pointless, better just fix to prevent such bugs from ever happening... +character* bugfixdp::ValidatePlayerAt(square* sqr) +{ + v2 Pos = sqr->GetPos(); + character* CharAtPos = sqr->GetCharacter(); + + std::vector vPF = FindByPlayerFlag(); + + //very consistent! + if(CharAtPos!=NULL && CharAtPos->IsPlayer() && vPF.size()==1) + if(CharAtPos->GetID()==1 || (CharAtPos->GetPolymorphBackup() && CharAtPos->GetPolymorphBackup()->GetID()==1)) + return CharAtPos; + + //////////////////// there are problems if reaching here //////////////////// + + /* + * TODO + * there is some double-checks/validationRepetition below... + * the flow below could be less confusing if possible... + */ + + character* CharID1 = FindByPlayerID1(Pos,true); //player is already fixed here, before the question below at (*1) + + if(vPF.size()==0 && CharID1==NULL){ //TODO let user chose one NPC to become the player? sounds too messy tho... + // this can't be fixed automatically + ABORT("Player can't be found anywhere! Try to restore a backup if available."); } - DBGCHAR(CharPlayer,"CharFix:CharPlayer"); - if(bAllowJustMissingPlayerBugFix)return CharPlayer; + if(vPF.size()>1){ // FIRST CHECK/FIX! + festring fsMsg; + fsMsg << "Multiple player instances found (x" << vPF.size() << "), try to fix this?"; + if(AlertConfirmFixMsg(fsMsg.CStr())) + return BugWorkaroundDupPlayer(); + /** + * a problem like moving two characters in alternating turns would happen, + * also unable to move between dungeons w/o crashing + */ + ABORT("The game would become inconsistent (prone to crash) w/o fixing this problem '%s'",fsMsg.CStr()); + } - character* CharWins = CharAsked; - character* CharPlayerOld = NULL; - bool bLevelItemsCollected=false; - if(CharPlayer!=CharAsked){ - bugWorkaroundDupPlayer::AlertConfirmFixMsg( - bNewPlayerInstanceShallWin ? - "Duplicated player found. Fix new player instance (experimental)?" : - "Duplicated player found. Restore old player instance (experimental)?" ); + if(vPF.size()==1){ + if(vPF[0]==CharID1){ + festring fsMsg; + fsMsg << "There was some problem but the Player character was found and moved to requested position " << Pos.X<<","<IsPlayer()){ + festring fsMsg; + fsMsg << "Requested location " << Pos.X<<","<GetID()); //actually id 1 - // the new char is not valid and not consistent at char's list yet, so... - game::AddCharacterID(CharAsked,CharAsked->GetID()); //this will "update" the player ID (actually id 1) to the new character making it consistent on the list - } +bool IsPlayerPB(character* C1, character* C2){ + if(C1->GetPolymorphBackup()==C2 && C2->IsPlayer())return true; + if(C2->GetPolymorphBackup()==C1 && C1->IsPlayer())return true; + return false; +} -// // prepare to check for dup item's ids -// bugWorkaroundDupPlayer::GatherAllItemInLevel();DBGLN; -// bLevelItemsCollected=true; +std::vector bugfixdp::FindByPlayerFlag() +{ + return FindCharactersOnLevel(true); +} - //TODO transfer old player items (pointers/objects/instances) to new player instance if they have the same ID? +/** + * tests for the CL_PLAYER flag + * @param bOnlyPlayers + * @return + */ +std::vector bugfixdp::FindCharactersOnLevel(bool bOnlyPlayerFlag) +{ + std::vector v; + for(int iY=0;iYGetYSize();iY++){for(int iX=0;iXGetXSize();iX++){ + square* sqr = game::GetCurrentArea()->GetSquare({iX,iY}); + character* C=sqr->GetCharacter(); + if(C==NULL)continue; + if(!bOnlyPlayerFlag || C->IsPlayer()) + v.push_back(C); + }} + return v; +} - if(bNewPlayerInstanceShallWin){ - // old player's items are consistent (on the list), they will get a new ID and be sent to hell - // new player's items have the same ID of old player's ones, their pointers will become consistent later... - bugWorkaroundDupPlayer::CharEquipmentsWork(CharPlayerOld,true,true);DBGLN; - bugWorkaroundDupPlayer::CharInventoryWork(CharPlayerOld,true,true);DBGLN; - } - CharToBeLost->_BugWorkaround_PlayerDup(game::CreateNewCharacterID(CharToBeLost));DBGLN; // make it consistent as removing it is crashing (also empties inv) - for(int i=0;iGetEquipments();i++){ // clear equipments too - if(CharToBeLost->CanUseEquipment(i)){ - item* it = CharToBeLost->GetEquipment(i); - if(it!=NULL){ - CharToBeLost->SetEquipment(i,NULL); //this leaves untracked objects in memory. TODO really untracked? -// if(SearchItem(it->GetID())!=NULL){ -// RemoveItemID(it->GetID()); //TODO could such item pointer or ID be still referenced somewhere? -// } - DBG5(CharToBeLost,"CharFix:EquipmentRemoved",i,DBGI(it->GetID()),it); - } - } +bool bBufFixDPMode=false; +bool bugfixdp::IsFixing(){return bBufFixDPMode;} + +void bugfixdp::DevConsCmd(std::string strCmdParams) +{ + BugWorkaroundDupPlayer(); + bBufFixDPMode=true; +} + +character* bugfixdp::FindByPlayerID1(v2 ReqPosL,bool bAndFixIt) +{DBGSV2(ReqPosL); + character* CharID1 = game::SearchCharacter(1); //this can ONLY return one char with ID=1 EVER, so there wont be a DUP char with ID=1 on the characters' map + if(CharID1==NULL) + return NULL; + DBGSV2(CharID1->GetPos()); + + character* PBID1=NULL; + character* PPolymL = NULL; + std::vector vCL = FindCharactersOnLevel(); + for(int j=0;jGetPolymorphBackup(); +// if(PB!=NULL && PB==CharID1){ + if(PBtmp!=NULL && PBtmp->GetID()==1){ + PBID1 = PBtmp; + DEVCMDMSG("polymorphed has backup with id=1 ref 0x%X",PBID1); + PPolymL=vCL[j]; + break; } - CharToBeLost->RemoveFlags(C_PLAYER);DBGLN; - CharToBeLost->SetTeam(game::GetTeam(MONSTER_TEAM));DBGLN; - CharToBeLost->SetAssignedName("_DupPlayerBug_");DBGLN; //non immersive naming, shall not be, this bug shall be properly fixed one day. + } -// // prepare to check for dup item's ids -// bugWorkaroundDupPlayer::GatherAllItemInLevel();DBGLN; -// bLevelItemsCollected=true; +// if(bAndFixIt && PBID1!=NULL && PBID1!=CharID1){ +// PPolymL->SetPolymorphBackup(CharID1); +// DEVCMDMSG("fixing PB ref to correct id=1 ref 0x%X",CharID1); +// +// characteridmap Cmap = game::GetCharacterIDMapCopy(); +//// bool b=false; +// for(characteridmap::iterator itr=Cmap.begin();itr!=Cmap.end();itr++){ +// if(itr->second==PBID1){ +//// b=true; +// DEVCMDMSG("ref 0x%X actually has id=%d ",PBID1,itr->first); //TODO remove? send to hell? needed? +//// if(itr->first!=1) +//// game::RemoveCharacterID(itr->first); +// break; +// } +// } +//// if(b){ +//// DEVCMDMSG("inconsistent PBID1 ref 0x%X",PBID1); +//// if(PBID1->GetSquareUnder()!=NULL){ +//// //THIS CRASHES!!! pointing to invalid SquareUnder reference ----> PBID1->Remove(); +//// } +//// //there is no point in sending to hell as is not on consistent map ---> PBID1->SendToHell(); +//// } +// } + + character* CharPlayerOk = NULL; + if(bAndFixIt && PPolymL!=NULL){ +// DEVCMDMSG("killing polymorphed id=%d",PPolymL->GetID()); + DEVCMDMSG("vanishing polymorphed id=%d",PPolymL->GetID()); + PPolymL->SetPolymorphBackup(NULL); + PPolymL->RemoveTraps(); + PPolymL->Remove(); +// PPolymL->Disable(); + PPolymL->SendToHell(); +// PPolymL->Die(NULL,CONST_S(""), FORBID_REINCARNATION|DISALLOW_MSG|DISALLOW_CORPSE); + +// DEVCMDMSG("polymorphed found id=%d",PPolymL->GetID()); +// CharPlayerOk = PPolymL; +// if(bAndFixIt && CharID1->GetSquareUnder()!=NULL){ +//// DEVCMDMSG("keeping the polymorphed id=%d and removing from level the ID1",PPolymL->GetID()); +// DEVCMDMSG("re-applying the polymorph to id=%d",PPolymL->GetID()); +// PPolymL->Remove(); +// CharID1->Polymorph(PPolymL,PPolymL->GetTemporaryStateCounter(POLYMORPHED)); +//// // fix based on character::Polymorph() +//// CharID1->RemoveFlags(C_PLAYER); +//// CharID1->Remove(); //the polymorphed will go back to it later +// } + } +// }else{ + CharPlayerOk = CharID1; + DEVCMDMSG("%s","ID1 will be ok now"); + if(bAndFixIt && CharID1->GetSquareUnder()==NULL){ + DEVCMDMSG("placing the character ID1 at %d,%d",ReqPosL.X,ReqPosL.Y); + CharID1->PutToOrNear(ReqPosL); //place he where expected + } +// } - bugWorkaroundDupPlayer::CharBodypartsWork(CharToBeLost,true,false);DBGLN; - // this leads to crash //CharAsked->Remove(); + if(bAndFixIt) + if(!CharPlayerOk->IsPlayer() || PLAYER!=CharPlayerOk){ + DEVCMDMSG("restoring player reference to id=%d",CharPlayerOk->GetID()); + game::SetPlayer(CharPlayerOk); + } - DBGCHAR(CharToBeLost,"CharFix:CharToBeLost"); + return CharPlayerOk; +} - CharWins = bNewPlayerInstanceShallWin ? CharAsked : CharPlayerOld; +character* bugfixdp::BugWorkaroundDupPlayer(){ + character* CharID1 = game::SearchCharacter(1); //this can ONLY return one char with ID=1 EVER, so there wont be a DUP char with ID=1 on the characters' map + if(CharID1==NULL) + ABORT("Can't find the valid player."); + + std::vector vCL = FindCharactersOnLevel(); DBG1(vCL.size()); + v2 ReqPosL; //TODO default to the entrance (not the exit). Or random? + for(int j=0;jIsPlayer() && vCL[j]->GetSquareUnder()!=NULL){ + ReqPosL=vCL[j]->GetPos(); + break; + } } - // now, grants the valid player has no item issues compared to other items on the level/chars TODO this may create duplicated items? -// if(!bLevelItemsCollected){bugWorkaroundDupPlayer::GatherAllItemInLevel();DBGLN;} - // new player's items will be made consistent if required (added to the list) - bugWorkaroundDupPlayer::CharEquipmentsWork(CharWins,true,false);DBGLN; - bugWorkaroundDupPlayer::CharInventoryWork(CharWins,true,false);DBGLN; + character* CharPlayerOk = FindByPlayerID1(ReqPosL,true); + if(CharPlayerOk==NULL) + ABORT("Unable to fix the valid player."); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////// below is in case the dup player is not consistent on the characters map ///////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + for(int j=0;jGetID(),vCL[j]->IsPlayer(),vCL[j]->GetPolymorphBackup()); + if(vCL[j]==CharPlayerOk) + continue; + if(vCL[j]->GetID()==1) + continue; + if(vCL[j]->IsPlayer() || vCL[j]->GetPolymorphBackup()==CharID1) + DupPlayerFix(vCL[j]); + } - // validate full level against other possible dup items - std::vector vAllItemsOnLevel; - bugWorkaroundDupPlayer::CollectAllItemsOnLevel(&vAllItemsOnLevel); - for(int i=0;ifirst;if(Cid==0)continue; + character* C = itr->second;if(C==NULL)continue; + bool bFound=false; + for(int j=0;jGetPolymorphBackup()==C){ + bFound=true; + break; + } + if(!bFound){ +// game::RemoveCharacterID(Cid); //causes weird crashes elsewhere +// DEVCMDMSG("removed inconsistent character id '%d'",Cid); + DEVCMDMSG("possibly inconsistent character id '%d'",Cid); } } - DBGCHAR(CharWins,"CharFix:CharWins"); - return CharWins; + // last thing is grant player's stuff is consistent + int iFixedCount=0; + iFixedCount=CharEquipmentsWork(CharPlayerOk,true,false);DBGLN; + DEVCMDMSG("fixed player '%s' equipments %d",CharPlayerOk->GetName(DEFINITE).CStr(),iFixedCount); + iFixedCount=CharInventoryWork (CharPlayerOk,true,false);DBGLN; + DEVCMDMSG("fixed player '%s' inventory %d",CharPlayerOk->GetName(DEFINITE).CStr(),iFixedCount); + iFixedCount=TrapsWork(); + DEVCMDMSG("fixed traps %d",iFixedCount); + + // just a final validation, may abort on failure + ValidateFullLevel(); + + return CharPlayerOk; } diff --git a/Main/Source/char.cpp b/Main/Source/char.cpp index a5b1db0aa..e3d0c853a 100644 --- a/Main/Source/char.cpp +++ b/Main/Source/char.cpp @@ -2157,7 +2157,7 @@ void character::Save(outputfile& SaveFile) const SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c]; SaveLinkedList(SaveFile, TrapData); - SaveFile << Action; + SaveFile << Action; DBG1(Action); for(c = 0; c < STATES; ++c) SaveFile << TemporaryStateCounter[c]; @@ -3406,6 +3406,10 @@ void character::GetPlayerCommand() { bool bWaitNeutralMove=false; HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning(), &bWaitNeutralMove); + if(HasActed){ + game::CheckAddAutoMapNote(); + game::CheckAutoPickup(); + } if(!HasActed && bWaitNeutralMove){ //cant access.. HasActed = commandsystem::NOP(this); Key = '.'; //TODO request NOP()'s key instead of this '.' hardcoded here. how? @@ -4217,73 +4221,14 @@ truth character::MoveRandomlyInRoom() return false; } -void character::GoOn(go* Go, truth FirstStep) +truth character::IsAboveUsefulItem() { - v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection())); - lsquare* MoveToSquare[MAX_SQUARES_UNDER]; - int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos() + MoveVector); - - if(!Squares || !CanMoveOn(MoveToSquare[0])) - { - Go->Terminate(false); - return; - } - - uint OldRoomIndex = GetLSquareUnder()->GetRoomIndex(); - uint CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex(); - - if((OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) && !FirstStep) - { - Go->Terminate(false); - return; - } - - for(int c = 0; c < Squares; ++c) - if((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) - || MoveToSquare[c]->IsDangerous(this)) - { - Go->Terminate(false); - return; - } - - int OKDirectionsCounter = 0; - - for(int d = 0; d < GetNeighbourSquares(); ++d) - { - lsquare* Square = GetNeighbourLSquare(d); - - if(Square && CanMoveOn(Square)) - ++OKDirectionsCounter; - } - - if(!Go->IsWalkingInOpen()) - { - if(OKDirectionsCounter > 2) - { - Go->Terminate(false); - return; - } - } - else - if(OKDirectionsCounter <= 2) - Go->SetIsWalkingInOpen(false); - - square* BeginSquare = GetSquareUnder(); - - if(!TryMove(MoveVector, true, game::PlayerIsRunning()) - || BeginSquare == GetSquareUnder() - || (CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) - { - Go->Terminate(false); - return; - } - if(GetStackUnder()->GetVisibleItems(this)) { bool bUseless=false,bTooCheap=false,bEncumbering=false; switch(ivanconfig::GetGoOnStopMode()){ - case 0: Go->Terminate(false); return; + case 0: return true; case 1:bUseless=true;break; case 2:bTooCheap=true;break; case 3:bEncumbering=true;break; @@ -4307,18 +4252,13 @@ void character::GoOn(go* Go, truth FirstStep) vit[i]->IsAppliable(this) || vit[i]->IsZappable(this) || - // bad! vit[i]->IsConsumable() || + // bad! keep as info! vit[i]->IsConsumable() || vit[i]->IsEatable(this) || vit[i]->IsDrinkable(this) || - // bad! vit[i]->AllowEquip() || + // bad! keep as info! vit[i]->AllowEquip() || vit[i]->IsWeapon(this) || vit[i]->IsArmor(this) || //all armor slots -// vit[i]->IsBodyArmor(this) || -// vit[i]->IsHelmet(this) || -// vit[i]->IsGauntlet(this) || -// vit[i]->IsBoot(this) || -// vit[i]->IsBelt(this) || vit[i]->IsAmulet(this) || vit[i]->IsRing(this) || @@ -4333,10 +4273,82 @@ void character::GoOn(go* Go, truth FirstStep) (vit[i]->GetTruePrice()/(vit[i]->GetWeight()/1000.0)) > (iMaxValueless*2) ) ){ + return true; + } + } + } + + return false; +} + +void character::GoOn(go* Go, truth FirstStep) +{ + v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection())); + lsquare* MoveToSquare[MAX_SQUARES_UNDER]; + int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos() + MoveVector); + + if(!Squares || !CanMoveOn(MoveToSquare[0])) + { + Go->Terminate(false); + return; + } + + uint OldRoomIndex = GetLSquareUnder()->GetRoomIndex(); + uint CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex(); + + if(!Go->IsRouteMode()) + if((OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) && !FirstStep) + { + Go->Terminate(false); + return; + } + + for(int c = 0; c < Squares; ++c) + if((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) + || MoveToSquare[c]->IsDangerous(this)) + { + Go->Terminate(false); + return; + } + + if(!Go->IsRouteMode()){ + int OKDirectionsCounter = 0; + + for(int d = 0; d < GetNeighbourSquares(); ++d) + { + lsquare* Square = GetNeighbourLSquare(d); + + if(Square && CanMoveOn(Square)) + ++OKDirectionsCounter; + } + + if(!Go->IsWalkingInOpen()) + { + if(OKDirectionsCounter > 2) + { Go->Terminate(false); return; } } + else + if(OKDirectionsCounter <= 2) + Go->SetIsWalkingInOpen(false); + } + + square* BeginSquare = GetSquareUnder(); + + if(!TryMove(MoveVector, true, game::PlayerIsRunning()) + || BeginSquare == GetSquareUnder() + || (!Go->IsRouteMode() && CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) + { + Go->Terminate(false); + return; + } + + if(IsAboveUsefulItem()) + { + Go->Terminate(false); + return; } game::DrawEverything(); @@ -7123,12 +7135,13 @@ void character::DisplayStethoscopeInfo(character*) const for(int c = 0; c < BodyParts; ++c) { bodypart* BodyPart = GetBodyPart(c); + if(!BodyPart)continue; EntryBP.Empty(); - if(BodyPart && BodyPart->GetMainMaterial()->GetConfig() == GetTorso()->GetMainMaterial()->GetConfig()) + if(BodyPart->GetMainMaterial()->GetConfig() == GetTorso()->GetMainMaterial()->GetConfig()) { BodyPart->GetMainMaterial()->AddName(EntryBP, UNARTICLED); - EntryBP << " "; + EntryBP<<" "; } BodyPart->AddName(EntryBP, UNARTICLED); //this already says the material if differs from torso Info.AddEntry(EntryBP, LIGHT_GRAY); diff --git a/Main/Source/cmdswapweap.cpp b/Main/Source/cmdswapweap.cpp index a936406cc..d0335fb23 100644 --- a/Main/Source/cmdswapweap.cpp +++ b/Main/Source/cmdswapweap.cpp @@ -107,11 +107,14 @@ void commandsystem::SaveSwapWeapons(outputfile& SaveFile) } void commandsystem::LoadSwapWeapons(inputfile& SaveFile) {DBGLN; + ClearSwapWeapons(); //make sure it is always cleaned from memory! + if(game::GetCurrentSavefileVersion()<132) + return; + SaveFile >> reinterpret_cast(iSwapCurrentIndex); int iSize=0; SaveFile >> reinterpret_cast(iSize); DBG2(iSwapCurrentIndex,iSize); - ClearSwapWeapons(); for(int i=0;iGetSquareUnder()==Char->GetSquareUnder()) + cW = colAtPlayerSquare; + else + cW = colNotOnInv; //inaccessible // if(it!=wL && it!=wR && !hasItem(iv,it))cW = colNotOnInv; @@ -395,7 +403,7 @@ truth commandsystem::SwapWeaponsWork(character* Char, int iIndexOverride) if(Arm && it){ std::vector iv; stk->FillItemVector(iv); - if(hasItem(iv,it)){ + if(hasItem(iv,it) || it->GetSquareUnder()==Char->GetSquareUnder()){ it->RemoveFromSlot(); // w/o this line of code (TODO mem gets corrupted?), it will SEGFAULT when saving the game! extremelly hard to track!!! TODO it is hard to track right? h->SetEquipment(awRL[iArm],it); bDidSwap=true; diff --git a/Main/Source/command.cpp b/Main/Source/command.cpp index c1253b533..3d2787818 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -15,6 +15,7 @@ #include "char.h" #include "command.h" #include "database.h" +#include "devcons.h" #include "felist.h" #include "game.h" #include "god.h" @@ -25,6 +26,7 @@ #include "message.h" #include "miscitem.h" #include "room.h" +#include "specialkeys.h" #include "stack.h" #include "team.h" #include "whandler.h" @@ -71,6 +73,7 @@ command* commandsystem::Command[] = /* Sort according to description */ new command(&Apply, "apply", 'a', 'a', 'a', false), + new command(&ApplyAgain, "apply last item again", 'A', 'A', 'A', false), new command(&Talk, "chat", 'C', 'C', 'C', false), new command(&Close, "close", 'c', 'c', 'c', false), new command(&Dip, "dip", '!', '!', '!', false), @@ -114,7 +117,9 @@ command* commandsystem::Command[] = new command(&WieldInRightArm, "wield in right arm", 'w', 'w', 'w', true), new command(&WieldInLeftArm, "wield in left arm", 'W', 'W', 'W', true), #ifdef WIZARD - new command(&WizardMode, "wizard mode activation", '`', '`', '`', true), + new command(&WizardMode, "wizard mode activation (Ctrl+ to access console commands)", '`', '`', '`', true), +#else + new command(&DevConsCmd, "access console commands", '`', '`', '`', true), //works w/o Ctrl in this case #endif new command(&Zap, "zap", 'z', 'z', 'z', false), @@ -144,6 +149,14 @@ command* commandsystem::Command[] = 0 }; +#ifndef WIZARD +truth commandsystem::DevConsCmd(character* Char) +{ + devcons::OpenCommandsConsole(); + return false; +} +#endif + truth commandsystem::IsForRegionListItem(int iIndex){ //see code generator helper script prepareCmdsDescrCode.sh (use cygwin) cchar* str = Command[iIndex]->GetDescription(); if(strcmp(str,"apply")==0)return true; @@ -554,6 +567,8 @@ truth commandsystem::Drop(character* Char) for(uint c = 0; c < ToDrop.size(); ++c) { ToDrop[c]->MoveTo(Char->GetStackUnder()); + if(ivanconfig::IsAutoPickupThrownItems()) + ToDrop[c]->ClearTag('t'); //throw: to avoid auto-pickup } Success = true; } @@ -1039,10 +1054,7 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng } if(game::StringQuestion(What, CONST_S("Write your map note (optionally position mouse cursor over it before editing):"), WHITE, 0, iLSqrLimit, true) == NORMAL_EXIT){ - festring finalWhat; - finalWhat << game::MapNoteToken(); - finalWhat << What; - lsqrN->Engrave(finalWhat); + game::SetMapNote(lsqrN,What); } break; @@ -1314,6 +1326,8 @@ truth commandsystem::Throw(character* Char) Char->EditExperience(PERCEPTION, 75, 1 << 8); Char->EditNP(-50); Char->DexterityAction(5); + if(ivanconfig::IsAutoPickupThrownItems()) + Item->SetTag('t'); return true; } else @@ -1322,9 +1336,37 @@ truth commandsystem::Throw(character* Char) } } +ulong itLastApplyID=0; //save it? +truth commandsystem::ApplyAgain(character* Char) +{ + if(itLastApplyID==0){ + ADD_MESSAGE("I need to apply something first."); + return false; + } + + item* it=game::SearchItem(itLastApplyID); + if(!it){ + itLastApplyID=0; + ADD_MESSAGE("I can't re-apply, it was destroyed."); + return false; + } + + if(it->FindCarrier()==Char){ + ADD_MESSAGE("I will apply my %s again.",it->GetName(UNARTICLED).CStr()); + return ApplyWork(Char,it); + }else + ADD_MESSAGE("I need to get my %s back!",it->GetName(UNARTICLED).CStr()); + + return false; +} + truth commandsystem::Apply(character* Char) { + return ApplyWork(Char); +} +truth commandsystem::ApplyWork(character* Char,item* itOverride) +{ if(!Char->CanApply()) { ADD_MESSAGE("This monster type cannot apply."); @@ -1341,9 +1383,14 @@ truth commandsystem::Apply(character* Char) return false; } - item* Item = Char->SelectFromPossessions(CONST_S("What do you want to apply?"), &item::IsAppliable); + item* Item = itOverride; + if(Item==NULL) + Item = Char->SelectFromPossessions(CONST_S("What do you want to apply?"), &item::IsAppliable); bool b = Item && Item->Apply(Char); + if(b) + itLastApplyID=Item->GetID(); + return b; } @@ -1487,17 +1534,45 @@ truth commandsystem::Rest(character* Char) } truth commandsystem::ShowMap(character* Char) +{ + return ShowMapWork(Char); +} +truth commandsystem::ShowMapWork(character* Char,v2* pv2ChoseLocation) { static humanoid* h;h = dynamic_cast(PLAYER); + bool bChoseLocationMode = pv2ChoseLocation!=NULL; + + festring fsHelp;fsHelp<< + "[Map Help:]\n" + " F1 - show this message\n" + " Map notes containing '!' or '!!' will be highlighted.\n" + " Position mouse cursor over a map note to edit or delete it.\n" + " In look mode, clicking on a map note will navigate to that location.\n"; + + if(bChoseLocationMode) + if(!game::ToggleShowMapNotes()) + game::ToggleShowMapNotes(); + if( h && (h->GetLeftArm() || h->GetRightArm()) ){ if(game::ToggleDrawMapOverlay()){ lsquare* lsqrH=NULL; while(true){ v2 noteAddPos = Char->GetPos(); - switch(game::KeyQuestion(CONST_S("Cartography notes action: (t)oggle, (e)dit/add, (l)ook mode, (r)otate, (d)elete."), - KEY_ESC, 5, 't', 'l', 'r','d','e') - ){ + + int key; + if(bChoseLocationMode) + key='l'; + else + key = game::KeyQuestion(CONST_S("Cartography notes action: (t)oggle, (e)dit/add, (l)ook mode, (r)otate, (d)elete. (F1 help)"), //TODO KeyQuestion() should detect F1 and return a default answer, currently F1 will just override any other key press + KEY_ESC, 5, 't', 'l', 'r', 'd', 'e'); + + if(specialkeys::IsRequestedEvent(specialkeys::FocusedElementHelp)){ + specialkeys::ConsumeEvent(specialkeys::FocusedElementHelp,fsHelp); + continue; + } + + switch(key){ case 'd': lsqrH = game::GetHighlightedMapNoteLSquare(); if(lsqrH!=NULL){ @@ -1515,11 +1590,30 @@ truth commandsystem::ShowMap(character* Char) case 'l': if(noteAddPos==Char->GetPos()){ game::RefreshDrawMapOverlay(); - noteAddPos = game::PositionQuestion(CONST_S( - "Where do you wish to add a map note? [direction keys move cursor, space accepts]"), - Char->GetPos(), NULL, NULL, true); DBGSV2(noteAddPos); - if(noteAddPos==ERROR_V2) - continue; + + festring fsMsg = pv2ChoseLocation!=NULL ? "Chose a location." : + "Where do you wish to add a map note?"; + fsMsg<<" [direction keys move cursor, space accepts]"; + + v2 start; + if(pv2ChoseLocation!=NULL){ + if(!(*pv2ChoseLocation).Is0()) + if(Char->GetLevel()->IsValidPos((*pv2ChoseLocation))) + start=(*pv2ChoseLocation); + } + if(start.Is0()) + start=Char->GetPos(); + + noteAddPos = game::PositionQuestion(fsMsg, start, NULL, NULL, true); DBGSV2(noteAddPos); + if(noteAddPos==ERROR_V2){ + game::ToggleDrawMapOverlay(); + return false; //continue; + } + if(pv2ChoseLocation!=NULL){ + (*pv2ChoseLocation)=noteAddPos; + game::ToggleDrawMapOverlay(); + return (*pv2ChoseLocation) != Char->GetPos(); + } } /* no break */ case 'e': @@ -1550,26 +1644,99 @@ truth commandsystem::Sit(character* Char) return (Square->GetOLTerrain() && Square->GetOLTerrain()->SitOn(Char)) || Square->GetGLTerrain()->SitOn(Char); } +std::vector RouteGoOn; +level* LevelRouteGoOn=NULL; +v2 v2RouteTarget=v2(0,0); //TODO savegame this? + +std::vector commandsystem::GetRouteGoOnCopy(){ + if(game::GetCurrentLevel()!=LevelRouteGoOn || v2RouteTarget.Is0()){ + std::vector empty; + return empty; + } + return RouteGoOn; +} + truth commandsystem::Go(character* Char) { - int Dir = game::DirectionQuestion(CONST_S("In what direction do you want to go? [press a direction key]"), false); + int Dir = DIR_ERROR; + + if(LevelRouteGoOn!=Char->GetLevel()) + v2RouteTarget=v2(0,0); + + if(Char->GetPos()==v2RouteTarget) //TODO is near by 1 dist (2 or more may have a wall in-between) + v2RouteTarget=v2(0,0); + + if(!v2RouteTarget.Is0()){ + switch(game::KeyQuestion(CONST_S("Continue going thru the route? [y/n]"), KEY_ESC, 2, 'y', 'n')){ + case 'y': + Dir = YOURSELF; + break; + case 'n': + v2RouteTarget=v2(0,0); + break; + default: + return false; + } + } if(Dir == DIR_ERROR) + Dir = game::DirectionQuestion(CONST_S("In what direction do you want to go? [press a direction key or '.' for map route]"), false, true); + + if(Dir == DIR_ERROR) + return false; + + RouteGoOn.clear(); + if(Dir == YOURSELF){ + if(v2RouteTarget.Is0()) + if(!ShowMapWork(Char,&v2RouteTarget)){ + v2RouteTarget=v2(0,0); + return false; + } + + if(Char->GetPos()==v2RouteTarget){ + v2RouteTarget=v2(0,0); + return false; + } + + std::set Illegal; + node* Node = Char->GetLevel()->FindRoute(Char->GetPos(), v2RouteTarget, Illegal, 0, Char); + if(Node){ + RouteGoOn.clear(); + while(Node->Last) + { + RouteGoOn.push_back(Node->Pos); + Node = Node->Last; + } + } + } + + if(Dir == YOURSELF && RouteGoOn.size()==0){ + v2RouteTarget=v2(0,0); return false; + } go* Go = go::Spawn(Char); - Go->SetDirection(Dir); - int OKDirectionsCounter = 0; + if(Dir == YOURSELF){ + Go->SetRoute(RouteGoOn); + Go->SetDirectionFromRoute(); + Go->SetIsWalkingInOpen(true); //prevents stopping on path crosses/forks + LevelRouteGoOn=Char->GetLevel(); + }else{ + Go->SetDirection(Dir); - for(int d = 0; d < Char->GetNeighbourSquares(); ++d) - { - lsquare* Square = Char->GetNeighbourLSquare(d); + int OKDirectionsCounter = 0; + + for(int d = 0; d < Char->GetNeighbourSquares(); ++d) + { + lsquare* Square = Char->GetNeighbourLSquare(d); + + if(Square && Char->CanMoveOn(Square)) + ++OKDirectionsCounter; + } - if(Square && Char->CanMoveOn(Square)) - ++OKDirectionsCounter; + Go->SetIsWalkingInOpen(OKDirectionsCounter > 2); } - Go->SetIsWalkingInOpen(OKDirectionsCounter > 2); Char->SetAction(Go); Char->EditAP(Char->GetStateAPGain(100)); // gum solution Char->GoOn(Go, true); diff --git a/Main/Source/definesvalidator.cpp b/Main/Source/definesvalidator.cpp new file mode 100644 index 000000000..e39a9d634 --- /dev/null +++ b/Main/Source/definesvalidator.cpp @@ -0,0 +1,145 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proto.h" +#include "devcons.h" +#include "game.h" +#include "message.h" + +//static void DefinesValidatorAppend(std::string s); +//static void DefinesValidatorTop(); +//static void DefinesValidatorAppendCode(std::string s); +std::ofstream DefinesValidator; +void DefinesValidatorAppend(std::string s) +{ + static std::stringstream ssValidateLine;ssValidateLine.str(std::string());ssValidateLine.clear(); //actually clear/empty it = "" + + ssValidateLine << s << std::endl; + + static bool bDummyInit = [](){ + DefinesValidator.open( + festring(game::GetHomeDir() + "definesvalidator.h").CStr(), + std::ios::binary); + return true;}(); + + DefinesValidator.write(ssValidateLine.str().c_str(),ssValidateLine.str().length()); +} +void DefinesValidatorTop() +{ + DefinesValidatorAppend("/****"); + DefinesValidatorAppend(" * AUTO-GENERATED CODE FILE, DO NOT MODIFY as modifications will be overwritten !!!"); + DefinesValidatorAppend(" *"); + DefinesValidatorAppend(" * After it is generated, update the one at source code path with it and"); + DefinesValidatorAppend(" * recompile so the results on the abort message (if happens) will be updated !!!"); + DefinesValidatorAppend(" */"); + DefinesValidatorAppend(""); + DefinesValidatorAppend("#ifndef _DEFINESVALIDATOR_H_"); + DefinesValidatorAppend("#define _DEFINESVALIDATOR_H_"); + DefinesValidatorAppend(""); + + #define INCDEPS(dep) DefinesValidatorAppend("#include <"#dep">"); + INCDEPS(string); + INCDEPS(algorithm); + INCDEPS(cstdarg); + INCDEPS(string); + INCDEPS(sstream); + INCDEPS(iostream); + INCDEPS(vector); + INCDEPS(bitset); + INCDEPS(ctime); + + DefinesValidatorAppend(""); + + #define INCDEPSH(dep) DefinesValidatorAppend("#include \""#dep"\""); + INCDEPSH(error.h) + + DefinesValidatorAppend(""); + DefinesValidatorAppend("class definesvalidator{"); + DefinesValidatorAppend(""); + DefinesValidatorAppend(" public:"); + DefinesValidatorAppend(" static void init();"); + DefinesValidatorAppend(" static void DevConsCmd(std::string);"); + DefinesValidatorAppend(" static void GenerateDefinesValidator(std::string);"); + DefinesValidatorAppend(""); + DefinesValidatorAppend(" static void Validate() {"); + DefinesValidatorAppend(" std::stringstream ssErrors;"); + DefinesValidatorAppend(" std::bitset<32> bsA, bsB;"); + DefinesValidatorAppend(""); +} +void DefinesValidatorAppendCode(std::string sDefineId, long valueReadFromDatFile) +{ + static std::stringstream ssMsg;ssMsg.str(std::string());ssMsg.clear(); //actually clear/empty it = "" + + ssMsg << "\"Defined " << sDefineId << " with value " << valueReadFromDatFile << " from .dat file " << + "mismatches hardcoded c++ define value of \" << " << sDefineId << " << \"!\""; + + + static std::stringstream ssCode;ssCode.str(std::string());ssCode.clear(); //actually clear/empty it = "" + +// " if( " << valueReadFromDatFile << " != ((ulong)" << sDefineId << ") ) // DO NOT MODIFY!" << std::endl << + ssCode << + " " << std::endl << + "#ifdef " << sDefineId << " // DO NOT MODIFY!" << std::endl << + " bsA = " << valueReadFromDatFile << ";" << std::endl << + " bsB = " << sDefineId << ";" << std::endl << + " if(bsA!=bsB)" << std::endl << + " ssErrors << " << ssMsg.str() << " << std::endl;" << std::endl << + "#endif " << std::endl; + + + DefinesValidatorAppend(ssCode.str()); +} +void DefinesValidatorClose(){ + DefinesValidatorAppend(""); + DefinesValidatorAppend(" if(ssErrors.str().length() > 0) ABORT(ssErrors.str().c_str());"); + DefinesValidatorAppend(""); + DefinesValidatorAppend("}};"); + DefinesValidatorAppend(""); + DefinesValidatorAppend("#endif // _DEFINESVALIDATOR_H_"); + + DefinesValidator.close(); +} +#include "definesvalidator.h" //tip: 1st run this was commented +void CmdDevConsGenDefVal(std::string strOpt){ + definesvalidator::GenerateDefinesValidator(strOpt); +} +void definesvalidator::init(){ + devcons::AddDevCmd("DefVal", CmdDevConsGenDefVal, + " generate the validator at user config path or validate the file 'define.dat' (may abort)"); +} +void definesvalidator::GenerateDefinesValidator(std::string strOpt) +{ + if(strOpt=="generate"){ + DefinesValidatorTop(); + + for(const valuemap::value_type& p : game::GetGlobalValueMap()) + DefinesValidatorAppendCode(p.first.CStr(), p.second); + + DefinesValidatorClose(); + ADD_MESSAGE("generated the defines validator"); + }else + if(strOpt=="validate"){ + definesvalidator::Validate(); //tip: 1st run this was commented + ADD_MESSAGE("validated 'defines.dat'"); + }else{ + ADD_MESSAGE("invalid option: '%s'",strOpt.c_str()); + } +} diff --git a/Main/Source/devcons.cpp b/Main/Source/devcons.cpp new file mode 100644 index 000000000..1bcd2fbed --- /dev/null +++ b/Main/Source/devcons.cpp @@ -0,0 +1,373 @@ +/* + * + * Iter Vehemens ad Necem (IVAN) + * Copyright (C) Timo Kiviluoto + * Released under the GNU General + * Public License + * + * See LICENSING which should be included + * along with this file for more details + * + */ + +#include +#include + +#include "SDL.h" + +//#include "bugworkaround.h" +#include "char.h" +#include "devcons.h" +#include "error.h" +#include "feio.h" +#include "felist.h" +#include "game.h" +#include "message.h" +#include "stack.h" +#include "specialkeys.h" + +/** + * ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! + * ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! + * ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! + * + * Non-wizard commands are intended ONLY to easify fixing the game, + * help the player get un-stuck if ever happens, + * workaround some glitch like a blocked path that shouldnt be, + * etc. + * + * Any information/functionality that provides an advantage must be considered a cheat, therefore WIZARD MODE!!! + */ + +#ifdef WIZARD +void ListChars(std::string strFilter){ + ulong idFilter=0; + if(!strFilter.empty()) + idFilter=atoi(strFilter.c_str()); + + DEVCMDMSG("params: %d",idFilter); + +// std::vector vc = game::GetAllCharacters(); +// for(int i=0;isecond; + if(idFilter!=0 && idFilter!=C->GetID())continue; + + festring fsPos="NULL"; + if(C->GetSquareUnder()!=NULL){ + fsPos.Empty(); + fsPos<GetPos().X<<","<GetPos().Y; + } + + festring fsMsg; + fsMsg << (C->IsPlayer()?"@":" ") << + "id="<GetID()<<"["<first<<"] "<< + "("<GetName(DEFINITE)<<"'"; + character* PB = C->GetPolymorphBackup(); + if(PB!=NULL) + fsMsg << " PB='"<GetID() <<"/"<< PB->GetName(DEFINITE)<<"'"; + fsMsg << "."; + DEVCMDMSG("%s",fsMsg.CStr()); +// DEVCMDMSG("%sid=%d[%d] (%s) '%s'.", +//// ADD_MESSAGE("%sid=%d (%d,%d) '%s'.", +// C->IsPlayer()?"@":" ", +// C->GetID(), +// itr->first, +// fsPos.CStr(), +// C->GetName(DEFINITE).CStr() +// ); + } +} +void ListItems(std::string strParams){ + ulong idCharFilter=0; + ulong idFilter=0; + + if(!strParams.empty()){ + std::string part; + std::stringstream iss(strParams); + if(iss >> part){ + if(part=="c"){ + if(iss >> part) + idCharFilter=atoi(part.c_str()); + } + if(part=="i"){ + if(iss >> part) + idFilter=atoi(part.c_str()); + } + } + } + + DEVCMDMSG("params: %d %d",idFilter,idCharFilter); + + itemidmap map = game::GetItemIDMapCopy(); + for(itemidmap::iterator itr = map.begin();itr!=map.end();itr++){ +// return Iterator != ItemIDMap.end() ? Iterator->second : 0; +// std::vector vc = game::GetAllItems(); +// for(int i=0;isecond; //helps on debugging too + + //TODO could check app memory range if pointer is within limits... + if( //TODO items could have a random key to detect if they were not deleted improperly or w/e, could still segfault when reading it tho... + it->GetVolume()==0 || + it->GetID()==0 || + it->GetSquaresUnder()==0 || + it->GetSquaresUnder()>100 || //something improbable, could be just 8 I guess... + false + ){ + DEVCMDMSG("item REFERENCE INVALID at consistent list ID=%d 0x%X",itr->first,it); //was the item deleted or what happened? + } + + if(idFilter!=0 && idFilter!=it->GetID()) + continue; + + slot* Slot = it->GetSlot(); + + const entity* ent; + festring fsType; + if(dynamic_cast(Slot)!=NULL){ + ent=((gearslot*)Slot)->FindCarrier(); + fsType="gear"; + }else + if(dynamic_cast(Slot)!=NULL){ + ent=((bodypartslot*)Slot)->GetMaster(); + fsType="bodypart"; + }else + if(dynamic_cast(Slot)!=NULL){ + stackslot* sl = ((stackslot*)Slot); + ent=sl->FindCarrier(); + if(sl->GetMotherStack()!=NULL) + ent=sl->GetMotherStack()->GetMotherEntity(); + fsType="stack"; + }else + ent=NULL; + + festring fsDec; + citem* entI; + ccharacter* entC; + if(dynamic_cast(ent)){ + entI=(citem*)ent; + entC=NULL; + if(dynamic_cast(ent)){ + const corpse* CP = (const corpse*)ent; + entC = CP->GetDeceased(); + fsDec=",Dec"; + } + }else + if(dynamic_cast(ent)){ + entI=NULL; + entC=(ccharacter*)ent; + }else{ + entI=NULL; + entC=NULL; + } + + if(idCharFilter!=0) + if(entC==NULL || entC->GetID()!=idCharFilter) + continue; + + bool bPlayerStuff = entC!=NULL && entC->IsPlayer(); + + festring fsPB; + if(entC!=NULL && entC->GetPolymorphBackup()!=NULL && entC->GetPolymorphBackup()->IsPlayer()) + fsPB=",PB"; + + festring fsPos="NULL"; + if(it->GetSquareUnder()!=NULL){ + fsPos.Empty(); + fsPos<GetPos().X<<","<GetPos().Y; + } + + DEVCMDMSG("%sid=%d (%s) '%s' owner '%d/%s' '%d/%s' (%s%s%s).", + bPlayerStuff?"@":" ", + it->GetID(), + + fsPos.CStr(), + + it->GetName(DEFINITE).CStr(), + + entC!=NULL ? entC->GetID() : 0, + entC!=NULL ? entC->GetName(DEFINITE).CStr() : "NoEntC", + + entI!=NULL ? entI->GetID() : 0, + entI!=NULL ? entI->GetName(DEFINITE).CStr() : "NoEntI", + + fsType.CStr(), + fsPB.CStr(), + fsDec.CStr() + ); + } +} +#endif + +void devcons::Init() +{ + specialkeys::AddCtrlOrFuncKeyHandler(SDLK_BACKQUOTE,&OpenCommandsConsole); +} + +const int iVarTot=10; +float afVars[iVarTot]; +void devcons::SetVar(std::string strParams) +{ + if(!strParams.empty()){ + std::string part; + std::stringstream iss(strParams); + + iss >> part; + int index = atoi(part.c_str()); //TODO use string IDs instead of index and create a map + if(index<0 || index>=iVarTot) + ABORT("invalid var index %d",index); + + iss >> part; + int value = atof(part.c_str()); + + afVars[index]=value; + } +} +float devcons::GetVar(int index,float fDefaultIf0) +{ + static bool bDummyInit = [](){for(int i=0;i=iVarTot) + ABORT("invalid var index %d",index); + return afVars[index] == 0 ? fDefaultIf0 : afVars[index]; +} + +bool bOpenCommandsConsole=false; +void devcons::OpenCommandsConsole() +{ + static bool bDummyInit = [](){ + #define ADDCMD(cm,sh,bw) AddDevCmd(#cm,cm,sh,bw); + ADDCMD(Help,"show this help",false); + AddDevCmd("?",Help,"show this help",false); +#ifdef WIZARD + ADDCMD(SetVar,festring()<<" set a float variable index (max "<<(iVarTot-1)<<") to be used on debug",true); + ADDCMD(ListChars,"[filterCharID:ulong] list all characters on current dungeon level",true); + ADDCMD(ListItems,"[[c|i] ] filter by char or item ID. List all items on current dungeon level, including on characters inventory and containers",true); +#endif + return true; + }(); + + // if(felist::isAnyFelistCurrentlyDrawn()) return; + // if(iosystem::IsInUse()) return; + // if(iosystem::IsOnMenu()) return; + // if(game::IsQuestionMode()) return; + // if(game::IsInGetCommand()) return; + // if(specialkeys::IsConsumingEvent()) return; + // if(bugfixdp::IsAlertConfirmFixMsgDraw) return; + /** + * can only be opened if nothing else is being done, + * if waiting some global player command, + * this is better than all other specific checks. + */ + if(!game::IsInGetCommand()) + return; + + if(bOpenCommandsConsole) + return; + + bOpenCommandsConsole=true; + for(;;){ + static festring fsFullCmd; + festring fsQ; + if(game::WizardModeIsActive()) + fsQ="Developer(WIZ) "; + fsQ<<"Console Command (try 'help' or '?'):"; + //TODO key up/down commands history and save/load to a txt file + if(game::StringQuestion(fsFullCmd, fsQ, WHITE, 1, 255, true) == NORMAL_EXIT){ + runCommand(fsFullCmd); + msgsystem::DrawMessageHistory(); + }else + break; + } + bOpenCommandsConsole=false; +} + +typedef void (*callcmd)(std::string); + +struct DevCmd{ + std::string strCmd=""; + std::string strCmdLowerCase=""; + std::string strHelp=""; + callcmd Call=NULL; + bool bWizardModeOnly=false; +}; + +std::vector vCmd; + +void devcons::AddDevCmd(festring fsCmd, callcmd Call, festring fsHelp,bool bWizardModeOnly) +{ + callcmd cc = Find(fsCmd.CStr()); + if(cc!=NULL) //TODO ignore if equal? + ABORT("command %s already set %x %x",fsCmd.CStr(),cc,Call); + + DevCmd dc; + dc.strCmd=fsCmd.CStr(); + dc.strCmdLowerCase=fsCmd.CStr(); + std::transform(dc.strCmdLowerCase.begin(), dc.strCmdLowerCase.end(), dc.strCmdLowerCase.begin(), ::tolower); + dc.strHelp=fsHelp.CStr(); + dc.Call = Call; + dc.bWizardModeOnly=bWizardModeOnly; + + int i=dc.strCmd.find(" "); + if(i!=std::string::npos) + ABORT("command must not contain spaces '%s'",dc.strCmd.c_str()); + + vCmd.push_back(dc); +} + +void devcons::Help(std::string strFilter) +{ + for(int j=0;jsecond == NULL){ + if(!bugfixdp::IsFixing()) + ABORT("AlreadyErased:CharacterID %d",ID); + }else + CharacterIDMap.erase(itr); } void game::AddItemID(item* Item, ulong ID) { @@ -293,15 +308,20 @@ void game::RemoveItemID(ulong ID) { if(ID){ DBG2("Erasing:ItemID",ID); -// if(ID==20957)DBGSTK;//temp test case debug - DBGEXEC( - if(SearchItem(ID)==NULL){ - DBG2("AlreadyErased:ItemID",ID); //TODO ABORT? - DBGSTK; - } - ); - ItemIDMap.erase(ItemIDMap.find(ID)); - DBG2("ERASED!:ItemID",ID); + + itemidmap::iterator itr = ItemIDMap.find(ID); //TODO if the search affects performance, make this optional + if(itr == ItemIDMap.end() || itr->second == NULL){ + /** + * This happens when the duplicated player bug happens! + * so it will try to erase the item 2 times and CRASH on the second, + * therefore the abort is appropriate. + */ + if(!bugfixdp::IsFixing()) + ABORT("AlreadyErased:ItemID %d, possible dup char bug",ID); + }else{ + ItemIDMap.erase(itr); + DBG2("ERASED!:ItemID",ID); + } } } @@ -315,7 +335,14 @@ void game::AddTrapID(entity* Trap, ulong ID) } void game::RemoveTrapID(ulong ID) { - if(ID) TrapIDMap.erase(TrapIDMap.find(ID)); + if(ID){ + trapidmap::iterator itr = TrapIDMap.find(ID); + if(itr == TrapIDMap.end() || itr->second == NULL){ + if(!bugfixdp::IsFixing()) + ABORT("AlreadyErased:TrapID %d",ID); + }else + TrapIDMap.erase(itr); + } } void game::UpdateTrapID(entity* Trap, ulong ID) { @@ -411,8 +438,15 @@ void game::PrepareToClearNonVisibleSquaresAround(v2 v2SqrPos) { v2BottomRight=v2ChkSqrPos; //it will keep updating bottom right while it can plsqChk = plv->GetLSquare(v2ChkSqrPos); - if(plsqChk->CanBeSeenByPlayer())continue;DBGLN; - if(!IsInWilderness() && plsqChk->CanBeFeltByPlayer())continue;DBGLN; + if(plsqChk->CanBeSeenByPlayer()) + continue; + if(!IsInWilderness()){ + if(plsqChk->CanBeFeltByPlayer()) + continue; + if(plsqChk->GetCharacter()!=NULL) + if(plsqChk->GetCharacter()->CanBeSeenByPlayer()) + continue; + } /******************************************************************************************** * Now the final thing is to setup the relative pixel position on the small blitdata->bitmap @@ -829,7 +863,8 @@ truth game::Init(cfestring& loadBaseName) GameBegan = time(0); LastLoad = time(0); TimePlayedBeforeLastLoad = time::GetZeroTime(); - commandsystem::ClearSwapWeapons(); + commandsystem::ClearSwapWeapons(); //to clear the memory from possibly previously loaded game +//TODO craftcore::SetSuspended(NULL); //to clear the memory from possibly previously loaded game bool PlayerHasReceivedAllGodsKnownBonus = false; ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to " "move, '>' to enter an area and '?' to view other commands."); @@ -1192,6 +1227,14 @@ truth game::OnScreen(v2 Pos) && Pos.X < GetCamera().X + GetScreenXSize() && Pos.Y < GetCamera().Y + GetScreenYSize(); } +void game::SetMapNote(lsquare* lsqrN,festring What) +{ + festring finalWhat; + finalWhat << game::MapNoteToken(); + finalWhat << What; + lsqrN->Engrave(finalWhat); +} + bool bDrawMapOverlayEnabled=false; int iMapOverlayDrawCount=0; bool game::ToggleDrawMapOverlay() @@ -1248,7 +1291,80 @@ int game::RotateMapNotes() return iMapNotesRotation; } -bool bShowMapNotes=false; +int game::CheckAutoPickup(square* sqr) +{ + if(!ivanconfig::IsAutoPickupThrownItems()) + return false; + + if(sqr==NULL) + sqr = PLAYER->GetSquareUnder(); + + if(dynamic_cast(sqr)==NULL) + return false; + + lsquare* lsqr = (lsquare*)sqr; + + itemvector iv; + lsqr->GetStack()->FillItemVector(iv); + int j=0; + for(int i=0;iHasTag('t')){ //throw + it->MoveTo(PLAYER->GetStack()); + ADD_MESSAGE("%s picked up.", it->GetName(INDEFINITE).CStr()); + j++; + } + } + + return j; +} + +bool game::CheckAddAutoMapNote(square* sqr) +{ + if(sqr==NULL) + sqr = PLAYER->GetSquareUnder(); + + if(dynamic_cast(sqr)==NULL) + return false; + + lsquare* lsqr = (lsquare*)sqr; + + if(lsqr->GetEngraved()) + return false; + + olterrain* olt = lsqr->GetOLTerrain(); + if(!olt)return false; + + festring fs; + if(fs.GetSize()==0 && dynamic_cast(olt)!=NULL) + fs<GetMasterGod()->GetName()<<" altar"; + if(fs.GetSize()==0 && dynamic_cast(olt)!=NULL) + fs<<"Sign: "<<((sign*)olt)->GetText(); + + if( + dynamic_cast(olt)!=NULL || + dynamic_cast(olt)!=NULL || + dynamic_cast(olt)!=NULL || + dynamic_cast(olt)!=NULL || + olt->GetConfig() == ANVIL || + olt->GetConfig() == FORGE || + olt->GetConfig() == WORK_BENCH || + false + ){ + olt->AddName(fs,INDEFINITE); +// fs<GetNameSingular(); + } + + if(fs.GetSize()>0){ + SetMapNote(lsqr,fs); + game::RefreshDrawMapOverlay(); + return true; + } + + return false; +} + +bool bShowMapNotes=true; bool game::ToggleShowMapNotes() { bShowMapNotes=!bShowMapNotes; @@ -1277,6 +1393,8 @@ col16 colMapNoteBkg; int iNoteHighlight=-1; lsquare* game::GetHighlightedMapNoteLSquare() {DBGLN; + if(!bDrawMapOverlayEnabled)return NULL; + if(!bShowMapNotes)return NULL; if(iNoteHighlight==-1)return NULL;DBGLN; if(iNoteHighlight>=vMapNotes.size())return NULL;DBGLN; return vMapNotes[iNoteHighlight].lsqr; //no need to expose mapnote, all info required is at lsqr @@ -1367,7 +1485,14 @@ void game::DrawMapNotesOverlay(bitmap* buffer) // col16 colBkg = iNoteHighlight==i ? colBkg=YELLOW : colMapNoteBkg; if(validateV2(bkgTL,buffer,bkgB)){ - buffer->Fill(bkgTL,bkgB,colMapNoteBkg); //bkg + col16 colMapNoteBkg2=colMapNoteBkg; + if(festring(vMapNotes[i].note).Find("!!")!=festring::NPos) + colMapNoteBkg2=RED; + else + if(festring(vMapNotes[i].note).Find("!")!=festring::NPos) + colMapNoteBkg2=BLUE; + + buffer->Fill(bkgTL,bkgB,colMapNoteBkg2); //bkg buffer->DrawRectangle(bkgTL,bkgTL+bkgB,LIGHT_GRAY,iNoteHighlight==i); //bkg } @@ -1382,8 +1507,10 @@ void game::DrawMapNotesOverlay(bitmap* buffer) } for(int i=0;iDrawLine(vMapNotes[i].scrPos, vMapNotes[i].v2LineHook, ac[i%iTotCol], iNoteHighlight==i); + if(validateV2(vMapNotes[i].scrPos,buffer) && validateV2(vMapNotes[i].v2LineHook,buffer)){ + bool bNH = iNoteHighlight==i; + buffer->DrawLine(vMapNotes[i].scrPos, vMapNotes[i].v2LineHook, bNH ? WHITE : ac[i%iTotCol], bNH); + } } for(int i=0;iPrintf(buffer, vMapNotes[i].basePos, WHITE, "%s", vMapNotes[i].note); } +const char* cHugeMap="I can't open a map that is as big as the world!"; void game::DrawMapOverlay(bitmap* buffer) { DBGLN; if(!bDrawMapOverlayEnabled)return; if(ivanconfig::GetStartingDungeonGfxScale()==1){ - ADD_MESSAGE("This map is as big as the world!"); + ADD_MESSAGE(cHugeMap); bDrawMapOverlayEnabled=false; return; } @@ -1437,6 +1565,8 @@ void game::DrawMapOverlay(bitmap* buffer) static v2 v2MapScrSizeFinal(0,0); static bitmap* bmpFinal; + bool bTransparentMap = bPositionQuestionMode && (CursorPos != PLAYER->GetPos()) && ivanconfig::IsTransparentMapLM(); + if(bPositionQuestionMode){ static v2 v2PreviousCursorPos; if(v2PreviousCursorPos != CursorPos){ @@ -1516,12 +1646,14 @@ void game::DrawMapOverlay(bitmap* buffer) delete bmpMapBuffer; bmpMapBuffer=new bitmap(v2BmpSize); } -// bmpMapBuffer->ClearToColor(TRANSPARENT_COLOR); +// bmpMapBuffer->ClearToColor(TRANSPARENT_COLOR); + bmpMapBuffer->ClearToColor(BLACK); v2 v2PlayerScrPos(0,0); v2 v2CursorScrPos(-1,-1); v2 v2Dest(0,0); vMapNotes.clear(); + std::vector RouteGoOn = commandsystem::GetRouteGoOnCopy(); for(int iY=v2Min.Y;iY<=v2Max.Y;iY++){ // B.Dest.Y = v2TopLeft.Y +iY*iMapTileSize; v2Dest.Y = (iY-v2Min.Y)*iMapTileSize; @@ -1572,16 +1704,18 @@ void game::DrawMapOverlay(bitmap* buffer) return true; }(); + bool bDrawSqr=true; col16 colorO; - if(lsqr->HasBeenSeen()){ - static col16 colorDoor =MakeRGB16(0xFF*0.66, 0xFF*0.33, 0); //brown - static col16 colorFountain=MakeRGB16( 0, 0,0xFF ); //blue - static col16 colorUp =MakeRGB16( 0, 0xFF , 0); //green - static col16 colorDown =MakeRGB16( 0, 0xFF*0.50, 0); //dark green - static col16 colorAltar =MakeRGB16(0xFF*0.50, 0,0xFF ); //purple - static col16 colorNote =MakeRGB16(0xFF*0.90, 0xFF*0.90,0xFF*0.90); //just not white TODO why? + static col16 colorDoor =MakeRGB16(0xFF*0.66, 0xFF*0.33, 0); //brown + static col16 colorFountain =MakeRGB16( 0, 0,0xFF ); //blue + static col16 colorUp =MakeRGB16( 0, 0xFF , 0); //green + static col16 colorDown =MakeRGB16( 0, 0xFF*0.50, 0); //dark green + static col16 colorAltar =MakeRGB16(0xFF*0.50, 0,0xFF ); //purple + static col16 colorNote =MakeRGB16(0xFF*0.90, 0xFF*0.90,0xFF*0.90); //just not white because white is used as look mode indicator on map +// static col16 colorGoOnRoute=MakeRGB16(0xFF*0.75, 0xFF*0.75,0xFF*0.75); //light gray + static col16 colorGoOnRoute=MakeRGB16( 0, 0xFF*0.75,0xFF ); //cyan // static col16 colorOnGround=MakeRGB16(0xFF*0.80, 0xFF*0.50,0xFF*0.20); //orange - + if(lsqr->HasBeenSeen()){ static const int iTotRM=5 +1; //5 is max rest modifier from dat files static col16 colorOnGroundRM[iTotRM]; static bool bDummyInit2 = [](){ @@ -1628,6 +1762,7 @@ void game::DrawMapOverlay(bitmap* buffer) } }else{ //floor colorO=colorFloor; + if(bTransparentMap)bDrawSqr=false; } if(lsqr->IsMaterialDetected()) //color override @@ -1635,9 +1770,25 @@ void game::DrawMapOverlay(bitmap* buffer) }else{ colorO=colorMapBkg; + if(bTransparentMap)bDrawSqr=false; } - bmpMapBuffer->Fill(v2Dest, v2MapTileSize, colorO); + if(RouteGoOn.size()>0) + for(auto v2Rt = RouteGoOn.begin(); v2Rt != RouteGoOn.end(); v2Rt++) + if(v2SqrPos == *v2Rt){ + colorO=colorGoOnRoute; + bDrawSqr=true; + break; + } +// for(std::list::iterator itrRt = RouteGoOn.begin(); itrRt != RouteGoOn.end(); itrRt++) +// if(itrRt->second == v2SqrPos){ +// colorO=colorGoOnRoute; +// bDrawSqr=true; +// break; +// } + + if(bDrawSqr) + bmpMapBuffer->Fill(v2Dest, v2MapTileSize, colorO); if(CursorPos == v2SqrPos) v2CursorScrPos=v2Dest; @@ -1766,8 +1917,18 @@ void game::DrawMapOverlay(bitmap* buffer) v2MapSize = v2MapScrSizeFinal; } - bmpFinal->FastBlit(buffer, v2TopLeftFinal); - graphics::DrawRectangleOutlineAround(buffer, v2TopLeftFinal, v2MapScrSizeFinal, LIGHT_GRAY, true); + static blitdata BFinal = DEFAULT_BLITDATA; + BFinal.Bitmap = buffer; + BFinal.Dest = v2TopLeftFinal; + BFinal.Border = bmpFinal->GetSize(); + BFinal.MaskColor = BLACK; + if(bTransparentMap){ + bmpFinal->NormalMaskedBlit(BFinal); + }else + bmpFinal->FastBlit(BFinal.Bitmap, BFinal.Dest ); + + if(!bTransparentMap) + graphics::DrawRectangleOutlineAround(buffer, v2TopLeftFinal, v2MapScrSizeFinal, LIGHT_GRAY, true); iMapOverlayDrawCount++; @@ -1782,7 +1943,7 @@ void DrawMapOverlayFancy(bitmap* buffer) if(!bDrawMapOverlayEnabled)return; if(ivanconfig::GetStartingDungeonGfxScale()==1){ - ADD_MESSAGE("This map is as big as the world!"); + ADD_MESSAGE(cHugeMap); }else{ //it actually work works (for now) if there is any dungeon stretching going on if(buffer!=NULL){ static float fRGB=0.3; @@ -3208,6 +3369,7 @@ truth game::Save(cfestring& SaveName) protosystem::SaveCharacterDataBaseFlags(SaveFile); commandsystem::SaveSwapWeapons(SaveFile); DBGLN; +//TODO craftcore::Save(SaveFile); return true; } @@ -3290,10 +3452,7 @@ int game::Load(cfestring& saveName) v2 Pos; SaveFile >> Pos >> PlayerName; - character* CharAtPos = GetCurrentArea()->GetSquare(Pos)->GetCharacter(); - if(CharAtPos==NULL || !CharAtPos->IsPlayer()) - ABORT("Player not found! If there are backup files, try the 'restore backup' option."); - SetPlayer( bugWorkaroundDupPlayer::BugWorkaroundDupPlayer(CharAtPos,Pos) ); DBG3(CharAtPos,Player,DBGAV2(Pos)); + SetPlayer(bugfixdp::ValidatePlayerAt(GetCurrentArea()->GetSquare(Pos))); msgsystem::Load(SaveFile); SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex; SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster; @@ -3303,8 +3462,10 @@ int game::Load(cfestring& saveName) LastLoad = time(0); protosystem::LoadCharacterDataBaseFlags(SaveFile); - if(game::GetSaveFileVersion()>=132) - commandsystem::LoadSwapWeapons(SaveFile); + commandsystem::LoadSwapWeapons(SaveFile); +//TODO craftcore::Load(SaveFile); + + ///////////////// loading ended //////////////// UpdateCamera(); @@ -3926,8 +4087,16 @@ void game::CreateBusyAnimationCache() } } +bool bQuestionMode=false; +bool game::IsQuestionMode() +{ + return bQuestionMode || bPositionQuestionMode; +} + int game::AskForKeyPress(cfestring& Topic) { + bQuestionMode=true; + DrawEverythingNoBlit(); FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr()); graphics::BlitDBToScreen(); @@ -3939,6 +4108,8 @@ int game::AskForKeyPress(cfestring& Topic) #endif igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23)); + + bQuestionMode=false; return Key; } @@ -3957,10 +4128,37 @@ v2 game::PositionQuestion(cfestring& Topic, v2 CursorPos, void (*Handler)(v2), if(Handler) Handler(CursorPos); + bool bMapNotesMode = bDrawMapOverlayEnabled && bShowMapNotes; + + /** + * using the min millis value grants mouse will be updated most often possible + * default key -1 just to be ignored + */ + if(bMapNotesMode) + globalwindowhandler::SetKeyTimeout(100,-1); + bPositionQuestionMode=true; + v2 v2PreviousClick=v2(0,0); for(;;) { square* Square = GetCurrentArea()->GetSquare(CursorPos); + + if(bMapNotesMode){ + lsquare* lsqrMapNote = GetHighlightedMapNoteLSquare(); + if(lsqrMapNote){ + mouseclick mc = globalwindowhandler::ConsumeMouseEvent(); + if(mc.btn==1){ + CursorPos = lsqrMapNote->GetPos(); + if(v2PreviousClick == CursorPos){ //the 2nd click on same pos will accept as expected TODO fast double click detection, just reset v2PreviousClick after 0.5s ? + Return = CursorPos; + break; + } + v2PreviousClick = CursorPos; + } + } + + CheckAddAutoMapNote(Square); + } if(!Square->HasBeenSeen() && (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) @@ -4037,6 +4235,9 @@ v2 game::PositionQuestion(cfestring& Topic, v2 CursorPos, void (*Handler)(v2), } } + if(bMapNotesMode) + globalwindowhandler::ResetKeyTimeout(); + return Return; } @@ -4113,89 +4314,6 @@ truth game::AnimationController() return true; } -//static void DefinesValidatorAppend(std::string s); -//static void DefinesValidatorTop(); -//static void DefinesValidatorAppendCode(std::string s); -std::ofstream DefinesValidator; -void DefinesValidatorAppend(std::string s) -{ - static std::stringstream ssValidateLine;ssValidateLine.str(std::string());ssValidateLine.clear(); //actually clear/empty it = "" - - ssValidateLine << s << std::endl; - - static bool bDummyInit = [](){ - DefinesValidator.open( - festring(game::GetHomeDir() + "definesvalidator.h").CStr(), - std::ios::binary); - return true;}(); - - DefinesValidator.write(ssValidateLine.str().c_str(),ssValidateLine.str().length()); -} -void DefinesValidatorTop() -{ - DefinesValidatorAppend("/****"); - DefinesValidatorAppend(" * AUTO-GENERATED CODE FILE, DO NOT MODIFY as modifications will be overwritten !!!"); - DefinesValidatorAppend(" *"); - DefinesValidatorAppend(" * After it is generated, update the one at source code path with it and"); - DefinesValidatorAppend(" * recompile so the results on the abort message (if happens) will be updated !!!"); - DefinesValidatorAppend(" */"); - DefinesValidatorAppend(""); - DefinesValidatorAppend("#ifndef _DEFINESVALIDATOR_H_"); - DefinesValidatorAppend("#define _DEFINESVALIDATOR_H_"); - DefinesValidatorAppend(""); - DefinesValidatorAppend("class definesvalidator{ public: static void Validate() {"); - DefinesValidatorAppend(""); - DefinesValidatorAppend(" std::stringstream ssErrors;"); - DefinesValidatorAppend(" std::bitset<32> bsA, bsB;"); - DefinesValidatorAppend(""); -} -void DefinesValidatorAppendCode(std::string sDefineId, long valueReadFromDatFile) -{ - static std::stringstream ssMsg;ssMsg.str(std::string());ssMsg.clear(); //actually clear/empty it = "" - - ssMsg << "\"Defined " << sDefineId << " with value " << valueReadFromDatFile << " from .dat file " << - "mismatches hardcoded c++ define value of \" << " << sDefineId << " << \"!\""; - - - static std::stringstream ssCode;ssCode.str(std::string());ssCode.clear(); //actually clear/empty it = "" - -// " if( " << valueReadFromDatFile << " != ((ulong)" << sDefineId << ") ) // DO NOT MODIFY!" << std::endl << - ssCode << - " " << std::endl << - "#ifdef " << sDefineId << " // DO NOT MODIFY!" << std::endl << - " bsA = " << valueReadFromDatFile << ";" << std::endl << - " bsB = " << sDefineId << ";" << std::endl << - " if(bsA!=bsB)" << std::endl << - " ssErrors << " << ssMsg.str() << " << std::endl;" << std::endl << - "#endif " << std::endl; - - - DefinesValidatorAppend(ssCode.str()); -} -void DefinesValidatorClose(){ - DefinesValidatorAppend(""); - DefinesValidatorAppend(" if(ssErrors.str().length() > 0) ABORT(ssErrors.str().c_str());"); - DefinesValidatorAppend(""); - DefinesValidatorAppend("}};"); - DefinesValidatorAppend(""); - DefinesValidatorAppend("#endif // _DEFINESVALIDATOR_H_"); - - DefinesValidator.close(); -} -#include "definesvalidator.h" //tip: 1st run this was commented -void game::GenerateDefinesValidator(bool bValidade) -{ - DefinesValidatorTop(); - - for(const valuemap::value_type& p : GlobalValueMap) - DefinesValidatorAppendCode(p.first.CStr(), p.second); - - DefinesValidatorClose(); - - if(bValidade) - definesvalidator::Validate(); //tip: 1st run this was commented -} - void game::InitGlobalValueMap() { inputfile SaveFile(GetDataDir() + "Script/define.dat", &GlobalValueMap); @@ -4221,6 +4339,8 @@ void game::TextScreen(cfestring& Text, v2 Displacement, col16 Color, globalwindowhandler::DisableControlLoops(); iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor); globalwindowhandler::EnableControlLoops(); + //TODO need? graphics::SetAllowStretchedBlit(); + //TODO useful or messy? graphics::BlitDBToScreen(); } /* ... all the keys that are acceptable @@ -4230,6 +4350,8 @@ void game::TextScreen(cfestring& Text, v2 Displacement, col16 Color, int game::KeyQuestion(cfestring& Message, int DefaultAnswer, int KeyNumber, ...) { + bQuestionMode=true; + int* Key = new int[KeyNumber]; va_list Arguments; va_start(Arguments, KeyNumber); @@ -4260,6 +4382,8 @@ int game::KeyQuestion(cfestring& Message, int DefaultAnswer, int KeyNumber, ...) delete [] Key; igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23)); + + bQuestionMode=false; return Return; } @@ -4325,6 +4449,8 @@ v2 game::NameKeyHandler(v2 CursorPos, int Key) void game::End(festring DeathMessage, truth Permanently, truth AndGoToMenu) { + game::SRegionAroundDisable(); + if(!Permanently) game::Save(); @@ -4567,16 +4693,31 @@ void game::EnterArea(charactervector& Group, int Area, int EntryIndex) if(Player) { - GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway(); + lsquare* lsqr = GetCurrentLevel()->GetLSquare(Pos); + character* NPC = lsqr->GetCharacter(); + + bool bMoveAway=true; + /** + * Genetrix Vesana goal is to protect the passage (or not?) TODO tho coming from above could grant a huge damage strike to help to kill it, what could be a tactical manouver + * using now largecreature check because of this crash stack: + area::GetSquare(v2) const //HERE V2 had invalid huge negative values for X and Y + largecreature::PutTo(v2) + character::PutNear(v2) //TODO some complexer code could be implemented at this method + lsquare::KickAnyoneStandingHereAway() + game::EnterArea(std::vector >&, int, int)+0x164) + */ + if(bMoveAway && dynamic_cast(NPC)!=NULL)bMoveAway=false; + + if(bMoveAway) + lsqr->KickAnyoneStandingHereAway(); + Player->PutToOrNear(Pos); } else { - SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter()); + SetPlayer(bugfixdp::ValidatePlayerAt(GetCurrentLevel()->GetLSquare(Pos))); } - bugWorkaroundDupPlayer::BugWorkaroundDupPlayer(Player,Pos); - uint c; for(c = 0; c < Group.size(); ++c) @@ -4693,7 +4834,7 @@ void prepareList(felist& rList, v2& v2TopLeft, int& iW){ iY=v2TopLeft.Y-3; } - int iItemW=bldListItemTMP.Border.X*bldListItemTMP.Stretch; + int iItemW = bldListItemTMP.Border.X * bldListItemTMP.Stretch; if(bAltItemPos){ iX += area::getOutlineThickness()*2; //to leave some space to alt item outline iX += iItemW; @@ -4706,9 +4847,11 @@ void prepareList(felist& rList, v2& v2TopLeft, int& iW){ //cant be so automatic... or user wants alt or default position... //if(bAltItemPos){iW+=iItemW;} } - v2TopLeft=v2(iX,iY); + v2TopLeft=v2(iX,iY); DBGSV2(v2TopLeft); graphics::SetSpecialListItemAltPos(bAltItemPos); + if(bAltItemPos) + felist::SetListItemAltPosMinY(area::getTopLeftCorner().Y); } int prepareListWidth(int iW){ @@ -4930,6 +5073,35 @@ character* game::SearchCharacter(ulong ID) return Iterator != CharacterIDMap.end() ? Iterator->second : 0; } +std::vector game::GetAllCharacters() +{ + std::vector vc; + for(int i=0;i game::GetAllItems() +{ + std::vector vc; + for(int i=0;iValue){ - case 0: Entry << "disabled";break; - case 1: Entry << "missing only";break; - case 2: Entry << "prefer old player";break; - case 3: Entry << "prefer new player";break; - } -} - void ivanconfig::SaveGameSortModeDisplayer(const cycleoption* O, festring& Entry) { switch(O->Value){ @@ -863,21 +844,6 @@ void ivanconfig::FontGfxChanger(cycleoption* O, long What) O->Value = What; } -void ivanconfig::GenerateDefinesValidatorChanger(truthoption* O, truth What) -{ - if(O!=NULL)O->Value = What; - - if(What) - game::GenerateDefinesValidator(true); //TODO make validation (that aborts) optional using cycleoption -} - -void ivanconfig::SavegameSafelyChanger(truthoption* O, truth What) -{ - if(O!=NULL)O->Value = What; - - outputfile::SetSafeSaving(What); -} - void ivanconfig::XBRZScaleChanger(truthoption* O, truth What) { O->Value = What; @@ -983,6 +949,7 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&WaitNeutralsMoveAway); configsystem::AddOption(fsCategory,&EnhancedLights); configsystem::AddOption(fsCategory,&DistLimitMagicMushrooms); + configsystem::AddOption(fsCategory,&AutoPickupThrownItems); fsCategory="Window"; configsystem::AddOption(fsCategory,&Contrast); @@ -1014,6 +981,7 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&RotateTimesPerSquare); configsystem::AddOption(fsCategory,&HitIndicator); configsystem::AddOption(fsCategory,&ShowMap); + configsystem::AddOption(fsCategory,&TransparentMapLM); fsCategory="Sounds"; configsystem::AddOption(fsCategory,&PlaySounds); @@ -1039,11 +1007,8 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&AllowMouseOnFelist); fsCategory="Advanced/Developer options"; - configsystem::AddOption(fsCategory,&BugWorkaroundDupPlayer); configsystem::AddOption(fsCategory,&AllowImportOldSavegame); - configsystem::AddOption(fsCategory,&SavegameSafely); configsystem::AddOption(fsCategory,&HideWeirdHitAnimationsThatLookLikeMiss); - configsystem::AddOption(fsCategory,&GenerateDefinesValidator); /******************************** * LOAD AND APPLY some SETTINGS * @@ -1070,7 +1035,6 @@ void ivanconfig::Initialize() FrameSkipChanger(NULL,FrameSkip.Value); StackListPageLengthChanger(NULL, StackListPageLength.Value); SaveGameSortModeChanger(NULL, SaveGameSortMode.Value); - SavegameSafelyChanger(NULL, SavegameSafely.Value); SelectedBkgColorChanger(NULL, SelectedBkgColor.Value); AllowMouseOnFelistChanger(NULL, AllowMouseOnFelist.Value); } diff --git a/Main/Source/item.cpp b/Main/Source/item.cpp index 9ca4171a7..dbe4edcf6 100644 --- a/Main/Source/item.cpp +++ b/Main/Source/item.cpp @@ -401,6 +401,31 @@ truth item::CanBeEatenByAI(ccharacter* Eater) const && ConsumeMaterial && ConsumeMaterial->CanBeEatenByAI(Eater); } +bool item::HasTag(char tag) +{ + static char Tag[3]={'#',0,0}; + Tag[1]=tag; + return label.Find(Tag,0) != festring::NPos; +} + +/** + * look for all usages to avoid tag clashes + */ +void item::SetTag(char tag) +{ + if(!HasTag(tag)) + label<<"#"<(GetConfig()); SaveFile << static_cast(Flags); SaveFile << Size << ID << LifeExpectancy << ItemFlags; - if(game::GetSaveFileVersion()>=132){ - SaveFile << label; - } + SaveFile << label; SaveLinkedList(SaveFile, CloneMotherID); if(Fluid) @@ -447,9 +470,8 @@ void item::Load(inputfile& SaveFile) databasecreator::InstallDataBase(this, ReadType(SaveFile)); Flags |= ReadType(SaveFile) & ~ENTITY_FLAGS; SaveFile >> Size >> ID >> LifeExpectancy >> ItemFlags; - if(game::GetSaveFileVersion()>=132){ + if(game::GetCurrentSavefileVersion()>=132) SaveFile >> label; - } LoadLinkedList(SaveFile, CloneMotherID); if(LifeExpectancy) diff --git a/Main/Source/level.cpp b/Main/Source/level.cpp index 03ddd63d8..a96d3ceed 100644 --- a/Main/Source/level.cpp +++ b/Main/Source/level.cpp @@ -2959,6 +2959,9 @@ int level::RevealDistantLightsToPlayer() //based on Draw() code if(!ivanconfig::IsEnhancedLights()) return 0; + if(!PLAYER->GetSquareUnder()) //NULL may happen on player's death, was polymorphed when the crash happened + return 0; + cint XMin = Max(game::GetCamera().X, 0); cint YMin = Max(game::GetCamera().Y, 0); cint XMax = Min(XSize, game::GetCamera().X + game::GetScreenXSize()); @@ -3003,7 +3006,8 @@ int level::RevealDistantLightsToPlayer() //based on Draw() code iMultDist=2; if(iDist <= lMaxDist*iMultDist) //ground view limit - if(!bTryReveal && hasLight(Square->Luminance,0xFF*0.475))bTryReveal=true; // 0.475 is based on tests with lantern + if(!bTryReveal && hasLight(Square->Luminance,0xFF*0.475)) // 0.475 is based on tests with lantern + bTryReveal=true; } if(bTryReveal){ diff --git a/Main/Source/main.cpp b/Main/Source/main.cpp index 4eef746b7..00dacceec 100644 --- a/Main/Source/main.cpp +++ b/Main/Source/main.cpp @@ -27,6 +27,8 @@ #include "game.h" #include "database.h" +#include "definesvalidator.h" +#include "devcons.h" #include "feio.h" #include "igraph.h" #include "iconf.h" @@ -34,12 +36,15 @@ #include "hscore.h" #include "graphics.h" #include "script.h" +#include "specialkeys.h" #include "message.h" #include "proto.h" #include "audio.h" #include "dbgmsgproj.h" +#include "bugworkaround.h" + #ifndef WIN32 void CrashHandler(int Signal) { @@ -97,6 +102,10 @@ int main(int argc, char** argv) game::CreateBusyAnimationCache(); globalwindowhandler::SetQuitMessageHandler(game::HandleQuitMessage); globalwindowhandler::SetScrshotDirectory(game::GetScrshotDir()); + specialkeys::init(); + bugfixdp::init(); + devcons::Init(); + definesvalidator::init(); msgsystem::Init(); protosystem::Initialize(); igraph::LoadMenu(); @@ -140,7 +149,7 @@ int main(int argc, char** argv) case 1: { iosystem::SetSkipSeekSave(&SkipGameScript); - festring LoadName = iosystem::ContinueMenu(WHITE, LIGHT_GRAY, game::GetSaveDir(), game::GetSaveFileVersion(), ivanconfig::IsAllowImportOldSavegame()); + festring LoadName = iosystem::ContinueMenu(WHITE, LIGHT_GRAY, game::GetSaveDir(), game::GetSaveFileVersionHardcoded(), ivanconfig::IsAllowImportOldSavegame()); if(LoadName.GetSize()) { diff --git a/Main/Source/materias.cpp b/Main/Source/materias.cpp index 551322b17..9d0628327 100644 --- a/Main/Source/materias.cpp +++ b/Main/Source/materias.cpp @@ -310,7 +310,8 @@ void powder::Load(inputfile& SaveFile) material* organic::EatEffect(character* Eater, long Amount) { Amount = Volume > Amount ? Amount : Volume; - GetMotherEntity()->SpecialEatEffect(Eater, Amount); + if(GetMotherEntity()) + GetMotherEntity()->SpecialEatEffect(Eater, Amount); Effect(Eater, TORSO_INDEX, Amount); Eater->ReceiveNutrition(GetNutritionValue() * Amount * 20 / (1000 * (GetSpoilLevel() + 1))); diff --git a/Main/Source/message.cpp b/Main/Source/message.cpp index 0355903de..99f787556 100644 --- a/Main/Source/message.cpp +++ b/Main/Source/message.cpp @@ -175,6 +175,7 @@ void msgsystem::Draw() void msgsystem::DrawMessageHistory() { game::RegionListItemEnable(false); //this fix the problem that happens on death + game::RegionSilhouetteEnable(false); MessageHistory.SetPageLength(ivanconfig::GetStackListPageLength()); MessageHistory.Draw(); diff --git a/Main/Source/miscitem.cpp b/Main/Source/miscitem.cpp index ab31ca03c..9537d60ee 100644 --- a/Main/Source/miscitem.cpp +++ b/Main/Source/miscitem.cpp @@ -3018,7 +3018,7 @@ void scrollofdetectmaterial::FinishReading(character* Reader) continue; } - TempMaterial = protosystem::CreateMaterial(Temp); + TempMaterial = protosystem::CreateMaterialForDetection(Temp); if(TempMaterial) break; diff --git a/Main/Source/proto.cpp b/Main/Source/proto.cpp index bd14704a7..a391de95e 100644 --- a/Main/Source/proto.cpp +++ b/Main/Source/proto.cpp @@ -473,7 +473,12 @@ item* protosystem::CreateItem(cfestring& What, truth Output) return 0; } -material* protosystem::CreateMaterial(cfestring& What, long Volume, truth Output) +material* protosystem::CreateMaterialForDetection(cfestring& What) +{ + return CreateMaterial(What,0,true,true); +} + +material* protosystem::CreateMaterial(cfestring& What, long Volume, truth Output, truth DetectMode) { for(int c1 = 1; c1 < protocontainer::GetSize(); ++c1) { @@ -484,11 +489,13 @@ material* protosystem::CreateMaterial(cfestring& What, long Volume, truth Output for(int c2 = 1; c2 < ConfigSize; ++c2) if(ConfigData[c2]->NameStem == What) { - if(ConfigData[c2]->CommonFlags & CAN_BE_WISHED - || game::WizardModeIsActive()) + if( + (ConfigData[c2]->CommonFlags & CAN_BE_WISHED) || + (DetectMode && (ConfigData[c2]->CommonFlags & CAN_BE_DETECTED)) || + game::WizardModeIsActive() + ){ return ConfigData[c2]->ProtoType->Spawn(ConfigData[c2]->Config, Volume); - else if(Output) - { + }else if(Output){ ADD_MESSAGE("You hear a booming voice: \"No, mortal! This will not be done!\""); return 0; } diff --git a/Script/define.dat b/Script/define.dat index 31427a73f..01eca5b9a 100644 --- a/Script/define.dat +++ b/Script/define.dat @@ -1297,6 +1297,7 @@ #define CAN_BE_DESTROYED 16 #define IS_VALUABLE 32 #define CAN_BE_MIRRORED 64 +#define CAN_BE_DETECTED 128 /* NameFlags */ #define USE_AN 1 diff --git a/Script/material.dat b/Script/material.dat index e3a030d8f..8838c4b45 100644 --- a/Script/material.dat +++ b/Script/material.dat @@ -50,7 +50,7 @@ material HardenedMaterial = NONE; SoftenedMaterial = NONE; IntelligenceRequirement = 1; - CommonFlags = IS_ABSTRACT|CAN_BE_WISHED|CAN_BE_MIRRORED; + CommonFlags = IS_ABSTRACT|CAN_BE_WISHED|CAN_BE_MIRRORED|CAN_BE_DETECTED; NameFlags = 0; CategoryFlags = 0; BodyFlags = USE_MATERIAL_ATTRIBUTES; diff --git a/Sound/SoundEffects.cfg b/Sound/SoundEffects.cfg index 703b3e128..ca08afe47 100644 --- a/Sound/SoundEffects.cfg +++ b/Sound/SoundEffects.cfg @@ -75,14 +75,14 @@ DoorResists; Door Resists 1.wav, Door Resists 2.wav, Door Resists 3.wav, Door Re DoorResists; Door Resists 1.wav, Door Resists 2.wav, Door Resists 3.wav, Door Resists 4.wav;.*door won't budge.* ############## -### drinkf ### +### drink fountain ### ############## drinkf; drinkf.wav;.*dries.*up.* drinkf; drinkf.wav;.*water.*taste.* ############## -### drinkp ### +### drink potion ### ############## drinkp; drinkp.wav;.*You.*finish.*drinking.* @@ -133,14 +133,14 @@ harp; harp.wav;.*mesmerizing sound.* ############## ### Hit ### ############## -HitBlade; blade1.wav, blade2.wav, blade3.wav, blade4.wav, blade5.wav, blade6.wav;.*slash.* -HitBlade; blade1.wav, blade2.wav, blade3.wav, blade4.wav, blade5.wav, blade6.wav;.*stab.* +HitBlade; blade1.wav, blade2.wav, blade3.wav, blade4.wav, blade5.wav, blade6.wav;.* slash .* +HitBlade; blade1.wav, blade2.wav, blade3.wav, blade4.wav, blade5.wav, blade6.wav;.* stab .* HitBlunt; blunt1.wav, blunt2.wav, blunt3.wav, blunt4.wav, punch4.wav;.* kick .* HitBlunt; blunt1.wav, blunt2.wav, blunt3.wav, blunt4.wav, punch4.wav;.* kicks .* -HitBlunt; blunt1.wav, blunt2.wav, blunt3.wav, blunt4.wav, punch4.wav;.*strike.* +HitBlunt; blunt1.wav, blunt2.wav, blunt3.wav, blunt4.wav, punch4.wav;.* strike .* -HitMe; hit.wav, hit2.wav, hit3.wav, hit4.wav, hit5.wav;.*hits.*you.* +HitMe; hit.wav, hit2.wav, hit3.wav, hit4.wav, hit5.wav;.* hits .* you.* HitMisses; miss.wav, miss2.wav, miss3.wav, miss4.wav, miss5.wav;.* (miss|misses) .* @@ -221,6 +221,8 @@ Snore; snore1.wav, snore2.wav, snore3.wav;.*You lose consciousness.* YouFallDown; FallDown1.wav, FallDown2.wav;.*You[^.]* fall down.* HappySounds; Woo-Hoo.wav;.*You feel tougher than anything.* +HappySounds; Woo-Hoo.wav;.*You feel your life experience increasing all the time.* +HappySounds; Woo-Hoo.wav;.*You feel very confident of your social skills.* StoreItem; ContainerPutGet1.wav, ContainerPutGet2.wav, ContainerPutGet3.wav;.*You (put|take) .* (in|from) .* (chest|strong-box) .*