diff --git a/.devsPrefs/AquariusPower/Graphics.WorkFiles/Item.xcf b/.devsPrefs/AquariusPower/Graphics.WorkFiles/Item.xcf new file mode 100644 index 000000000..a4b669fb3 Binary files /dev/null and b/.devsPrefs/AquariusPower/Graphics.WorkFiles/Item.xcf differ diff --git a/.devsPrefs/AquariusPower/Graphics.WorkFiles/OLTerra.xcf b/.devsPrefs/AquariusPower/Graphics.WorkFiles/OLTerra.xcf new file mode 100644 index 000000000..c0d220ae9 Binary files /dev/null and b/.devsPrefs/AquariusPower/Graphics.WorkFiles/OLTerra.xcf differ diff --git a/.devsPrefs/AquariusPower/nbproject/configurations.xml b/.devsPrefs/AquariusPower/nbproject/configurations.xml index 47597a70f..6887e56b4 100644 --- a/.devsPrefs/AquariusPower/nbproject/configurations.xml +++ b/.devsPrefs/AquariusPower/nbproject/configurations.xml @@ -2,21 +2,51 @@ + MIDIDebug.cpp + MIDIUtils.cpp + RtMidi.cpp + audio.cpp + audio_stack.cpp + linkedlist.cpp + midiparser.cpp + midiplayback.cpp + + + + CMakeFiles/3.10.2/CompilerIdC/CMakeCCompilerId.c + + + CMakeFiles/3.10.2/CompilerIdCXX/CMakeCXXCompilerId.cpp + + + CMakeFiles/feature_tests.c + CMakeFiles/feature_tests.cxx + + example.cc + namegen.cc - - felist.h - feloops.h - femath.h - festring.h - fetime.h - graphics.h - hscore.h + + + - + + /usr/share/cmake-3.10/Modules/CMakeCCompilerABI.c + /usr/share/cmake-3.10/Modules/CMakeCXXCompilerABI.cpp + /usr/share/cmake-3.10/Modules/CMakeCompilerABI.h + + bitmap.cpp config.cpp dbgmsg.cpp @@ -34,7 +64,7 @@ specialkeys.cpp whandler.cpp - + action.cpp actions.cpp actset.cpp @@ -50,6 +80,7 @@ command.cpp cont.cpp coreset.cpp + curseddeveloper.cpp database.cpp dataset.cpp definesvalidator.cpp @@ -86,6 +117,7 @@ trap.cpp traps.cpp trapset.cpp + wizautoplay.cpp wmapset.cpp worldmap.cpp wskill.cpp @@ -93,7 +125,11 @@ wterra.cpp wterras.cpp - + + + xbrz.cpp + + libxbrzscale.cpp Main/Source Main/Include FeLib/Include + SDL2-2.0.4/include Makefile @@ -123,6 +160,7 @@ false + true @@ -130,6 +168,17 @@ ${MAKE} -f Makefile ${MAKE} -f Makefile clean + + + CURSEDDEVELOPER + DBGMSG + FELIST_WAITKEYUP + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + WIZARD + + . @@ -143,7 +192,14 @@ false false + + + + + + + true @@ -151,13 +207,19 @@ ${MAKE} -f Makefile ${MAKE} -f Makefile clean + + + CMakeFiles/CMakeTmp + + + + CMakeFiles/CMakeTmp + + CURSEDDEVELOPER DBGMSG FELIST_WAITKEYUP - FIX_LARGECREATURE_TELEPORT_GLITCH - UNIX - USE_SDL WIZARD @@ -167,165 +229,609 @@ ${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 . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + CMakeFiles/CMakeTmp + + + + + + + CMakeFiles/CMakeTmp + + + + + + + FeLib/Include + /usr/include/SDL2 + /usr/include/libpng16 + xbrzscale + xbrzscale/xbrz + FeLib + + + BACKTRACE + DATADIR="/home/teique/Projects/ivan/ivan.instDev.THE_MERGED_BRANCHES_I_AM_PLAYING/share" + GCC + IVAN_VERSION="0.58" + + + + + + + FeLib/Include + /usr/include/SDL2 + /usr/include/libpng16 + xbrzscale + xbrzscale/xbrz + FeLib + + + BACKTRACE + DATADIR="/home/teique/Projects/ivan/ivan.instDev.THE_MERGED_BRANCHES_I_AM_PLAYING/share" + GCC + IVAN_VERSION="0.58" + + + + + + + FeLib/Include + /usr/include/SDL2 + /usr/include/libpng16 + xbrzscale + xbrzscale/xbrz + FeLib + + + BACKTRACE + DATADIR="/home/teique/Projects/ivan/ivan.instDev.THE_MERGED_BRANCHES_I_AM_PLAYING/share" + GCC + IVAN_VERSION="0.58" + + + + + + + FeLib/Include + /usr/include/SDL2 + /usr/include/libpng16 + xbrzscale + xbrzscale/xbrz + FeLib + + + BACKTRACE + DATADIR="/home/teique/Projects/ivan/ivan.instDev.THE_MERGED_BRANCHES_I_AM_PLAYING/share" + GCC + IVAN_VERSION="0.58" + + + + + + + FeLib/Include + /usr/include/SDL2 + /usr/include/libpng16 + xbrzscale + xbrzscale/xbrz + FeLib + + + BACKTRACE + DATADIR="/home/teique/Projects/ivan/ivan.instDev.THE_MERGED_BRANCHES_I_AM_PLAYING/share" + GCC + IVAN_VERSION="0.58" + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + + + + + + + UNIX + USE_SDL + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + + + + + + + SDL2-2.0.4/include + /usr/include + + + CURSEDDEVELOPER + FIX_LARGECREATURE_TELEPORT_GLITCH + UNIX + USE_SDL + + + + + + + + + + + + + + + + + + diff --git a/.devsPrefs/AquariusPower/nbproject/project.xml b/.devsPrefs/AquariusPower/nbproject/project.xml index 491b5d418..3ae0b57ca 100644 --- a/.devsPrefs/AquariusPower/nbproject/project.xml +++ b/.devsPrefs/AquariusPower/nbproject/project.xml @@ -4,8 +4,8 @@ Ivan.github - - cc,cpp + c + cc,cpp,cxx h UTF-8 @@ -17,6 +17,7 @@ Main/Source Main/Include FeLib/Include + SDL2-2.0.4/include diff --git a/Doc/HowTo/DefinesValidator.txt b/Doc/HowTo/DefinesValidator.txt index 0b2e9b97e..cae7eb51b 100644 --- a/Doc/HowTo/DefinesValidator.txt +++ b/Doc/HowTo/DefinesValidator.txt @@ -16,3 +16,7 @@ or when someone changes a value only at `define.dat` or at some c++ .h file, so 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 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!" + +Ps.: alternatively (instead of using developer console), you can run this command line commands: +./ivan --defgen # that creates definesvalidator.h +./ivan --defval # to validate it diff --git a/FeLib/Include/festring.h b/FeLib/Include/festring.h index 3c1af2a54..5b4d93895 100644 --- a/FeLib/Include/festring.h +++ b/FeLib/Include/festring.h @@ -15,6 +15,7 @@ #include #include +#include #include "felibdef.h" @@ -119,6 +120,7 @@ class festring void ExtractWord(festring&); long GetCheckSum() const; void EnsureOwnsData(bool = false); + static pcre* CompilePCRE(pcre *pcreExistingRegexWorker, cfestring &fsPattern, festring *pfsErrorMsg=NULL); private: static void InstallIntegerMap(); static void DeInstallIntegerMap(); diff --git a/FeLib/Include/graphics.h b/FeLib/Include/graphics.h index 68ca890c8..ff17b6897 100644 --- a/FeLib/Include/graphics.h +++ b/FeLib/Include/graphics.h @@ -167,4 +167,4 @@ class graphics static rawbitmap* DefaultFont; }; -#endif +#endif //__GRAPHICS_H__ diff --git a/FeLib/Include/rawbit.h b/FeLib/Include/rawbit.h index 5d1df428b..d1d1b46ab 100644 --- a/FeLib/Include/rawbit.h +++ b/FeLib/Include/rawbit.h @@ -49,6 +49,7 @@ class rawbitmap cpackalpha* = 0, cuchar* = 0, cuchar* = 0, truth = true) const; v2 GetSize() const { return Size; } + v2 GetFontSize() { return v2FontSize; } void AlterGradient(v2, v2, int, int, truth); void SwapColors(v2, v2, int, int); @@ -74,6 +75,7 @@ class rawbitmap uchar* Palette; paletteindex** PaletteBuffer; fontcache FontCache; + v2 v2FontSize=v2(8,8); //TODO everywhere using 8 to calculate font size should use this variable, so one day we can have dynamic size font, after that we can set this var to any other proper value }; #endif diff --git a/FeLib/Source/bitmap.cpp b/FeLib/Source/bitmap.cpp index 5b0c69416..4500eb52e 100644 --- a/FeLib/Source/bitmap.cpp +++ b/FeLib/Source/bitmap.cpp @@ -865,6 +865,11 @@ void bitmap::DrawLine(int FromX, int FromY, v2 To, col16 Color, truth Wide) void bitmap::DrawLine(v2 From, v2 To, col16 Color, truth Wide) { DrawLine(From.X, From.Y, To.X, To.Y, Color, Wide); } +/** + * TODO this needs a fix: + * If a line is drawn from x,1 to anywhere and it is WIDE, the game will SEGFAULT. + * Every drawn dot must be checked if is inside the bitmap boundaries... + */ void bitmap::DrawLine(int OrigFromX, int OrigFromY, int OrigToX, int OrigToY, col16 Color, truth Wide) { if(OrigFromY == OrigToY) diff --git a/FeLib/Source/festring.cpp b/FeLib/Source/festring.cpp index 6d26138ce..bece51dc3 100644 --- a/FeLib/Source/festring.cpp +++ b/FeLib/Source/festring.cpp @@ -13,6 +13,8 @@ #include #include #include +#include + #include "festring.h" #include "allocate.h" #include "error.h" @@ -713,8 +715,13 @@ festring::sizetype festring::IgnoreCaseFind(cfestring& Where, return NPos; } -/* Replaces all occurances of What in Where after Begin with With */ - +/** + * Replaces all occurances of What in Where after Begin with With + * @param Where + * @param What + * @param With + * @param Begin + */ void festring::SearchAndReplace(festring& Where, cfestring& What, cfestring& With, sizetype Begin) { @@ -907,3 +914,43 @@ void festring::EnsureOwnsData(bool Unique) CreateOwnData(Data, Size); } } + +/** + * + * @param pcreExistingRegexWorker it has to be freed if was already set + * @param fsPattern + * @param bWarnOnError + * @return + */ +pcre* festring::CompilePCRE(pcre *pcreExistingRegexWorker, cfestring &fsPattern, festring *pfsErrorMsg) +{ + if(pcreExistingRegexWorker) + pcre_free(pcreExistingRegexWorker); + + if(fsPattern.IsEmpty()) + return NULL; + + const char *errMsg; + int iErrOffset; + pcreExistingRegexWorker = pcre_compile( + fsPattern.CStr(), + 0, // no options + &errMsg, &iErrOffset, + 0 // default char tables + ); + + if (!pcreExistingRegexWorker){ + festring fsErr; + fsErr<<"Regex validation failed, if ignored will just not work at all.\n" + <key.keysym.unicode; - if(!KeyPressed) return; #endif diff --git a/Graphics/Item.png b/Graphics/Item.png index 3451b7934..55202f613 100644 Binary files a/Graphics/Item.png and b/Graphics/Item.png differ diff --git a/Graphics/OLTerra.png b/Graphics/OLTerra.png index 16a67f697..43eba8cc3 100644 Binary files a/Graphics/OLTerra.png and b/Graphics/OLTerra.png differ diff --git a/Main/CMakeLists.txt b/Main/CMakeLists.txt index 193e505ee..a7f9789f4 100644 --- a/Main/CMakeLists.txt +++ b/Main/CMakeLists.txt @@ -28,8 +28,8 @@ set_source_files_properties( Source/smoke.cpp Source/square.cpp Source/stack.cpp Source/team.cpp Source/terra.cpp Source/trap.cpp Source/traps.cpp Source/worldmap.cpp Source/wsquare.cpp Source/wterra.cpp Source/wterras.cpp Source/hiteffect.cpp - Source/cmdcraft.cpp Source/cmdcraftfilters.cpp - Source/cmdswapweap.cpp + Source/cmdcraft.cpp Source/cmdcraftfilters.cpp Source/cmdswapweap.cpp + Source/curseddeveloper.cpp Source/wizautoplay.cpp PROPERTIES HEADER_FILE_ONLY TRUE) add_executable(ivan ${IVAN_SOURCES} Resource/Ivan.rc) diff --git a/Main/Include/char.h b/Main/Include/char.h index fe7140009..b0b7313b8 100644 --- a/Main/Include/char.h +++ b/Main/Include/char.h @@ -287,6 +287,8 @@ class character : public entity, public id public: friend class databasecreator; friend class corpse; + friend class curseddeveloper; + friend class wizautoplay; typedef characterprototype prototype; typedef characterdatabase database; character(); @@ -364,6 +366,7 @@ class character : public entity, public id void ActivateEquipmentState(long What) { EquipmentState |= What; } void DeActivateEquipmentState(long What) { EquipmentState &= ~What; } truth TemporaryStateIsActivated(long What) const; + truth HasStateFlag(long Flag); truth EquipmentStateIsActivated(long What) const { return EquipmentState & What; } truth StateIsActivated(long What) const; truth LoseConsciousness(int, truth = false); @@ -689,6 +692,7 @@ class character : public entity, public id void LycanthropyHandler(); void SearchingHandler(); void SaveLife(); + void SaveLifeBase(); void BeginInvisibility(); void BeginInfraVision(); void BeginESP(); @@ -1126,6 +1130,7 @@ class character : public entity, public id festring GetTrapDescription() const; truth TryToUnStickTraps(v2); void RemoveTrap(ulong); + truth ValidateTrapData(bool bForceNow = false); void AddTrap(ulong, ulong); truth IsStuckToTrap(ulong) const; void RemoveTraps(); @@ -1184,7 +1189,6 @@ class character : public entity, public id void SignalBurn(); void Extinguish(truth); truth IsBurnt() const; - truth IsPlayerAutoPlay(); truth CheckAIZapOpportunity(); int GetAdjustedStaminaCost(int, int); truth TryToStealFromShop(character*, item*); @@ -1192,6 +1196,7 @@ class character : public entity, public id void SetNewVomitMaterial(int What) { MyVomitMaterial = What; } festring GetHitPointDescription() const; truth WillGetTurnSoon() const; + truth OverloadedKickFailCheck(); virtual void SetFeedingSumo(truth What) { return; } protected: static truth DamageTypeDestroysBodyPart(int); @@ -1222,23 +1227,6 @@ class character : public entity, public id void StandIdleAI(); virtual void CreateCorpse(lsquare*); void GetPlayerCommand(); - - truth AutoPlayAICommand(int&); - truth AutoPlayAIPray(); - bool AutoPlayAIChkInconsistency(); - static void AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex=-1, bool bWide=false, bool bKeepColor=false); - static void AutoPlayAIDebugDrawOverlay(); - static bool AutoPlayAICheckAreaLevelChangedAndReset(); - truth AutoPlayAIDropThings(); - bool IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern); - truth AutoPlayAIEquipAndPickup(bool bPlayerHasLantern); - int AutoPlayAIFindWalkDist(v2 v2To); - truth AutoPlayAITestValidPathTo(v2 v2To); - truth AutoPlayAINavigateDungeon(bool bPlayerHasLantern); - truth AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo); - void AutoPlayAITeleport(bool bDeathCountBased); - void AutoPlayAIReset(bool bFailedToo); - virtual void GetAICommand(); truth MoveTowardsTarget(truth); virtual cchar* FirstPersonUnarmedHitVerb() const; @@ -1318,6 +1306,7 @@ class character : public entity, public id ulong WarnFlags; int ScienceTalks; trapdata* TrapData; + long lTmpLastValidateTrapDataTurn; //performace wise, no need to store expmodifiermap ExpModifierMap; int CounterToMindWormHatch; ulong MemorizedEquippedItemIDs[MAX_EQUIPMENT_SLOTS]; diff --git a/Main/Include/command.h b/Main/Include/command.h index 3fe8e080c..ec1ec1741 100644 --- a/Main/Include/command.h +++ b/Main/Include/command.h @@ -74,6 +74,7 @@ class commandsystem static truth Open(character*); static truth PickUp(character*); static truth Pray(character*); + static truth AskFavour(character*); static truth Craft(character*); static truth Quit(character*); static truth Read(character*); diff --git a/Main/Include/craft.h b/Main/Include/craft.h index 61b9a07f7..cf5e24039 100644 --- a/Main/Include/craft.h +++ b/Main/Include/craft.h @@ -229,6 +229,7 @@ class recipedata { v2 v2TailoringWorkbenchLocation; long lDamageFinalItem; + long lInitialTurn; public: recipedata(humanoid* H=NULL,uint sel=FELIST_ERROR_BIT); diff --git a/Main/Include/curseddeveloper.h b/Main/Include/curseddeveloper.h new file mode 100644 index 000000000..6b25a07c3 --- /dev/null +++ b/Main/Include/curseddeveloper.h @@ -0,0 +1,56 @@ +/* + * + * 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 __CURSEDDEVELOPER_H__ +#define __CURSEDDEVELOPER_H__ + +class bodypart; +class character; +class festring; + +class curseddeveloper { + public: + static bool IsCursedDeveloper(); + +#ifdef CURSEDDEVELOPER + public: + static void Init(); + static bool IsCursedDeveloperTeleport(){return bCursedDeveloperTeleport;} + static bool LifeSaveJustABitIfRequested(); + static bool LifeSaveJustABit(character* Killer); + static long UpdateKillCredit(character* Victim=NULL,int iMod=0); + static long ModKillCredit(int i){UpdateKillCredit(NULL,i);return lKillCredit;} + static long GetKillCredit(){return lKillCredit;} + static void RequestResurrect(character* C){if(!Killer)Killer=C;bResurrect=true;} + protected: + static bool BuffAndDebuffPlayerKiller(character* Killer,int& riBuff,int& riDebuff,bool& rbRev); + static void RestoreLimbs(festring fsCmdParams = CONST_S("")); + static bool HealTorso(bodypart* bp); + static bool HealBP(int iIndex,int iMode,int iMinHpOK=0); + static bool CreateBP(int iIndex); + static void NightmareWakeUp(character* P); + static void ResetKillCredit(festring fsCmdParams = CONST_S("")); + private: + static bool bCursedDeveloper; + static bool bCursedDeveloperTeleport; + static bool bInit; + static long lKillCredit; + static bool bNightmareWakeUp; + static bool bResurrect; + static character* Killer; +#else + public: + static bool IsCursedDeveloperTeleport(){return false;} +#endif +}; + +#endif //__CURSEDDEVELOPER_H__ diff --git a/Main/Include/definesvalidator.h b/Main/Include/definesvalidator.h index 9957597d9..79b5d6913 100644 --- a/Main/Include/definesvalidator.h +++ b/Main/Include/definesvalidator.h @@ -9473,10 +9473,10 @@ class definesvalidator{ #ifdef WEAPON_SKILL_CATEGORIES // DO NOT MODIFY! - bsA = 11; + bsA = 12; bsB = WEAPON_SKILL_CATEGORIES; if(bsA!=bsB) - ssErrors << "Defined WEAPON_SKILL_CATEGORIES with value 11 from .dat file mismatches hardcoded c++ define value of " << WEAPON_SKILL_CATEGORIES << "!" << std::endl; + ssErrors << "Defined WEAPON_SKILL_CATEGORIES with value 12 from .dat file mismatches hardcoded c++ define value of " << WEAPON_SKILL_CATEGORIES << "!" << std::endl; #endif diff --git a/Main/Include/fluid.h b/Main/Include/fluid.h index 26059fe29..91a3b41d4 100644 --- a/Main/Include/fluid.h +++ b/Main/Include/fluid.h @@ -20,6 +20,8 @@ class bitmap; typedef truth (rawbitmap::*pixelpredicate)(v2) const; +class liquid; + class fluid : public entity { public: @@ -56,7 +58,7 @@ class fluid : public entity cfestring& GetLocationName() const { return LocationName; } truth IsInside() const { return Flags & FLUID_INSIDE; } truth UseImage() const; - virtual int GetTrapType() const { return Liquid->GetType() | FLUID_TRAP; } + virtual int GetTrapType() const; virtual ulong GetTrapID() const { return TrapData.TrapID; } virtual ulong GetVictimID() const { return TrapData.VictimID; } virtual void AddTrapName(festring&, int) const; diff --git a/Main/Include/game.h b/Main/Include/game.h index b497cd938..1effe765e 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -182,6 +182,9 @@ class game static void UpdateSRegionsXBRZ(bool bIsXBRZScale); static void RegionSilhouetteEnable(bool b); static void RegionListItemEnable(bool b); + static void SetDropTag(item* it); + static cchar* StoreMatchNameKey(item* it,bool bUnarticled=false); + static void AutoStoreItemInContainer(item* itToStore,character* C); static void UpdatePosAroundForXBRZ(v2 ScreenPos); static void SRegionAroundDisable(); static void SRegionAroundAllow(); @@ -280,6 +283,7 @@ class game static int GetDirectionForVector(v2); static int GetPlayerAlignment(); static cchar* GetVerbalPlayerAlignment(); + static int GetGodAlignmentVsPlayer(god* G); static void CreateGods(); static int GetScreenXSize(); static int GetScreenYSize(); @@ -393,7 +397,7 @@ class game static bool ToggleShowMapNotes(); static bool CheckAddAutoMapNote(square* =NULL); static int CheckAutoPickup(square* sqr = NULL); - static void UpdateAutoPickUpMatching(); + static void UpdateAutoPickUpRegex(); static int RotateMapNotes(); static char MapNoteToken(); static bool IsAutoPickupMatch(cfestring fsName); @@ -401,10 +405,6 @@ class game #ifdef WIZARD static void ActivateWizardMode() { WizardMode = true; } static truth WizardModeIsActive() { return WizardMode; } - static void IncAutoPlayMode(); - static int GetAutoPlayMode() { return AutoPlayMode; } - static void AutoPlayModeApply(); - static void DisableAutoPlayMode() {AutoPlayMode=0;AutoPlayModeApply();} static void SeeWholeMap(); static int GetSeeWholeMapCheatMode() { return SeeWholeMapCheatMode; } static truth GoThroughWallsCheatIsActive() { return GoThroughWallsCheat; } @@ -413,7 +413,6 @@ class game static truth WizardModeIsActive() { return false; } static int GetSeeWholeMapCheatMode() { return 0; } static truth GoThroughWallsCheatIsActive() { return false; } - static int GetAutoPlayMode() { return 0; } #endif static truth WizardModeIsReallyActive() { return WizardMode; } @@ -587,7 +586,6 @@ class game static long PetMassacreAmount; static long MiscMassacreAmount; static truth WizardMode; - static int AutoPlayMode; static int SeeWholeMapCheatMode; static truth GoThroughWallsCheat; static int QuestMonstersFound; diff --git a/Main/Include/god.h b/Main/Include/god.h index febaa873d..deaecc0b6 100644 --- a/Main/Include/god.h +++ b/Main/Include/god.h @@ -38,6 +38,7 @@ class godprototype cchar* ClassID; }; +typedef bool (*CallFavourType)(god*); class god { public: @@ -83,13 +84,23 @@ class god virtual int GetSex() const = 0; void SignalRandomAltarGeneration(const std::vector&); virtual truth LikesVomit() const { return false; } - protected: + virtual bool Favour(int iWhat, int iDebit=0); + static festring GetFavourName(int iID); + const std::vector GetKnownSpells() const { return knownSpellsID; } + bool CallFavour(CallFavourType call, int iCallFavour, int iWhat, int iDebit, int iDbtDefault); +protected: virtual void PrayGoodEffect() = 0; virtual void PrayBadEffect() = 0; int Relation, LastPray; festring fsLastKnownRelation; long Timer; truth Known; + + static std::vector> vFavID; + static void AddFavourID(int i,festring fs); + static void FavourInit(); + int CalcDebit(int iDebit,int iDefault); + std::vector knownSpellsID; }; #ifdef __FILE_OF_STATIC_GOD_PROTOTYPE_DEFINITIONS__ diff --git a/Main/Include/gods.h b/Main/Include/gods.h index db3e5663e..502e87670 100644 --- a/Main/Include/gods.h +++ b/Main/Include/gods.h @@ -15,6 +15,10 @@ #include "god.h" +#define FAVOURDEBIT_AUTO -1 +#define FAVOURDEBIT_AUTOHALF -2 +#define FAVOURDEBIT_AUTODOUBLE -3 + GOD(valpurus, god) { public: @@ -29,6 +33,7 @@ GOD(valpurus, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(legifer, god) @@ -44,6 +49,7 @@ GOD(legifer, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(atavus, god) @@ -60,6 +66,7 @@ GOD(atavus, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(dulcis, god) @@ -75,6 +82,7 @@ GOD(dulcis, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(seges, god) @@ -93,6 +101,7 @@ GOD(seges, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(sophos, god) @@ -108,6 +117,7 @@ GOD(sophos, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(silva, god) @@ -123,6 +133,7 @@ GOD(silva, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(loricatus, god) @@ -138,6 +149,7 @@ GOD(loricatus, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(mellis, god) @@ -153,6 +165,7 @@ GOD(mellis, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(cleptia, god) @@ -168,6 +181,7 @@ GOD(cleptia, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(nefas, god) @@ -183,6 +197,7 @@ GOD(nefas, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(scabies, god) @@ -202,6 +217,7 @@ GOD(scabies, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(infuscor, god) @@ -217,6 +233,7 @@ GOD(infuscor, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(cruentus, god) @@ -232,6 +249,7 @@ GOD(cruentus, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; GOD(mortifer, god) @@ -248,6 +266,7 @@ GOD(mortifer, god) protected: virtual void PrayGoodEffect(); virtual void PrayBadEffect(); + virtual bool Favour(int iWhat, int iDebit=0); }; #endif diff --git a/Main/Include/human.h b/Main/Include/human.h index f6e7cd791..8083072ec 100644 --- a/Main/Include/human.h +++ b/Main/Include/human.h @@ -172,7 +172,6 @@ CHARACTER(humanoid, character) truth HasSadistWeapon() const; truth CheckAIZapOpportunity(); virtual truth HasSadistAttackMode() const; - truth AutoPlayAIequip(); static v2 GetSilhouetteWhere(){return SilhouetteWhere;} static v2 GetSilhouetteWhereDefault(){return SilhouetteWhereDefault;} static void SetSilhouetteWhere(v2 pos){SilhouetteWhere=pos;} diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index 47e387514..faef7c2f3 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -25,6 +25,8 @@ class ivanconfig static cfestring& GetSelectedBkgColor() { return SelectedBkgColor.Value; } static cfestring& GetAutoPickUpMatching() { return AutoPickUpMatching.Value; } static truth IsAllWeightIsRelevant() { return AllWeightIsRelevant.Value; } + static truth IsDropBeforeOffering() { return DropBeforeOffering.Value; } + static truth IsAllowContrastBackground() { return AllowContrastBackground.Value; }; static long GetAutoSaveInterval() { return AutoSaveInterval.Value; } static long GetContrast() { return Contrast.Value; } static long GetHitIndicator() { return HitIndicator.Value; } @@ -42,11 +44,12 @@ class ivanconfig static int GetAltSilhouettePreventColorGlitch(){return AltSilhouettePreventColorGlitch.Value;} static int GetShowMap(){return ShowMap.Value;} static truth IsShowMapAtDetectMaterial() { return ShowMapAtDetectMaterial.Value; } + static truth IsOverloadedFight() { return OverloadedFight.Value; } static truth IsTransparentMapLM() { return TransparentMapLM.Value; } static truth IsWaitNeutralsMoveAway() { return WaitNeutralsMoveAway.Value; } static truth IsEnhancedLights() { return EnhancedLights.Value; } static int GetMemorizeEquipmentMode() { return 2; /*MemorizeEquipmentMode.Value;*/ } - static int GetDistLimitMagicMushrooms() { return DistLimitMagicMushrooms.Value * 4; } + static int GetDistLimitMagicMushrooms() { return DistLimitMagicMushrooms.Value; } static truth IsShowFullDungeonName() { return ShowFullDungeonName.Value; } static truth IsCenterOnPlayerAfterLook(){ return CenterOnPlayerAfterLook.Value; } static truth IsShowGodInfo(){ return ShowGodInfo.Value; } @@ -77,6 +80,7 @@ class ivanconfig static long GetVolume() { return Volume.Value; } static long GetSfxVolume() { return SfxVolume.Value; } static long GetMIDIOutputDevice() { return MIDIOutputDevice.Value; } + static col16 CheckChangeColor(col16 col); static truth GetExtraMenuGraphics(){ return UseExtraMenuGraphics.Value; } static v2 GetWorldSizeConfig(); static int GetLandTypeConfig() { return LandTypeConfig.Value; } @@ -171,6 +175,7 @@ class ivanconfig static void FullScreenModeChanger(truthoption*, truth); #endif + static void EnableSFX(truthoption* O, truth What); static void DungeonGfxScaleDisplayer(const cycleoption*, festring&); static void FontGfxDisplayer(const cycleoption*, festring&); static void SilhouetteScaleDisplayer(const cycleoption* O, festring& Entry); @@ -217,9 +222,12 @@ class ivanconfig static cycleoption AltSilhouettePreventColorGlitch; static cycleoption ShowMap; static truthoption ShowMapAtDetectMaterial; + static truthoption OverloadedFight; static truthoption TransparentMapLM; static truthoption WaitNeutralsMoveAway; static truthoption AllWeightIsRelevant; + static truthoption DropBeforeOffering; + static truthoption AllowContrastBackground; static truthoption ShowVolume; static truthoption EnhancedLights; diff --git a/Main/Include/id.h b/Main/Include/id.h index fa46b4d5b..8cd94bf88 100644 --- a/Main/Include/id.h +++ b/Main/Include/id.h @@ -27,6 +27,7 @@ class id virtual festring GetName(int, int) const; virtual void AddName(festring&, int) const; virtual festring GetName(int) const; + virtual festring GetNameToMatch() const; cchar* GetArticle() const { return UsesLongArticle() ? "an" : "a"; } protected: virtual cfestring& GetNameSingular() const = 0; diff --git a/Main/Include/ivandef.h b/Main/Include/ivandef.h index 4fedf0080..cca2313b7 100644 --- a/Main/Include/ivandef.h +++ b/Main/Include/ivandef.h @@ -233,13 +233,15 @@ const name##prototype name::ProtoType #define ACM 9 #define ACMM 10 -#define UNARTICLED 0 -#define PLURAL 1 -#define ARTICLE_BIT 2 -#define DEFINITE 2 -#define INDEFINE_BIT 4 -#define INDEFINITE 6 -#define STRIPPED 8 +#define UNARTICLED 0b00000000 +#define PLURAL 0b00000001 +#define ARTICLE_BIT 0b00000010 +#define DEFINITE 0b00000010 +#define INDEFINE_BIT 0b00000100 +#define INDEFINITE 0b00000110 +#define STRIPPED 0b00001000 +#define UNLABELED 0b00010000 +#define NOPOSTFIX 0b00100000 #define TRANSPARENT_COLOR 0xF81F // pink @@ -1184,6 +1186,7 @@ cv2 TILE_V2(TILE_SIZE, TILE_SIZE); #define SQUARE_INDEX_MASK 0xFFFF #define ALLOW_ANIMATE 0x10000 #define ALLOW_ALPHA 0x20000 +#define ALLOW_CONTRAST 0x40000 #define TALENTS 5 diff --git a/Main/Include/miscitem.h b/Main/Include/miscitem.h index 1b62ab8cf..3e4c6bad9 100644 --- a/Main/Include/miscitem.h +++ b/Main/Include/miscitem.h @@ -203,6 +203,7 @@ ITEM(scrollofearthquake, scroll) { public: virtual void FinishReading(character*); + static void EarthQuakeMagic(festring fsMsgHitNPC = cfestring()); }; ITEM(scrollofbodyswitch, scroll) @@ -363,14 +364,16 @@ ITEM(stone, item) virtual truth WeightIsIrrelevant() const { return true; } }; -//ITEM(ingot, item) -//{ -// public: -// virtual long GetTruePrice() const; -// virtual truth IsLuxuryItem(ccharacter*) const { return GetTruePrice() > 0; } -// protected: -// virtual truth WeightIsIrrelevant() const { return true; } -//}; +/* +ITEM(nail, item) +{ + public: + virtual long GetTruePrice() const; + virtual truth IsLuxuryItem(ccharacter*) const { return GetTruePrice() > 0; } + protected: + virtual truth WeightIsIrrelevant() const { return true; } +}; +*/ ITEM(scrolloftaming, scroll) { @@ -442,6 +445,7 @@ ITEM(itemcontainer, lockableitem) public: itemcontainer(); itemcontainer(const itemcontainer&); + static truth OpenGeneric(character* Opener, stack* Stk, festring fsName, long volume, ulong ID); virtual ~itemcontainer(); virtual truth Open(character*); virtual void Load(inputfile&); @@ -469,10 +473,14 @@ ITEM(itemcontainer, lockableitem) virtual void SetParameters(int); virtual void Disappear(); virtual stack* GetContained() const { return Contained; } + virtual void SetLabel(cfestring& What); + truth IsAutoStoreMatch(cfestring fs); protected: virtual col16 GetMaterialColorB(int) const; virtual void PostConstruct(); stack* Contained; + pcre* pcreAutoStoreRegex; + bool bLazyInitPcre; }; ITEM(beartrap, itemtrap) diff --git a/Main/Include/wizautoplay.h b/Main/Include/wizautoplay.h new file mode 100644 index 000000000..e2ef17441 --- /dev/null +++ b/Main/Include/wizautoplay.h @@ -0,0 +1,66 @@ +/* + * + * 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 __WIZAUTOPLAY_H__ +#define __WIZAUTOPLAY_H__ + +#define AUTOPLAYMODE_DISABLED 0 +#define AUTOPLAYMODE_NOTIMEOUT 1 +#define AUTOPLAYMODE_SLOW 2 +#define AUTOPLAYMODE_FAST 3 +#define AUTOPLAYMODE_FRENZY 4 + +class wizautoplay +{ + public: + static int GetMaxValueless(){return iMaxValueless;} + private: + static int iMaxValueless; + +#ifdef WIZARD + public: + static void AutoPlayCommandKey(character* C,int& Key,truth& HasActed,truth& ValidKeyPressed); + static truth AutoPlayAICommand(int&); + static truth AutoPlayAIPray(); + static bool AutoPlayAIChkInconsistency(); + static void AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex=-1, bool bWide=false, bool bKeepColor=false); + static void AutoPlayAIDebugDrawOverlay(); + static bool AutoPlayAICheckAreaLevelChangedAndReset(); + static truth AutoPlayAIcanApply(item* it); + static truth AutoPlayAIDropThings(); + static bool IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern); + static truth AutoPlayAIEquipAndPickup(bool bPlayerHasLantern); + static int AutoPlayAIFindWalkDist(v2 v2To); + static truth AutoPlayAITestValidPathTo(v2 v2To); + static truth AutoPlayAINavigateDungeon(bool bPlayerHasLantern); + static truth AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo); + static void AutoPlayAITeleport(bool bDeathCountBased); + static void AutoPlayAIReset(bool bFailedToo); + static truth AutoPlayAIequipConsumeZapReadApply(); + static void IncAutoPlayMode(); + static void AutoPlayModeApply(); + static void DisableAutoPlayMode() {AutoPlayMode=AUTOPLAYMODE_DISABLED;AutoPlayModeApply();} + + static truth IsPlayerAutoPlay(character* C); + static int GetAutoPlayMode() { return AutoPlayMode; } + private: + static truth IsPlayerAutoPlay(){return IsPlayerAutoPlay(P);}; + static character* P; + static int AutoPlayMode; +#else + public: + static truth IsPlayerAutoPlay(character* C){return false;} + static int GetAutoPlayMode() { return AUTOPLAYMODE_DISABLED; } +#endif +}; + +#endif //__WIZAUTOPLAY_H__ diff --git a/Main/Source/bodypart.cpp b/Main/Source/bodypart.cpp index 7ddf08815..47cac8992 100644 --- a/Main/Source/bodypart.cpp +++ b/Main/Source/bodypart.cpp @@ -13,6 +13,7 @@ /* Compiled through itemset.cpp */ #include "dbgmsgproj.h" +#include "curseddeveloper.h" int bodypart::GetGraphicsContainerIndex() const { return GR_HUMANOID; } int bodypart::GetArticleMode() const { return IsUnique() ? FORCE_THE : 0; } @@ -291,7 +292,7 @@ truth bodypart::ReceiveDamage(character* Damager, int Damage, int Type, int Dire else ADD_MESSAGE("Your %s is in very bad condition.", GetBodyPartName().CStr()); - if(Master->BodyPartIsVital(GetBodyPartIndex())) + if(Master->BodyPartIsVital(GetBodyPartIndex()) && !curseddeveloper::IsCursedDeveloper()) game::AskForKeyPress(CONST_S("Vital bodypart in serious danger! [press any key to continue]")); } else if(IsBadlyHurt() && !WasBadlyHurt) @@ -301,7 +302,7 @@ truth bodypart::ReceiveDamage(character* Damager, int Damage, int Type, int Dire else ADD_MESSAGE("Your %s is in bad condition.", GetBodyPartName().CStr()); - if(Master->BodyPartIsVital(GetBodyPartIndex())) + if(Master->BodyPartIsVital(GetBodyPartIndex()) && !curseddeveloper::IsCursedDeveloper()) game::AskForKeyPress(CONST_S("Vital bodypart in danger! [press any key to continue]")); } } @@ -3537,7 +3538,7 @@ void bodypart::UpdateFlags() else Flags &= ~BADLY_HURT; - if(Master->BodyPartIsStuck(GetBodyPartIndex())) + if(Master->ValidateTrapData() && Master->BodyPartIsStuck(GetBodyPartIndex())) Flags |= STUCK; else Flags &= ~STUCK; diff --git a/Main/Source/char.cpp b/Main/Source/char.cpp index a263c8238..246478bfc 100644 --- a/Main/Source/char.cpp +++ b/Main/Source/char.cpp @@ -23,10 +23,6 @@ * These flags can be found in ivandef.h. RANDOMIZABLE sets all source * & duration flags at once. */ -#include "hiteffect.h" //TODO move to charsset.cpp? -#include "lterras.h" -#include "gods.h" - //#define DBGMSG_V2 #include "dbgmsgproj.h" #include @@ -541,7 +537,8 @@ character::character(ccharacter& Char) Stamina(Char.Stamina), MaxStamina(Char.MaxStamina), BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger), CommandFlags(Char.CommandFlags), WarnFlags(0), - ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0) + ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0), + lTmpLastValidateTrapDataTurn(-1) { Flags &= ~C_PLAYER; Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE; @@ -600,7 +597,8 @@ character::character() Money(0), Action(0), MotherEntity(0), PolymorphBackup(0), EquipmentState(0), SquareUnder(0), RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0), BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER), - WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0) + WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0), + lTmpLastValidateTrapDataTurn(-1) { Stack = new stack(0, this, HIDDEN); @@ -769,9 +767,20 @@ int character::TakeHit(character* Enemy, item* Weapon, /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */ - if(RAND() % int(100 + ToHitValue / DodgeValue * (100 + Success)) < 100 - && !Critical && !ForceHit) - { + /** + * SIGFPE happened once when: + * ToHitValue = -2.1331964070645735 (why < 0 ???); + * DodgeValue = 2.3094010767585029; + * Success = 8; + * result thru linux `bc <<< "scale=16;100 + -2.1331964070645735/2.3094010767585029 * (100+8)"` + * was = .2402768919010060 + */ + int a = int(100 + ToHitValue / DodgeValue * (100 + Success)); + if( + ((a>=-1 && a<=1) || ((RAND() % a) < 100)) && + !Critical && + !ForceHit + ){ Enemy->AddMissMessage(this); EditExperience(AGILITY, 150, 1 << 7); EditExperience(PERCEPTION, 75, 1 << 7); @@ -1213,7 +1222,7 @@ void character::Move(v2 MoveTo, truth TeleportMove, truth Run) void character::GetAICommand() { - if(!IsPlayerAutoPlay()){ + if(!wizautoplay::IsPlayerAutoPlay(this)){ SeekLeader(GetLeader()); if(FollowLeader(GetLeader())) @@ -1505,7 +1514,7 @@ truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWai { /* not sure if this is better than "the door is locked", but I guess it _might_ be slightly better */ ADD_MESSAGE("The %s is locked.", Terrain->GetNameSingular().CStr()); - if(!IsPlayerAutoPlay())return false; + if(!wizautoplay::IsPlayerAutoPlay(this))return false; } if(Important && CheckKick()) @@ -1555,6 +1564,7 @@ truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWai } else { + ValidateTrapData(); if(IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ") + game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()) @@ -1702,34 +1712,6 @@ void character::CreateCorpse(lsquare* Square) SendToHell(); } -bool bSafePrayOnce=false; -void character::AutoPlayAITeleport(bool bDeathCountBased) -{ - bool bTeleportNow=false; - - if(bDeathCountBased){ // this is good to prevent autoplay AI getting stuck endless dieing - static int iDieMax=10; - static int iDieTeleportCountDown=iDieMax; - if(iDieTeleportCountDown==0){ //this helps on defeating not so strong enemies in spot - if(IsPlayerAutoPlay()) - bTeleportNow=true; - iDieTeleportCountDown=iDieMax; - bSafePrayOnce=true; - }else{ - static v2 v2DiePos(0,0); - if(v2DiePos==GetPos()){ - iDieTeleportCountDown--; - }else{ - v2DiePos=GetPos(); - iDieTeleportCountDown=iDieMax; - } - } - } - - if(bTeleportNow) - Move(GetLevel()->GetRandomSquare(this), true); //not using teleport function to avoid prompts, but this code is from there TODO and should be in sync! create TeleportRandomDirectly() ? -} - void character::Die(ccharacter* Killer, cfestring& Msg, ulong DeathFlags) { /* Note: This function musn't delete any objects, since one of these may be @@ -1738,38 +1720,46 @@ void character::Die(ccharacter* Killer, cfestring& Msg, ulong DeathFlags) if(!IsEnabled()) return; +#ifdef CURSEDDEVELOPER + if(Killer && Killer->IsPlayer()){ + if(!game::WizardModeIsReallyActive()) + curseddeveloper::UpdateKillCredit(this); + } +#endif + RemoveTraps(); - + if(IsPlayer()) { ADD_MESSAGE("You die."); - + +#ifdef CURSEDDEVELOPER + if(!game::WizardModeIsReallyActive() && curseddeveloper::IsCursedDeveloper()){ + curseddeveloper::RequestResurrect((character*)Killer); + return; + } +#endif + +#ifdef WIZARD if(game::WizardModeIsActive()) { game::DrawEverything(); bool bInstaResurrect=false; - if(!bInstaResurrect && IsPlayerAutoPlay())bInstaResurrect=true; + if(!bInstaResurrect && wizautoplay::IsPlayerAutoPlay(this))bInstaResurrect=true; if(!bInstaResurrect && !game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER))bInstaResurrect=true; if(bInstaResurrect) { - RestoreBodyParts(); - ResetSpoiling(); - if(IsBurning()) - { - doforbodypartswithparam()(this, &bodypart::Extinguish, false); - doforbodyparts()(this, &bodypart::ResetThermalEnergies); - doforbodyparts()(this, &bodypart::ResetBurning); - } - RestoreHP(); - RestoreStamina(); - ResetStates(); - SetNP(SATIATED_LEVEL); - SendNewDrawRequest(); - if(IsPlayerAutoPlay())AutoPlayAITeleport(true); + SaveLifeBase(); + + if(wizautoplay::IsPlayerAutoPlay(this)) + wizautoplay::AutoPlayAITeleport(true); + return; } } +#endif + } else if(CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG)) ProcessAndAddMessage(GetDeathMessage()); @@ -2564,36 +2554,38 @@ truth character::TestForPickup(item* ToBeTested) const void character::AddScoreEntry(cfestring& Description, double Multiplier, truth AddEndLevel) const { - if(!game::WizardModeIsReallyActive()) - { - highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME); - - if(!HScore.CheckVersion()) - { - if(game::Menu(std::vector(), v2(RES.X >> 1, RES.Y >> 1), - CONST_S("The highscore version doesn't match.\rDo you want to erase " - "previous records and start a new file?\rNote, if you answer " - "no, the score of your current game will be lost!\r"), - CONST_S("Yes\rNo\r"), LIGHT_GRAY)) - return; + if(game::WizardModeIsReallyActive()) + return; + if(curseddeveloper::IsCursedDeveloper()) + return; + + highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME); - HScore.Clear(); - } + if(!HScore.CheckVersion()) + { + if(game::Menu(std::vector(), v2(RES.X >> 1, RES.Y >> 1), + CONST_S("The highscore version doesn't match.\rDo you want to erase " + "previous records and start a new file?\rNote, if you answer " + "no, the score of your current game will be lost!\r"), + CONST_S("Yes\rNo\r"), LIGHT_GRAY)) + return; - festring Desc = game::GetPlayerName(); - Desc << ", " << Description; + HScore.Clear(); + } - if(AddEndLevel) - { - if(game::IsInWilderness()) - Desc << " in the wilderness"; - else - Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()); - } + festring Desc = game::GetPlayerName(); + Desc << ", " << Description; - HScore.Add(long(game::GetScore() * Multiplier), Desc); - HScore.Save(); + if(AddEndLevel) + { + if(game::IsInWilderness()) + Desc << " in the wilderness"; + else + Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()); } + + HScore.Add(long(game::GetScore() * Multiplier), Desc); + HScore.Save(); } truth character::CheckDeath(cfestring& Msg, ccharacter* Murderer, ulong DeathFlags) @@ -2622,6 +2614,7 @@ truth character::CheckDeath(cfestring& Msg, ccharacter* Murderer, ulong DeathFla ++SpecifierParts; } + ValidateTrapData(); if(!(DeathFlags & IGNORE_TRAPS) && IsStuck()) { if(SpecifierParts++) @@ -2734,923 +2727,6 @@ truth character::DodgesFlyingItem(item* Item, double ToHitValue) return !Item->EffectIsGood() && RAND() % int(100 + ToHitValue / DodgeValue * 100) < 100; } -character* AutoPlayLastChar=NULL; -const int iMaxWanderTurns=20; -const int iMinWanderTurns=3; - -/** - * 5 seems good, broken cheap weapons, stones, very cheap weapons non broken etc - * btw, lantern price is currently 10. - */ -static int iMaxValueless = 5; - -v2 v2KeepGoingTo=v2(0,0); -v2 v2TravelingToAnotherDungeon=v2(0,0); -int iWanderTurns=iMinWanderTurns; -bool bAutoPlayUseRandomNavTargetOnce=false; -std::vector vv2DebugDrawSqrPrevious; -v2 v2LastDropPlayerWasAt=v2(0,0); -std::vector vv2FailTravelToTargets; -std::vector vv2WrongGoingTo; - -void character::AutoPlayAIReset(bool bFailedToo) -{ DBG7(bFailedToo,iWanderTurns,DBGAV2(v2KeepGoingTo),DBGAV2(v2TravelingToAnotherDungeon),DBGAV2(v2LastDropPlayerWasAt),vv2FailTravelToTargets.size(),vv2DebugDrawSqrPrevious.size()); - v2KeepGoingTo=v2(0,0); //will retry - v2TravelingToAnotherDungeon=v2(0,0); - iWanderTurns=0; // warning: this other code was messing the logic ---> if(iWanderTurnsTerminateGoingTo(); - - if(bFailedToo){ - vv2FailTravelToTargets.clear(); - vv2WrongGoingTo.clear(); - } -} -truth character::AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo) -{ - v2KeepGoingTo=v2KGTo; - - bool bOk=true; - - if(bOk){ - lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo); - if(!CanTheoreticallyMoveOn(lsqr)) - bOk=false; -// olterrain* olt = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo)->GetOLTerrain(); -// if(olt){ -// if(bOk && !CanMoveOn(olt)){ -// DBG4(DBGAV2(v2KeepGoingTo),"olterrain? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr()); -// bOk=false; -// } -// -// /**** -// * keep these commented for awhile, may be useful later -// * -// if(bOk && olt->IsWall()){ //TODO this may be unnecessary cuz of above test -// //TODO is this a bug in the CanMoveOn() code? navigation AI is disabled when player is ghost TODO confirm about ethereal state, ammy of phasing -// DBG4(DBGAV2(v2KeepGoingTo),"walls? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr()); -// bOk=false; -// } -// -// if(bOk && (olt->GetWalkability() & ETHEREAL)){ //TODO this may be too much unnecessary test -// bOk=false; -// } -// */ -// } - } - - if(bOk){ - SetGoingTo(v2KeepGoingTo); DBG3(DBGAV2(GetPos()),DBGAV2(GoingTo),DBGAV2(v2KeepGoingTo)); - CreateRoute(); - if(Route.empty()){ - TerminateGoingTo(); //redundant? - bOk=false; - } - } - - if(!bOk){ - DBG1("RouteCreationFailed"); - vv2FailTravelToTargets.push_back(v2KeepGoingTo); DBG3("BlockGoToDestination",DBGAV2(v2KeepGoingTo),vv2FailTravelToTargets.size()); - bAutoPlayUseRandomNavTargetOnce=true; - - AutoPlayAIReset(false); //v2KeepGoingTo is reset here too - } - - return bOk; -} - -void character::AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex, bool bWide, bool bKeepColor) -{ - static v2 v2ScrPos=v2(0,0); //static to avoid instancing - static int iAddPos;iAddPos=bWide?2:1; - static int iSubBorder;iSubBorder=bWide?3:2; - if(game::OnScreen(v2SqrPos)){ - v2ScrPos=game::CalculateScreenCoordinates(v2SqrPos); - - DOUBLE_BUFFER->DrawRectangle( - v2ScrPos.X+iAddPos, v2ScrPos.Y+iAddPos, - v2ScrPos.X+TILE_SIZE-iSubBorder, v2ScrPos.Y+TILE_SIZE-iSubBorder, - color, bWide); - - if(iPrintIndex>-1) - FONT->Printf(DOUBLE_BUFFER, v2(v2ScrPos.X+1,v2ScrPos.Y+5), DARK_GRAY, "%d", iPrintIndex); - - if(!bKeepColor) - vv2DebugDrawSqrPrevious.push_back(v2SqrPos); - } -} - -const int iVisitAgainMax=10; -int iVisitAgainCount=iVisitAgainMax; -std::vector vv2AllDungeonSquares; -bool character::AutoPlayAICheckAreaLevelChangedAndReset() -{ - static area* areaPrevious=NULL; - area* Area = game::GetCurrentArea(); - if(Area != areaPrevious){ - areaPrevious=Area; - - iVisitAgainCount=iVisitAgainMax; - - vv2DebugDrawSqrPrevious.clear(); - - vv2AllDungeonSquares.clear(); - if(!game::IsInWilderness()) - for(int iY=0;iYGetYSize();iY++){ for(int iX=0;iXGetXSize();iX++){ - vv2AllDungeonSquares.push_back(game::GetCurrentLevel()->GetLSquare(iX, iY)); - }} - - return true; - } - - return false; -} - -void character::AutoPlayAIDebugDrawOverlay() -{ - if(!game::WizardModeIsActive())return; - - AutoPlayAICheckAreaLevelChangedAndReset(); - - // redraw previous to clean them - area* Area = game::GetCurrentArea(); //got the Area to draw in the wilderness too and TODO navigate there one day - std::vector vv2DebugDrawSqrPreviousCopy(vv2DebugDrawSqrPrevious); - for(int i=0;iGetSquare(vv2DebugDrawSqrPrevious[i])->SendNewDrawRequest(); -// square* sqr = Area->GetSquare(vv2DebugDrawSqrPrevious[i]); -// if(sqr)sqr->SendStrongNewDrawRequest(); //TODO sqr NULL? - AutoPlayAIDebugDrawSquareRect(vv2DebugDrawSqrPreviousCopy[i],DARK_GRAY); - } - - // draw new ones - vv2DebugDrawSqrPrevious.clear(); //empty before fillup below - - for(int i=0;iRoute.empty()) - for(int i=0;iRoute.size();i++) - AutoPlayAIDebugDrawSquareRect(PLAYER->Route[i],GREEN); - - if(!v2KeepGoingTo.Is0()) - AutoPlayAIDebugDrawSquareRect(v2KeepGoingTo,BLUE,PLAYER->Route.size(),true); - else if(iWanderTurns>0) - AutoPlayAIDebugDrawSquareRect(PLAYER->GetPos(),YELLOW,iWanderTurns); - - for(int i=0;iIsBroken()){ DBG2("chkDropBroken",eqDropChk); - eqBroken=eqDropChk; - bDropSomething=true; - break; - } - } - - if(!bDropSomething && GetBurdenState() == STRESSED){ - if(clock()%100<5){ //5% chance to drop something weighty randomly every turn - bDropSomething=true; DBGLN; - } - } - - if(!bDropSomething && GetBurdenState() == OVER_LOADED){ - bDropSomething=true; - } - - if(bDropSomething){ DBG1("DropSomething"); - item* dropMe=NULL; - if(eqBroken!=NULL)dropMe=eqBroken; - - item* heaviest=NULL; - item* cheapest=NULL; - -// bool bFound=false; -// for(int k=0;k<2;k++){ -// if(dropMe!=NULL)break; -// static item* eqDropChk=NULL; -// for(int i=0;iIsBroken()){ -// dropMe=eqDropChk; -// break; -// } -// } - - if(dropMe==NULL){ - static itemvector vit;vit.clear();GetStack()->FillItemVector(vit); - for(int i=0;iGetName(DEFINITE).CStr(),vit[i]->GetTruePrice(),vit[i]->GetWeight()); - if(vit[i]->IsEncryptedScroll())continue; -// if(!bPlayerHasLantern && dynamic_cast(vit[i])!=NULL){ -// bPlayerHasLantern=true; //will keep only the 1st lantern -// continue; -// } - - if(vit[i]->IsBroken()){ //TODO use repair scroll? - dropMe=vit[i]; - break; - } - - if(heaviest==NULL)heaviest=vit[i]; - if(cheapest==NULL)cheapest=vit[i]; - -// switch(k){ -// case 0: //better not implement this as a user function as that will remove the doubt about items values what is another fun challenge :) - if(vit[i]->GetTruePrice() < cheapest->GetTruePrice()) //cheapest - cheapest=vit[i]; -// break; -// case 1: //this could be added as user function to avoid browsing the drop list, but may not be that good... - if(vit[i]->GetWeight() > heaviest->GetWeight()) //heaviest - heaviest=vit[i]; -// break; -// } - } - } - - if(heaviest!=NULL && cheapest!=NULL){ - if(dropMe==NULL && heaviest==cheapest) - dropMe=heaviest; - - if(dropMe==NULL && cheapest->GetTruePrice()<=iMaxValueless){ DBG2("DropValueless",cheapest->GetName(DEFINITE).CStr()); - dropMe=cheapest; - } - - if(dropMe==NULL){ - // the worst price VS weight will be dropped - float fC = cheapest ->GetTruePrice()/(float)cheapest ->GetWeight(); - float fW = heaviest->GetTruePrice()/(float)heaviest->GetWeight(); DBG3("PriceVsWeightRatio",fC,fW); - if(fC < fW){ - dropMe = cheapest; - }else{ - dropMe = heaviest; - } - } - - if(dropMe==NULL) - dropMe = clock()%2==0 ? heaviest : cheapest; - } - - // chose a throw direction - if(dropMe!=NULL){ - static std::vector vv2DirBase;static bool bDummyInit = [](){for(int i=0;i<8;i++)vv2DirBase.push_back(i);return true;}(); - std::vector vv2Dir(vv2DirBase); - int iDirOk=-1; - v2 v2DropAt(0,0); - lsquare* lsqrDropAt=NULL; - for(int i=0;i<8;i++){ - int k = clock()%vv2Dir.size(); //random chose from remaining TODO could be where there is NPC foe - int iDir = vv2Dir[k]; //collect direction value - vv2Dir.erase(vv2Dir.begin() + k); //remove using the chosen index to prepare next random choice - - v2 v2Dir = game::GetMoveVector(iDir); - v2 v2Chk = GetPos() + v2Dir; - if(game::GetCurrentLevel()->IsValidPos(v2Chk)){ - lsquare* lsqrChk=game::GetCurrentLevel()->GetLSquare(v2Chk); - if(lsqrChk->IsFlyable()){ - iDirOk = iDir; - v2DropAt = v2Chk; - lsqrDropAt=lsqrChk; - break; - } - } - };DBGLN; - - if(iDirOk==-1){iDirOk=clock()%8;DBG2("RandomDir",iDirOk);}DBGLN; //TODO should just drop may be? unless hitting w/e is there could help - - if(iDirOk>-1){DBG2("KickOrThrow",iDirOk); - static itemcontainer* itc;itc = dynamic_cast(dropMe);DBGLN; - static humanoid* h;h = dynamic_cast(this);DBGLN; - DBG8("CanKickLockedChest",lsqrDropAt,itc,itc?itc->IsLocked():-1,CanKick(),h,h?h->GetLeftLeg():0,h?h->GetRightLeg():0); - if(lsqrDropAt && itc && itc->IsLocked() && CanKick() && h && h->GetLeftLeg() && h->GetRightLeg()){DBGLN; - dropMe->MoveTo(lsqrDropAt->GetStack());DBGLN; //drop in front.. - Kick(lsqrDropAt,iDirOk,true);DBGLN; // ..to kick it - }else{DBGLN; - ThrowItem(iDirOk, dropMe); DBG5("DropThrow",iDirOk,dropMe->GetName(DEFINITE).CStr(),dropMe->GetTruePrice(),dropMe->GetWeight()); - } - }else{DBGLN; - dropMe->MoveTo(GetLSquareUnder()->GetStack());DBGLN; //just drop - } - - v2LastDropPlayerWasAt=GetPos();DBGSV2(v2LastDropPlayerWasAt); - - return true; - } - - DBG1("AutoPlayNeedsImprovement:DropItem"); - ADD_MESSAGE("%s says \"I need more intelligence to drop trash...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI - //TODO stop autoplay mode? if not, something random may happen some time and wont reach here ex.: spoil, fire, etc.. - } - - return false; -} - -bool character::IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern) -{ - if(!it->CanBeSeenBy(this))return false; - if(!it->IsPickable(this))return false; - if(it->GetSquaresUnder()!=1)return false; //avoid big corpses 2x2 - - if(!bPlayerHasLantern && it->IsOnFire(this)){ - //ok - }else{ - if(it->IsBroken())return false; - if(it->GetTruePrice()<=iMaxValueless)return false; //mainly to avoid all rocks from broken walls - if(clock()%3!=0 && it->GetSpoilLevel()>0)return false; //some spoiled may be consumed to randomly test diseases flows - } - - return true; -} - -truth character::AutoPlayAIEquipAndPickup(bool bPlayerHasLantern) -{ - static humanoid* h;h = dynamic_cast(this); - if(h==NULL)return false; - - if(h->AutoPlayAIequip()) - return true; - - if(GetBurdenState()!=OVER_LOADED){ DBG4(CommandFlags&DONT_CHANGE_EQUIPMENT,this,GetNameSingular().CStr(),GetSquareUnder()); - if(v2LastDropPlayerWasAt!=GetPos()){ - static bool bHoarder=true; //TODO wizard autoplay AI config exclusive felist - - if(CheckForUsefulItemsOnGround(false)) - if(!bHoarder) - return true; - - //just pick up any useful stuff - static itemvector vit;vit.clear();GetStackUnder()->FillItemVector(vit); - for(uint c = 0; c < vit.size(); ++c){ - if(!IsAutoplayAICanPickup(vit[c],bPlayerHasLantern))continue; - - static itemcontainer* itc;itc = dynamic_cast(vit[c]); - if(itc && !itc->IsLocked()){ //get items from unlocked container - static itemvector vitItc;vitItc.clear();itc->GetContained()->FillItemVector(vitItc); - for(uint d = 0; d < vitItc.size(); ++d) - vitItc[d]->MoveTo(itc->GetLSquareUnder()->GetStack()); - continue; - } - - vit[c]->MoveTo(GetStack()); DBG2("pickup",vit[c]->GetNameSingular().CStr()); -// if(GetBurdenState()==OVER_LOADED)ThrowItem(clock()%8,ItemVector[c]); -// return true; - if(!bHoarder) - return true; - } - } - } - - return false; -} - -static const int iMoreThanMaxDist=10000000; //TODO should be max integer but this will do for now in 2018 :) -truth character::AutoPlayAITestValidPathTo(v2 v2To) -{ - return AutoPlayAIFindWalkDist(v2To) < iMoreThanMaxDist; -} - -int character::AutoPlayAIFindWalkDist(v2 v2To) -{ - static bool bUseSimpleDirectDist=false; //very bad navigation this is - if(bUseSimpleDirectDist)return (v2To - GetPos()).GetLengthSquare(); - - static v2 GoingToBkp;GoingToBkp = GoingTo; //IsGoingSomeWhere() ? GoingTo : v2(0,0); - - SetGoingTo(v2To); - CreateRoute(); - static int iDist;iDist=Route.size(); - TerminateGoingTo(); - - if(GoingToBkp!=ERROR_V2){ DBG2("Warning:WrongUsage:ShouldBeGoingNoWhere",DBGAV2(GoingToBkp)); - SetGoingTo(GoingToBkp); - CreateRoute(); - } - - return iDist>0?iDist:iMoreThanMaxDist; -} - -truth character::AutoPlayAINavigateDungeon(bool bPlayerHasLantern) -{ - /** - * navigate the unknown dungeon - */ - std::vector v2Exits; - if(v2KeepGoingTo.Is0()){ DBG1("TryNewMoveTarget"); - // target undiscovered squares to explore - v2 v2PreferedTarget(0,0); - - int iNearestLanterOnFloorDist = iMoreThanMaxDist; - v2 v2PreferedLanternOnFloorTarget(0,0); - - v2 v2NearestUndiscovered(0,0); - int iNearestUndiscoveredDist=iMoreThanMaxDist; - std::vector vv2UndiscoveredValidPathSquares; - - lsquare* lsqrNearestSquareWithWallLantern=NULL; - lsquare* lsqrNearestDropWallLanternAt=NULL; - stack* stkNearestDropWallLanternAt = NULL; - int iNearestSquareWithWallLanternDist=iMoreThanMaxDist; - item* itNearestWallLantern=NULL; - - /*************************************************************** - * scan whole dungeon squares - */ - for(int iY=0;iYGetYSize();iY++){ for(int iX=0;iXGetXSize();iX++){ - lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(iX,iY); - - olterrain* olt = lsqr->GetOLTerrain(); - if(olt && (olt->GetConfig() == STAIRS_UP || olt->GetConfig() == STAIRS_DOWN)){ - v2Exits.push_back(v2(lsqr->GetPos())); DBGSV2(v2Exits[v2Exits.size()-1]); - } - - stack* stkSqr = lsqr->GetStack(); - static itemvector vit;vit.clear();stkSqr->FillItemVector(vit); - bool bAddValidTargetSquare=true; - - // find nearest wall lantern - if(!bPlayerHasLantern && olt && olt->IsWall()){ - for(int n=0;nIsLanternOnWall() && !vit[n]->IsBroken()){ - static stack* stkDropWallLanternAt;stkDropWallLanternAt = lsqr->GetStackOfAdjacentSquare(vit[n]->GetSquarePosition()); - static lsquare* lsqrDropWallLanternAt;lsqrDropWallLanternAt = - stkDropWallLanternAt?stkDropWallLanternAt->GetLSquareUnder():NULL; - - if(stkDropWallLanternAt && lsqrDropWallLanternAt && CanTheoreticallyMoveOn(lsqrDropWallLanternAt)){ - int iDist = AutoPlayAIFindWalkDist(lsqrDropWallLanternAt->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); - if(lsqrNearestSquareWithWallLantern==NULL || iDistGetPos()),DBGAV2(GetPos())); - lsqrNearestDropWallLanternAt=lsqrDropWallLanternAt; - stkNearestDropWallLanternAt=stkDropWallLanternAt; - } - } - - break; - } - } - } - - if(bAddValidTargetSquare && !CanTheoreticallyMoveOn(lsqr)) - bAddValidTargetSquare=false; - - bool bIsFailToTravelSquare=false; - if(bAddValidTargetSquare){ - for(int j=0;jGetPos()){ - bAddValidTargetSquare=false; - bIsFailToTravelSquare=true; - break; - } - } - - if(!bIsFailToTravelSquare){ - -// if(bAddValidTargetSquare && v2PreferedTarget.Is0() && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){ - if(bAddValidTargetSquare && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){ - bool bVisitAgain=false; - if(iVisitAgainCount>0 || !bPlayerHasLantern){ - if(stkSqr!=NULL && stkSqr->GetItems()>0){ - for(int n=0;nGetID());DBG1(vit[n]->GetType());DBG3("VisitAgain:ChkItem",vit[n]->GetNameSingular().CStr(),vit.size()); - if(vit[n]->IsBroken())continue; DBGLN; - - static bool bIsLanternOnFloor;bIsLanternOnFloor = dynamic_cast(vit[n])!=NULL;// || vit[n]->IsOnFire(this); DBGLN; - - if( // if is useful to the AutoPlay AI endless tests - vit[n]->IsShield (this) || - vit[n]->IsWeapon (this) || - vit[n]->IsArmor (this) || - vit[n]->IsAmulet (this) || - vit[n]->IsZappable(this) || - vit[n]->IsRing (this) || - bIsLanternOnFloor - ) - if(IsAutoplayAICanPickup(vit[n],bPlayerHasLantern)) - { - bVisitAgain=true; - - if(bIsLanternOnFloor && !bPlayerHasLantern){ - static int iDist;iDist = AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); - if(iDistGetPos(); DBG2("PreferLanternAt",DBGAV2(lsqr->GetPos())) - } - }else{ - iVisitAgainCount--; - } - - DBG4(bVisitAgain,DBGAV2(lsqr->GetPos()),iVisitAgainCount,bIsLanternOnFloor); - break; - } - } - } - } - - if(!bVisitAgain)bAddValidTargetSquare=false; - } - - } - - if(bAddValidTargetSquare) - if(!CanTheoreticallyMoveOn(lsqr)) //if(olt && !CanMoveOn(olt)) - bAddValidTargetSquare=false; - - if(bAddValidTargetSquare){ DBG2("addValidSqr",DBGAV2(lsqr->GetPos())); - static int iDist;iDist=AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); - - if(iDistGetPos()); - - if(iDistGetPos(); DBG3(iNearestUndiscoveredDist,DBGAV2(lsqr->GetPos()),DBGAV2(GetPos())); - } - } - }} DBG2(DBGAV2(v2PreferedTarget),vv2UndiscoveredValidPathSquares.size()); - - /*************************************************************** - * define prefered navigation target - */ - if(!bPlayerHasLantern && v2PreferedTarget.Is0()){ - bool bUseWallLantern=false; - if(!v2PreferedLanternOnFloorTarget.Is0() && lsqrNearestSquareWithWallLantern!=NULL){ - if(iNearestLanterOnFloorDist <= iNearestSquareWithWallLanternDist){ - v2PreferedTarget=v2PreferedLanternOnFloorTarget; - }else{ - bUseWallLantern=true; - } - }else if(!v2PreferedLanternOnFloorTarget.Is0()){ - v2PreferedTarget=v2PreferedLanternOnFloorTarget; - }else if(lsqrNearestSquareWithWallLantern!=NULL){ - bUseWallLantern=true; - } - - if(bUseWallLantern){ - /** - * target to nearest wall lantern - * check for lanterns on walls of adjacent squares if none found on floors - */ - itNearestWallLantern->MoveTo(stkNearestDropWallLanternAt); // the AI is prepared to get things from the floor only so "magically" drop it :) - v2PreferedTarget = lsqrNearestDropWallLanternAt->GetPos(); DBG2("PreferWallLanternAt",DBGAV2(lsqrNearestDropWallLanternAt->GetPos())) - } - - } - - /*************************************************************** - * validate and set new navigation target - */ -// DBG9("AllNavigatePossibilities",DBGAV2(v2PreferedTarget),DBGAV2(v2PreferedLanternOnFloorTarget),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2()); - v2 v2NewKGTo=v2(0,0); - - if(v2NewKGTo.Is0()){ - //TODO if(!v2PreferedTarget.Is0){ // how can this not be compiled? error: cannot convert ‘v2::Is0’ from type ‘truth (v2::)() const {aka bool (v2::)() const}’ to type ‘bool’ - if(v2PreferedTarget.GetLengthSquare()>0) - if(AutoPlayAITestValidPathTo(v2PreferedTarget)) - v2NewKGTo=v2PreferedTarget; DBGSV2(v2PreferedTarget); - } - - if(v2NewKGTo.Is0()){ - if(bAutoPlayUseRandomNavTargetOnce){ //these targets were already path validated and are safe to use! - v2NewKGTo=vv2UndiscoveredValidPathSquares[clock()%vv2UndiscoveredValidPathSquares.size()]; DBG2("RandomTarget",DBGAV2(v2NewKGTo)); - bAutoPlayUseRandomNavTargetOnce=false; - }else{ //find nearest - if(!v2NearestUndiscovered.Is0()){ - v2NewKGTo=v2NearestUndiscovered; DBGSV2(v2NearestUndiscovered); - } - } - } - - if(v2NewKGTo.Is0()){ //no new destination: fully explored - if(v2Exits.size()>0){ - if(game::GetCurrentDungeonTurnsCount()==0){ DBG1("Dungeon:FullyExplored:FirstTurn"); - iWanderTurns=100+clock()%300; DBG2("WanderALotOnFullyExploredLevel",iWanderTurns); //just move around a lot, some NPC may spawn - }else{ - // travel between dungeons if current fully explored - v2 v2Try = v2Exits[clock()%v2Exits.size()]; - if(AutoPlayAITestValidPathTo(v2Try)) - v2NewKGTo = v2TravelingToAnotherDungeon = v2Try; DBGSV2(v2TravelingToAnotherDungeon); - } - }else{ - DBG1("AutoPlayNeedsImprovement:Navigation") - ADD_MESSAGE("%s says \"I need more intelligence to move around...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI - //TODO stop autoplay mode? - } - } - - if(v2NewKGTo.Is0()){ DBG1("Desperately:TryAnyRandomTargetNavWithValidPath"); - std::vector vlsqrChk(vv2AllDungeonSquares); - - while(vlsqrChk.size()>0){ - static int i;i=clock()%vlsqrChk.size(); - static v2 v2Chk; v2Chk = vlsqrChk[i]->GetPos(); - - if(!AutoPlayAITestValidPathTo(v2Chk)){ - vlsqrChk.erase(vlsqrChk.begin()+i); - }else{ - v2NewKGTo=v2Chk; - break; - } - } - } - - if(!v2NewKGTo.Is0()){ - AutoPlayAISetAndValidateKeepGoingTo(v2NewKGTo); - }else{ - DBG1("TODO:too complex paths are failing... improve CreateRoute()?"); - } - } - - if(!v2KeepGoingTo.Is0()){ - if(v2KeepGoingTo==GetPos()){ DBG3("ReachedDestination",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo)); - //wander a bit before following new target destination - iWanderTurns=(clock()%iMaxWanderTurns)+iMinWanderTurns; DBG2("WanderAroundAtReachedDestination",iWanderTurns); - -// v2KeepGoingTo=v2(0,0); -// TerminateGoingTo(); - AutoPlayAIReset(false); - return true; - } - -// CheckForUsefulItemsOnGround(false); DBGSV2(GoingTo); -// CheckForEnemies(false, true, false, false); DBGSV2(GoingTo); - -// if(!IsGoingSomeWhere() || v2KeepGoingTo!=GoingTo){ DBG3("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo)); -// SetGoingTo(v2KeepGoingTo); -// } - static int iForceGoingToCountDown=10; - static v2 v2GoingToBkp;v2GoingToBkp=GoingTo; - if(!v2KeepGoingTo.IsAdjacent(GoingTo)){ - if(iForceGoingToCountDown==0){ - DBG4("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo),DBGAV2(GetPos())); - - if(!AutoPlayAISetAndValidateKeepGoingTo(v2KeepGoingTo)){ - static int iSetFailTeleportCountDown=10; - iSetFailTeleportCountDown--; - vv2WrongGoingTo.push_back(v2GoingToBkp); - if(iSetFailTeleportCountDown==0){ - AutoPlayAITeleport(false); - AutoPlayAIReset(true); //refresh to test/try it all again - iSetFailTeleportCountDown=10; - } - } - DBGSV2(GoingTo); - return true; - }else{ - iForceGoingToCountDown--; DBG1(iForceGoingToCountDown); - } - }else{ - iForceGoingToCountDown=10; - } - - /** - * Determinedly blindly moves towards target, the goal is to Navigate! - * - * this has several possible status if returning false... - * so better do not decide anything based on it? - */ - MoveTowardsTarget(false); - -// if(!MoveTowardsTarget(false)){ DBG3("OrFailedGoingTo,OrReachedDestination...",DBGAV2(GoingTo),DBGAV2(GetPos())); // MoveTowardsTarget may break the GoingTo EVEN if it succeeds????? -// TerminateGoingTo(); -// v2KeepGoingTo=v2(0,0); //reset only this one to try again -// GetAICommand(); //wander once for randomicity -// } - - return true; - } - - return false; -} - -bool character::AutoPlayAIChkInconsistency() -{ - if(GetSquareUnder()==NULL){ - DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet()); - DBG6("GetSquareUnderIsNULLhow?",IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr()); - return true; //to just ignore this turn expecting on next it will be ok. - } - return false; -} - -truth character::AutoPlayAIPray() -{ - bool bSPO = bSafePrayOnce; - bSafePrayOnce=false; - - if(bSPO){} - else if(StateIsActivated(PANIC) && clock()%10==0){ - iWanderTurns=1; DBG1("Wandering:InPanic"); // to regain control as soon it is a ghost anymore as it can break navigation when inside walls - }else return false; - - // check for known gods - int aiKGods[GODS]; - int iKGTot=0; - int aiKGodsP[GODS]; - int iKGTotP=0; - static int iPleased=50; //see god::PrintRelation() - for(int c = 1; c <= GODS; ++c){ - if(!game::GetGod(c)->IsKnown())continue; - // even known, praying to these extreme ones will be messy if Relation<1000 - if(dynamic_cast(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue; - if(dynamic_cast(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue; - - aiKGods[iKGTot++]=c; - - if(game::GetGod(c)->GetRelation() > iPleased){ -// //TODO could this help? -// switch(game::GetGod(c)->GetBasicAlignment()){ //game::GetGod(c)->GetAlignment(); -// case GOOD: -// if(game::GetPlayerAlignment()>=2){}else continue; -// break; -// case NEUTRAL: -// if(game::GetPlayerAlignment()<2 && game::GetPlayerAlignment()>-2){}else continue; -// break; -// case EVIL: -// if(game::GetPlayerAlignment()<=-2){}else continue; -// break; -// } - aiKGodsP[iKGTotP++] = c; - } - } - if(iKGTot==0)return false; -// if(bSPO && iKGTotP==0)return false; - - // chose and pray to one god - god* g = NULL; - if(iKGTotP>0 && (bSPO || clock()%10!=0)) - g = game::GetGod(aiKGodsP[clock()%iKGTotP]); - else - g = game::GetGod(aiKGods[clock()%iKGTot]); - - if(bSPO || clock()%10!=0){ //it may not recover some times to let pray unsafely - int iRecover=0; - if(iKGTotP==0){ - if(iRecover==0 && g->GetRelation()==-1000)iRecover=1000; //to test all relation range - if(iRecover==0 && g->GetRelation() <= iPleased)iRecover=iPleased; //to alternate tests on many with low good relation - } - if(iRecover>0) - g->SetRelation(iRecover); - - g->AdjustTimer(-1000000000); //TODO filter gods using timer too instead of this reset? - } - - g->Pray(); DBG2("PrayingTo",g->GetName()); - - return true; -} - -truth character::AutoPlayAICommand(int& rKey) -{ - DBGLN;if(AutoPlayAIChkInconsistency())return true; - DBGSV2(GetPos()); - - if(AutoPlayLastChar!=this){ - AutoPlayAIReset(true); - AutoPlayLastChar=this; - } - - DBGLN;if(AutoPlayAIChkInconsistency())return true; - if(AutoPlayAICheckAreaLevelChangedAndReset()) - AutoPlayAIReset(true); - - static bool bDummy_initDbg = [](){game::AddDebugDrawOverlayFunction(&AutoPlayAIDebugDrawOverlay);return true;}(); - - truth bPlayerHasLantern=false; - static itemvector vit;vit.clear();GetStack()->FillItemVector(vit); - for(uint i=0;i(vit[i])!=NULL || vit[i]->IsOnFire(this)){ - bPlayerHasLantern=true; //will keep only the 1st lantern - break; - } - } - - DBGLN;if(AutoPlayAIChkInconsistency())return true; - AutoPlayAIPray(); - - //TODO this doesnt work??? -> if(IsPolymorphed()){ //to avoid some issues TODO but could just check if is a ghost -// if(dynamic_cast(this) == NULL){ //this avoid some issues TODO but could just check if is a ghost -// if(StateIsActivated(ETHEREAL_MOVING)){ //this avoid many issues - static bool bPreviousTurnWasGhost=false; - if(dynamic_cast(this) != NULL){ DBG1("Wandering:Ghost"); //this avoid many issues mainly related to navigation - iWanderTurns=1; // to regain control as soon it is a ghost anymore as it can break navigation when inside walls - bPreviousTurnWasGhost=true; - }else{ - if(bPreviousTurnWasGhost){ - AutoPlayAIReset(true); //this may help on navigation - bPreviousTurnWasGhost=false; - return true; - } - } - - DBGLN;if(AutoPlayAIChkInconsistency())return true; - if(AutoPlayAIDropThings()) - return true; - - DBGLN;if(AutoPlayAIChkInconsistency())return true; - if(AutoPlayAIEquipAndPickup(bPlayerHasLantern)) - return true; - - if(iWanderTurns>0){ - if(!IsPlayer() || game::GetAutoPlayMode()==0 || !IsPlayerAutoPlay()){ //redundancy: yep - DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet()); - DBG5(IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr()); - ABORT("autoplay is inconsistent %d %d %d %d %d %s %d %s %d %d %d %d %d", - IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(), - GetNameSingular().CStr(),game::GetAutoPlayMode(),GetName(DEFINITE).CStr(), - IsTemporary(),IsPet(),IsHeadless(),IsPlayer(),IsPlayerAutoPlay()); - } - GetAICommand(); DBG2("Wandering",iWanderTurns); //fallback to default TODO never reached? - iWanderTurns--; - return true; - } - - /*************************************************************************************************** - * WANDER above here - * NAVIGATE below here - ***************************************************************************************************/ - - /** - * travel between dungeons - */ - if(!v2TravelingToAnotherDungeon.Is0() && GetPos() == v2TravelingToAnotherDungeon){ - bool bTravel=false; - lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2TravelingToAnotherDungeon); -// square* sqr = Area->GetSquare(v2TravelingToAnotherDungeon); - olterrain* ot = lsqr->GetOLTerrain(); -// oterrain* ot = sqr->GetOTerrain(); - if(ot){ - if(ot->GetConfig() == STAIRS_UP){ - rKey='<'; - bTravel=true; - } - - if(ot->GetConfig() == STAIRS_DOWN){ - rKey='>'; - bTravel=true; - } - } - - if(bTravel){ DBG3("travel",DBGAV2(v2TravelingToAnotherDungeon),rKey); - AutoPlayAIReset(true); - return false; //so the new/changed key will be used as command, otherwise it would be ignored - } - } - - static const int iDesperateResetCountDownDefault=10; - static const int iDesperateEarthQuakeCountDownDefault=iDesperateResetCountDownDefault*5; - static int iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; - if(AutoPlayAINavigateDungeon(bPlayerHasLantern)){ - iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; - return true; - }else{ - if(iDesperateEarthQuakeCountDown==0){ - iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; - scrollofearthquake::Spawn()->FinishReading(this); - DBG1("UsingTerribleEarthquakeSolution"); // xD - }else{ - iDesperateEarthQuakeCountDown--; - DBG1(iDesperateEarthQuakeCountDown); - } - } - - /**************************************** - * Twighlight zone - */ - - ADD_MESSAGE("%s says \"I need more intelligence to do things by myself...\"", CHAR_NAME(DEFINITE)); DBG1("TODO: AI needs improvement"); - - static int iDesperateResetCountDown=iDesperateResetCountDownDefault; - if(iDesperateResetCountDown==0){ - iDesperateResetCountDown=iDesperateResetCountDownDefault; - - AutoPlayAIReset(true); - - // AFTER THE RESET!!! - iWanderTurns=iMaxWanderTurns; DBG2("DesperateResetToSeeIfAIWorksAgain",iWanderTurns); - }else{ - GetAICommand(); DBG2("WanderingDesperatelyNotKnowingWhatToDo",iDesperateResetCountDown); // :) - iDesperateResetCountDown--; - } - - return true; -} - void character::GetPlayerCommand() { truth HasActed = false; @@ -3671,7 +2747,8 @@ void character::GetPlayerCommand() BeginTemporaryState(PANIC, 500 + RAND_N(500)); } - game::AskForKeyPress(CONST_S("You are horrified by your situation! [press any key to continue]")); + if(!curseddeveloper::IsCursedDeveloper()) + game::AskForKeyPress(CONST_S("You are horrified by your situation! [press any key to continue]")); } else if(ivanconfig::GetWarnAboutDanger()) { @@ -3684,6 +2761,13 @@ void character::GetPlayerCommand() game::SetDangerFound(0); } +#ifdef CURSEDDEVELOPER + if(curseddeveloper::LifeSaveJustABitIfRequested()){ + HasActed = true; + continue; + } +#endif + game::SetIsInGetCommand(true); int Key = GET_KEY(); game::SetIsInGetCommand(false); @@ -3695,29 +2779,7 @@ void character::GetPlayerCommand() int c; #ifdef WIZARD - if(IsPlayerAutoPlay()){ - bool bForceStop = false; - if(game::GetAutoPlayMode()>=2) - bForceStop = globalwindowhandler::IsKeyPressed(SDL_SCANCODE_ESCAPE); - - if(!bForceStop && Key=='.'){ // pressed or simulated - if(game::IsInWilderness()){ - Key='>'; //blindly tries to go back to the dungeon safety :) TODO target and move to other dungeons/towns in the wilderness - }else{ - HasActed = AutoPlayAICommand(Key); DBG2("Simulated",Key); - if(HasActed)ValidKeyPressed = true; //valid simulated action - } - }else{ - /** - * if the user hits any key during the autoplay mode that runs by itself, it will be disabled. - * at non auto mode, can be moved around but cannot rest or will move by itself - */ - if(game::GetAutoPlayMode()>=2 && (Key!='~' || bForceStop)){ - game::DisableAutoPlayMode(); - AutoPlayAIReset(true); // this will help on re-randomizing things, mainly paths - } - } - } + wizautoplay::AutoPlayCommandKey(this,Key,HasActed,ValidKeyPressed); #endif if(!HasActed){ @@ -3799,10 +2861,15 @@ void character::Vomit(v2 Pos, int Amount, truth ShowMsg) DeActivateTemporaryState(PARASITE_TAPE_WORM); } - - if(!game::IsInWilderness()) + + if(!game::IsInWilderness()){ + if(GetMyVomitMaterial() < LIQUID_ID || GetMyVomitMaterial() > _LIQUID_ID_END_){ + DBGSTK;DBG4("_BUG_TRACK_:Fixing invalid vomit material config ID to prevent unnecessary ABORT()",GetMyVomitMaterial(),LIQUID_ID,_LIQUID_ID_END_); + SetNewVomitMaterial(VOMIT); + } GetNearLSquare(Pos)->ReceiveVomit(this, - liquid::Spawn(GetMyVomitMaterial(), long(sqrt(GetBodyVolume()) * Amount / 1000))); + liquid::Spawn(GetMyVomitMaterial(), long(sqrt(GetBodyVolume()) * Amount / 1000))); + } } truth character::Polymorph(character* NewForm, int Counter) @@ -3908,6 +2975,7 @@ void character::BeKicked(character* Kicker, item* Boot, bodypart* Leg, v2 HitPos truth character::CheckBalance(double KickDamage) { + ValidateTrapData(); return !CanMove() || IsStuck() || !KickDamage @@ -4209,7 +3277,8 @@ truth character::CheckForUsefulItemsOnGround(truth CheckFood) itemvector ItemVector; GetStackUnder()->FillItemVector(ItemVector); - for(uint c = 0; c < ItemVector.size(); ++c) + ValidateTrapData(); + for(uint c = 0; c < ItemVector.size(); ++c){ if(ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this)) { if(!(CommandFlags & DONT_CHANGE_EQUIPMENT) @@ -4220,6 +3289,7 @@ truth character::CheckForUsefulItemsOnGround(truth CheckFood) && TryToConsume(ItemVector[c])) return true; } + } return false; } @@ -4352,6 +3422,8 @@ truth character::Displace(character* Who, truth Forced) else Danger /= 1 << -PriorityDifference; + ValidateTrapData(); + Who->ValidateTrapData(); if(IsSmall() && Who->IsSmall() && (Forced || Danger > 1. || !(Who->IsPlayer() || Who->IsBadPath(GetPos()))) && !IsStuck() && !Who->IsStuck() @@ -4607,10 +3679,10 @@ truth character::IsAboveUsefulItem() ) ) || (bTooCheap && - (vit[i]->GetTruePrice() > iMaxValueless) + (vit[i]->GetTruePrice() > wizautoplay::GetMaxValueless()) ) || (bEncumbering && //calc in float price vs weight - (vit[i]->GetTruePrice()/(vit[i]->GetWeight()/1000.0)) > (iMaxValueless*2) + (vit[i]->GetTruePrice()/(vit[i]->GetWeight()/1000.0)) > (wizautoplay::GetMaxValueless()*2) ) ){ return true; @@ -4971,10 +4043,11 @@ void character::TeleportRandomly(truth Intentional) else if(IsPlayer()) { // This is to prevent uncontrolled teleportation from going unnoticed by players. - game::AskForKeyPress(CONST_S("You teleport! [press any key to continue]")); + if(!curseddeveloper::IsCursedDeveloperTeleport()) + game::AskForKeyPress(CONST_S("You teleport! [press any key to continue]")); } - if(IsPlayer()) + if(IsPlayer() && !curseddeveloper::IsCursedDeveloperTeleport()) ADD_MESSAGE("A rainbow-colored whirlpool twists the existence around you. " "You are sucked through a tunnel piercing a myriad of surreal " "universes. Luckily you return to this dimension in one piece."); @@ -4990,8 +4063,10 @@ void character::TeleportRandomly(truth Intentional) if(GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false); - if(IsPlayerAutoPlay()) - AutoPlayAIReset(true); +#ifdef WIZARD + if(wizautoplay::IsPlayerAutoPlay(this)) + wizautoplay::AutoPlayAIReset(true); +#endif // There's a small chance that some warp gas/fluid is left behind. if(FromSquare->IsFlyable() && !RAND_N(1000)) @@ -5003,14 +4078,9 @@ void character::TeleportRandomly(truth Intentional) } } -truth character::IsPlayerAutoPlay() -{ - return IsPlayer() && game::GetAutoPlayMode()>0; -} - void character::DoDetecting() { - if(IsPlayerAutoPlay() || !IsPlayer()) + if(wizautoplay::IsPlayerAutoPlay(this) || !IsPlayer()) return; material* TempMaterial; @@ -5276,7 +4346,7 @@ int character::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, i else if(IsPlayer() || CanBeSeenByPlayer()) ADD_MESSAGE("It vanishes."); - if(IsPlayer()) + if(IsPlayer() && !curseddeveloper::IsCursedDeveloper()) game::AskForKeyPress(CONST_S("Bodypart severed! [press any key to continue]")); } @@ -5613,7 +4683,7 @@ void character::Regenerate() EditNP(-Max(7500 / MaxHP, 1)); RegenerationCounter -= 1250000; int HP = BodyPart->GetHP(); - EditExperience(ENDURANCE, Min(1000 * BodyPart->GetMaxHP() / (HP * HP), 300), 1000); + EditExperience(ENDURANCE, Min(1000 * BodyPart->GetMaxHP() / Max(HP * HP,1), 300), 1000); } } @@ -6202,7 +5272,20 @@ void character::DrawPanel(truth AnimationDraw) const v2(RES.X - 19 - (game::GetMaxScreenXSize() << 4), RES.Y)); igraph::BlitBackGround(v2(16, 45 + (game::GetMaxScreenYSize() << 4)), v2(game::GetMaxScreenXSize() << 4, 9)); - FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetMaxScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr()); + int iLeftPos=0; + if(curseddeveloper::IsCursedDeveloper()){ //this will work in case a cursed savegame is moved to a normal gameplay game executable + festring fsCD;fsCD<<"(Cursed Immortal, KC="; + col24 cBkg=YELLOW; +#ifdef CURSEDDEVELOPER + fsCD<GetFontSize().X; + FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetMaxScreenYSize() << 4)), + cBkg, fsCD.CStr(), GetPanelName().CStr()); + } + FONT->Printf(DOUBLE_BUFFER, v2(16+iLeftPos, 45 + (game::GetMaxScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr()); game::UpdateAttributeMemory(); int PanelPosX = RES.X - 96; int PanelPosY = DrawStats(false); @@ -6855,6 +5938,32 @@ void character::LycanthropyHandler() } } +void character::SaveLifeBase() +{ + if(IsPlayer() && !wizautoplay::IsPlayerAutoPlay(this)) + game::AskForKeyPress(CONST_S("Life saved! [press any key to continue]")); + + RestoreBodyParts(); + ResetSpoiling(); + if(IsBurning()) + { + doforbodypartswithparam()(this, &bodypart::Extinguish, false); + doforbodyparts()(this, &bodypart::ResetThermalEnergies); + doforbodyparts()(this, &bodypart::ResetBurning); + } + RestoreHP(); + RestoreStamina(); + ResetStates(); + + if(GetNP() < SATIATED_LEVEL) + SetNP(SATIATED_LEVEL); + + SendNewDrawRequest(); + + if(GetAction()) + GetAction()->Terminate(false); +} + void character::SaveLife() { if(TemporaryStateIsActivated(LIFE_SAVED)) @@ -6892,23 +6001,8 @@ void character::SaveLife() LifeSaver->RemoveFromSlot(); LifeSaver->SendToHell(); } - - if(IsPlayer()) - game::AskForKeyPress(CONST_S("Life saved! [press any key to continue]")); - - RestoreBodyParts(); - ResetSpoiling(); - RestoreHP(); - RestoreStamina(); - ResetStates(); - - if(GetNP() < SATIATED_LEVEL) - SetNP(SATIATED_LEVEL); - - SendNewDrawRequest(); - - if(GetAction()) - GetAction()->Terminate(false); + + SaveLifeBase(); } character* character::PolymorphRandomly(int MinDanger, int MaxDanger, int Time) @@ -6924,7 +6018,7 @@ character* character::PolymorphRandomly(int MinDanger, int MaxDanger, int Time) if(StateIsActivated(POLYMORPH_CONTROL)) {DBGLN; - if(IsPlayer() && !IsPlayerAutoPlay()) + if(IsPlayer() && !wizautoplay::IsPlayerAutoPlay(this)) {DBGLN; if(!GetNewFormForPolymorphWithControl(NewForm)){DBG1(NewForm); return NULL; @@ -9169,8 +8263,7 @@ truth character::ConsumeItem(item* Item, cfestring& ConsumeVerb, truth nibbling) return false; } - if(IsPlayer() - && HasHadBodyPart(Item) + if(IsPlayer() && !curseddeveloper::IsCursedDeveloper() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]"))) return false; @@ -9320,7 +8413,7 @@ void character::ResetStates() && TemporaryStateIsActivated(1 << c) && - (IsPlayerAutoPlay() || TemporaryStateCounter[c] != PERMANENT) //autoplay will be messed if not removing some things like leprosy or worms + (wizautoplay::IsPlayerAutoPlay(this) || TemporaryStateCounter[c] != PERMANENT) //autoplay will be messed if not removing some things like leprosy or worms ){ TemporaryState &= ~(1 << c); @@ -10035,8 +9128,9 @@ truth character::CheckForFoodInSquare(v2 Pos) lsquare* Square = Level->GetLSquare(Pos); stack* Stack = Square->GetStack(); - if(Stack->GetItems()) - for(stackiterator i = Stack->GetBottom(); i.HasItem(); ++i) + if(Stack->GetItems()){ + ValidateTrapData(); + for(stackiterator i = Stack->GetBottom(); i.HasItem(); ++i){ if(i->IsPickable(this) && i->CanBeSeenBy(this) && i->CanBeEatenByAI(this) @@ -10046,6 +9140,8 @@ truth character::CheckForFoodInSquare(v2 Pos) SetGoingTo(Pos); return MoveTowardsTarget(false); } + } + } } return false; @@ -12069,23 +11165,17 @@ truth character::IsUsingWeaponOfCategory(int Category) const truth character::TryToUnStickTraps(v2 Dir) { - if(!TrapData) - return true; - - std::vector TrapVector; - - for(const trapdata* T = TrapData; T; T = T->Next) - TrapVector.push_back(*TrapData); - - for(uint c = 0; c < TrapVector.size(); ++c) - if(IsEnabled()) - { - entity* Trap = game::SearchTrap(TrapVector[c].TrapID); - - if(Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) - break; + if(ValidateTrapData(true)){ + for(trapdata* T = TrapData; T; T = T->Next){ + if(IsEnabled()) + { + entity* Trap = game::SearchTrap(T->TrapID); + if(Trap && Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) + break; + } } - + ValidateTrapData(); //if something changed this fixes it + } return !TrapData && IsEnabled(); } @@ -12096,17 +11186,50 @@ struct trapidcomparer ulong ID; }; +truth character::ValidateTrapData(bool bForceNow) +{ + if(!TrapData)return false; + + if(bForceNow || game::GetTurn()!=lTmpLastValidateTrapDataTurn){ + static trapdata* ToDel=NULL; + trapdata* T = TrapData; + for(;T;){ + if(game::SearchTrap(T->TrapID)){ //may ne slow, performance bottle neck... + T = T->Next; + continue; + } + + ToDel = T; + T = T->Next; + if(TrapData==ToDel){ + TrapData=T; //update 1st on the LL + } + delete ToDel; + ToDel=NULL; + } + + lTmpLastValidateTrapDataTurn = game::GetTurn(); + } + + return TrapData!=NULL; +} + void character::RemoveTrap(ulong ID) { - trapdata*& T = ListFind(TrapData, trapidcomparer(ID)); - trapdata* ToDel = T; - T = T->Next; - delete ToDel; + if(ValidateTrapData()){ + trapdata*& T = ListFind(TrapData, trapidcomparer(ID)); + if(T){ + trapdata* ToDel = T; + T = T->Next; + delete ToDel; + } + } doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange); } void character::AddTrap(ulong ID, ulong BodyParts) { + ValidateTrapData(); trapdata*& T = ListFind(TrapData, trapidcomparer(ID)); if(T) @@ -12208,25 +11331,27 @@ festring character::GetTrapDescription() const } } - if(Index <= 3) - { - TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second); - - if(Index == 2) - { - Desc << " and "; - TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second); - } - else if(Index == 3) + if(Index > 0){ + if(Index <= 3) { - Desc << ", "; - TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second); - Desc << " and "; - TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second); + TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second); + + if(Index == 2) + { + Desc << " and "; + TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second); + } + else if(Index == 3) + { + Desc << ", "; + TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second); + Desc << " and "; + TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second); + } } + else + Desc << "lots of traps"; } - else - Desc << "lots of traps"; return Desc; } @@ -13053,6 +12178,11 @@ truth character::IsESPBlockedByEquipment() const return false; } +truth character::HasStateFlag(long Flag) +{ + return TemporaryState & Flag; +} + truth character::TemporaryStateIsActivated (long What) const {DBG7(this,GetNameSingular().CStr(),TemporaryState&What,TemporaryState,std::bitset<32>(TemporaryState),What,std::bitset<32>(What)); if((What&PANIC) && (TemporaryState&PANIC) && StateIsActivated(FEARLESS)) @@ -13324,3 +12454,22 @@ truth character::WillGetTurnSoon() const { return GetAP() >= 900; } + +truth character::OverloadedKickFailCheck() +{ + if(ivanconfig::IsOverloadedFight() && GetBurdenState() == OVER_LOADED){ + if(IsPlayer()) + ADD_MESSAGE("You try to kick, lose balance and fall down."); + else if(CanBeSeenByPlayer()) + ADD_MESSAGE("%s tries to kick, loses balance and falls down.", CHAR_NAME(DEFINITE)); + + ReceiveDamage(0, 1 + (RAND() & 1), PHYSICAL_DAMAGE, ALL); //based on banana peel slip + CheckDeath(CONST_S("was overloaded, tried to kick and fell down"), 0); + + if(!IsPlayer()) // if player, the -AP would accumulate while the game turn would NOT increase messing the gameplay + EditAP(-100000 / APBonus(GetAttribute(AGILITY))); //based on kick command anyway + + return true; + } + return false; +} diff --git a/Main/Source/charset.cpp b/Main/Source/charset.cpp index 5e15fc68b..d3ecf9571 100644 --- a/Main/Source/charset.cpp +++ b/Main/Source/charset.cpp @@ -27,30 +27,40 @@ EXTENDED_SYSTEM_SPECIALIZATIONS(character)(0, 0, 0, "character"); #include #include -#include "team.h" -#include "error.h" -#include "game.h" -#include "message.h" -#include "save.h" -#include "stack.h" -#include "wsquare.h" #include "actions.h" -#include "iconf.h" -#include "whandler.h" -#include "hscore.h" -#include "god.h" +#include "balance.h" +#include "bitmap.h" +#include "bodypart.h" #include "command.h" -#include "materias.h" -#include "room.h" +#include "confdef.h" +#include "curseddeveloper.h" +#include "error.h" #include "felist.h" +#include "fluid.h" +#include "game.h" +#include "god.h" +#include "gods.h" #include "graphics.h" -#include "bitmap.h" -#include "rawbit.h" +#include "hiteffect.h" +#include "hscore.h" +#include "iconf.h" +#include "iloops.h" +#include "ivandef.h" +#include "lterras.h" +#include "materias.h" +#include "message.h" #include "miscitem.h" -#include "confdef.h" +#include "rawbit.h" +#include "room.h" +#include "save.h" +#include "stack.h" +#include "team.h" #include "traps.h" -#include "iloops.h" -#include "balance.h" +#include "whandler.h" +#include "wizautoplay.h" +#include "wsquare.h" #include "team.cpp" #include "char.cpp" +#include "curseddeveloper.cpp" +#include "wizautoplay.cpp" diff --git a/Main/Source/cmdcraft.cpp b/Main/Source/cmdcraft.cpp index 31c67c42d..061ac48e7 100644 --- a/Main/Source/cmdcraft.cpp +++ b/Main/Source/cmdcraft.cpp @@ -335,9 +335,10 @@ void recipedata::Save(outputfile& SaveFile) const << bGradativeCraftOverride << bTailoringMode << v2TailoringWorkbenchLocation - + << lDamageFinalItem - + << lInitialTurn + ; } @@ -394,18 +395,12 @@ void recipedata::Load(inputfile& SaveFile) >> bGradativeCraftOverride >> bTailoringMode >> v2TailoringWorkbenchLocation - + >> lDamageFinalItem + >> lInitialTurn ; - - if(game::GetCurrentSavefileVersion() >= 135){ - SaveFile >> bTailoringMode; - SaveFile >> v2TailoringWorkbenchLocation; - } - -// if(otSpawnType!=CTT_NONE) -// SaveFile >> otSpawn; + rc.integrityCheck(); } cfestring recipedata::id() const @@ -420,6 +415,7 @@ cfestring recipedata::id() const festring fs; #define RPDINFO(o) fs<<(#o)<<"="<<(o)<<"; "; + RPDINFO(lInitialTurn); RPDINFO(rc.IsCanBeSuspended()); RPDINFO(itToolID); @@ -519,6 +515,7 @@ recipedata::recipedata(humanoid* H,uint sel) : rc(H,sel) iMinTurns=0; bFailedTerminateCancel=false; bFailedSuspend=false; + //////////////////////////////////////////////////////////////////////////////////// @@ -705,7 +702,59 @@ struct recipe{ } virtual void fillInfo(){ ABORT("missing recipe info implementation"); } + + static itemvector vitInv(recipedata& rpd,bool bAllowWielded=true,bool bAllowAllEquipped=false){ + itemvector vi; + + //prefer already equipped + if(bAllowWielded){ //TODO not showing on list... + if(rpd.rc.H()->GetRightWielded())vi.push_back(rpd.rc.H()->GetRightWielded()); + if(rpd.rc.H()->GetLeftWielded ())vi.push_back(rpd.rc.H()->GetLeftWielded ()); + } + if(bAllowAllEquipped){ //TODO not showing on list... + for(int c = 0; c < rpd.rc.H()->GetEquipments(); ++c){ + if( + c!=RIGHT_WIELDED_INDEX && + c!=LEFT_WIELDED_INDEX && + rpd.rc.H()->GetEquipment(c) + ){ + vi.push_back(rpd.rc.H()->GetEquipment(c)); + } + } + } + + rpd.rc.H()->GetStack()->FillItemVector(vi); //TODO once, the last item from here had an invalid pointer, HOW? + + return vi; + } + + /** + * @return the lump where it was mixed into (or the input lump) + */ + static item* lumpMix(itemvector vi,item* lumpToMix, bool& bSpendCurrentTurn){ + // to easily (as possible) create a big lump + lump* lumpAtInv=NULL; + for(int i=0;i(vi[i])!=NULL){ + lump* lumpAtInv = (lump*)vi[i]; + if(lumpAtInv->GetMainMaterial()->GetConfig() == lumpToMix->GetMainMaterial()->GetConfig()){ + lumpAtInv->GetMainMaterial()->SetVolume( + lumpAtInv->GetMainMaterial()->GetVolume() + lumpToMix->GetMainMaterial()->GetVolume()); + lumpAtInv->CalculateAll(); + + craftcore::SendToHellSafely(lumpToMix); DBG5("SentToHell",lumpToMix,lumpToMix->GetID(),lumpAtInv,lumpAtInv->GetID()); + bSpendCurrentTurn=true; //this is necessary or item wont be sent to hell... + break; + } + } + } + + return lumpAtInv!=NULL ? lumpAtInv : lumpToMix; + } + void failPlacementMsg(recipedata& rpd){ ADD_MESSAGE("%s can't be placed here.",name.CStr()); rpd.SetAlreadyExplained(); @@ -1263,58 +1312,6 @@ struct recipe{ CIok); } - static itemvector vitInv(recipedata& rpd,bool bAllowWielded=true,bool bAllowAllEquipped=false){ - itemvector vi; - - //prefer already equipped - if(bAllowWielded){ //TODO not showing on list... - if(rpd.rc.H()->GetRightWielded())vi.push_back(rpd.rc.H()->GetRightWielded()); - if(rpd.rc.H()->GetLeftWielded ())vi.push_back(rpd.rc.H()->GetLeftWielded ()); - } - - if(bAllowAllEquipped){ //TODO not showing on list... - for(int c = 0; c < rpd.rc.H()->GetEquipments(); ++c){ - if( - c!=RIGHT_WIELDED_INDEX && - c!=LEFT_WIELDED_INDEX && - rpd.rc.H()->GetEquipment(c) - ){ - vi.push_back(rpd.rc.H()->GetEquipment(c)); - } - } - } - - rpd.rc.H()->GetStack()->FillItemVector(vi); //TODO once, the last item from here had an invalid pointer, HOW? - - return vi; - } - - /** - * @return the lump where it was mixed into (or the input lump) - */ - static item* lumpMix(itemvector vi,item* lumpToMix, bool& bSpendCurrentTurn){ - // to easily (as possible) create a big lump - lump* lumpAtInv=NULL; - for(int i=0;i(vi[i])!=NULL){ - lump* lumpAtInv = (lump*)vi[i]; - if(lumpAtInv->GetMainMaterial()->GetConfig() == lumpToMix->GetMainMaterial()->GetConfig()){ - lumpAtInv->GetMainMaterial()->SetVolume( - lumpAtInv->GetMainMaterial()->GetVolume() + lumpToMix->GetMainMaterial()->GetVolume()); - lumpAtInv->CalculateAll(); - - craftcore::SendToHellSafely(lumpToMix); DBG5("SentToHell",lumpToMix,lumpToMix->GetID(),lumpAtInv,lumpAtInv->GetID()); - bSpendCurrentTurn=true; //this is necessary or item wont be sent to hell... - break; - } - } - } - - return lumpAtInv!=NULL ? lumpAtInv : lumpToMix; - } - static void SetOLT(recipedata& rpd,lsquare* lsqr,int iCfgOLT){ switch(iCfgOLT){ case FORGE: @@ -1495,7 +1492,10 @@ struct srpCutWeb : public recipe{ if(bSuccess){ rpd.SetAlreadyExplained(); material* matSSilk=material::MakeMaterial(SPIDER_SILK); - craftcore::FinishSpawning(rpd, craftcore::PrepareRemains(rpd,matSSilk,CIT_LUMP,RAND()%6+3)); + item* itSS = craftcore::PrepareRemains(rpd,matSSilk,CIT_LUMP,RAND()%6+3); + craftcore::FinishSpawning(rpd, itSS); + bool b=false; + lumpMix(vitInv(rpd),itSS,b); }else{ bool bGetStuckOnTheWeb=false; bool bLoseWeapon=false; @@ -2096,8 +2096,9 @@ struct srpInspect : public recipe{ material* matM = it0->GetMainMaterial(); material* matS = it0->GetSecondaryMaterial(); festring fs; - fs<GetName(DEFINITE)<<" is made of "; - if(matM)fs<GetName(UNARTICLED); + fs << it0->GetName(DEFINITE) << " is made of "; + if(matM) + fs<GetName(UNARTICLED); if(matS){ if(matM)fs<<" and "; //actually, there is only 2nd material if there is main but anyway... fs<GetName(UNARTICLED); @@ -2707,7 +2708,7 @@ struct srpForgeItem : public recipe{ } if(rpd.bTailoringMode){ - if(!recipe::findOLT(rpd,TAILORING_BENCH)){ //must be near it //TODO should be a new bench called TAILORING_BENCH with new graphics one day... + if(!recipe::findOLT(rpd,TAILORING_BENCH)){ //must be near it craftcore::SendToHellSafely(itSpawn); return false; } @@ -3428,7 +3429,9 @@ truth craftcore::Craft(character* Char) //TODO currently this is an over simplif prp->action+" "+prp->name+ (rpd.itSpawnCfg!=0 ? festring(" ("+rpd.fsItemSpawnSearchPrototype+")") : festring())+ ", started at "+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex(), true); - + + rpd.lInitialTurn=game::GetTurn(); + rpd.ClearRefs(); //pointers must be revalidated on the action handler DBG1(rpd.dbgInfo().CStr()); if(Char->SwitchToCraft(rpd)) // everything must be set before this!!! @@ -3586,7 +3589,7 @@ item* crafthandle::SpawnItem(recipedata& rpd, festring& fsCreated) // matM->SetSpoilCounter(rpd.itSpawnMatMainSpoilLevel); material* matM = craftcore::CreateMaterial(MAIN_MATERIAL,rpd); delete itSpawn->SetMainMaterial(matM); - + if(rpd.itSpawnMatSecCfg==0) craftcore::EmptyContentsIfPossible(rpd,itSpawn); else{ diff --git a/Main/Source/command.cpp b/Main/Source/command.cpp index 40e2fea57..4e4fad451 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -22,6 +22,7 @@ #include "game.h" #include "gear.h" #include "god.h" +#include "gods.h" #include "graphics.h" #include "human.h" #include "iconf.h" @@ -37,6 +38,7 @@ #include "stack.h" #include "team.h" #include "whandler.h" +#include "wizautoplay.h" #include "worldmap.h" #include "wsquare.h" #include "wterras.h" @@ -64,7 +66,7 @@ int command::GetKey() const if(Key4>0) return Key4; } - + switch(ivanconfig::GetDirectionKeyMap()) { case DIR_NORM: // Normal @@ -114,6 +116,7 @@ command* commandsystem::Command[] = new command(&IssueCommand, "issue commands to team members", 'I', 'I', 'I', false), new command(&Offer, "offer to gods", 'O', 'f', 'O', false), new command(&Pray, "pray to gods", 'p', 'p', 'p', false), + new command(&AskFavour, "pray for a favour", 'P', 'P', 'P', false), new command(&Sit, "sit down", '_', '_', '_', false), new command(&Rest, "rest and heal", 'h', 'h', 'H', true), new command(&Save, "save and quit", 'S', 'S', 'S', true), @@ -530,12 +533,7 @@ 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 - if(game::IsAutoPickupMatch(ToDrop[c]->GetName(DEFINITE))){ - ToDrop[c]->SetTag('d'); //intentionally dropped: this will let user decide specific items to NOT auto-pickup regex matching - } - } + game::SetDropTag(ToDrop[c]); } Success = true; } @@ -686,6 +684,8 @@ truth commandsystem::PickUp(character* Char) if(game::IsAutoPickupMatch(PileVector[0][c]->GetName(DEFINITE))){ PileVector[0][c]->ClearTag('d'); //intentionally drop tag dismissed for autopickup regex match } + + game::AutoStoreItemInContainer(PileVector[0][c],Char); } ADD_MESSAGE("%s picked up.", PileVector[0][0]->GetName(INDEFINITE, Amount).CStr()); @@ -732,6 +732,8 @@ truth commandsystem::PickUp(character* Char) if(game::IsAutoPickupMatch(ToPickup[c]->GetName(DEFINITE))){ ToPickup[c]->ClearTag('d'); //intentionally drop tag dismissed for autopickup regex match } + + game::AutoStoreItemInContainer(ToPickup[c],Char); } ADD_MESSAGE("%s picked up.", ToPickup[0]->GetName(INDEFINITE, ToPickup.size()).CStr()); @@ -872,8 +874,8 @@ truth commandsystem::Read(character* Char) #ifdef WIZARD // stops auto question timeout that was preventing reading at all - if(Item && game::GetAutoPlayMode()) - game::DisableAutoPlayMode(); + if(Item && wizautoplay::GetAutoPlayMode()) + wizautoplay::DisableAutoPlayMode(); #endif return Item && Char->ReadItem(Item); @@ -1088,9 +1090,7 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng What=c; if(What.GetSize()>0){ if(What[0]==game::MapNoteToken()){ //having map note token means it is already a map note, so let it be read/write at will - std::string str=What.CStr(); - What.Empty(); - What<HasBeenSeen()){ /***** @@ -1145,7 +1145,8 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng break; festring What = ToAddLabel[0]->GetLabel(); - if(game::StringQuestion(What, CONST_S("What would you like to inscribe on this item?"), WHITE, 0, 20, true) == NORMAL_EXIT) + int iMaxChars=150; // item labels can contain user custom hints to let the algorithm use these to perform automatic actions + if(game::StringQuestion(What, CONST_S("What would you like to inscribe on this item?"), WHITE, 0, iMaxChars, true) == NORMAL_EXIT) for(int i=0;iSetLabel(What); } @@ -1157,6 +1158,99 @@ truth commandsystem::WhatToEngrave(character* Char,bool bEngraveMapNote,v2 v2Eng return false; } +truth commandsystem::AskFavour(character* Char) +{ + felist felFavourList(CONST_S("Ask a favour from Whom?")); + felFavourList.SetEntryDrawer(game::GodEntryDrawer); + + int iTot=0; + std::vector> vSelectableFavours; + for(int c = 1; c <= GODS; ++c){ + god* pgod = game::GetGod(c); + if(!pgod->IsKnown()) continue; + + bool bOk=false; + if(pgod->GetBasicAlignment() == GOOD && game::GetPlayerAlignment() > 0) bOk=true; + if(pgod->GetBasicAlignment() == NEUTRAL && game::GetPlayerAlignment() == 0) bOk=true; + if(pgod->GetBasicAlignment() == EVIL && game::GetPlayerAlignment() < 0) bOk=true; + if(c == Char->GetLSquareUnder()->GetDivineMaster()) bOk=true; + + bool bGodSectionEntry=true; + std::vector v = pgod->GetKnownSpells(); + for(auto piFavour = v.begin(); piFavour != v.end(); ++piFavour){ + festring fsFavour = CONST_S("") + god::GetFavourName(*piFavour); + + col16 col = bOk ? LIGHT_GRAY : DARK_GRAY; + // if(!bOk && game::WizardModeIsReallyActive()) col=RED; + + if(bGodSectionEntry){ + festring fsGodEntry = CONST_S("") + game::GetAlignment(pgod->GetAlignment()); + fsGodEntry.Resize(4); // Longest alignment name is L++ or C--, so have min of one space. + fsGodEntry << pgod->GetName() << " might grant you a favour."; //TODO: won't for known gods with no favours, or only name? + if(ivanconfig::IsShowGodInfo()) + fsGodEntry << " " << game::GetGod(c)->GetLastKnownRelation(); + felFavourList.AddEntry(fsGodEntry, DARK_GRAY, 20, c, false); + bGodSectionEntry=false; + } + felFavourList.AddEntry(fsFavour, col, 0, NO_IMAGE, bOk || game::WizardModeIsReallyActive()); + // TODO: favour F1 Description + + if(bOk || game::WizardModeIsReallyActive()){ +// std::pair GS; +// GS.first = pgod; +// GS.second = *piSpell; + vSelectableFavours.push_back(std::make_pair(pgod,*piFavour)); + } + + iTot++; + } + } + + festring fsMsg; + //fsMsg = fsMsg+"You don't know about any "+game::GetVerbalPlayerAlignment()+" favours..."; //TODO: How exactly to phrase this? + fsMsg = fsMsg+"You can call upon no favours."; + + if(iTot>0 && vSelectableFavours.size()==0){ + felFavourList.AddEntry(cfestring("(")+fsMsg+")", DARK_GRAY, 0, NO_IMAGE, false); + } + + int Select = LIST_WAS_EMPTY; + if(iTot>0){ + game::SetStandardListAttributes(felFavourList); + if(vSelectableFavours.size()>0) + felFavourList.AddFlags(SELECTABLE); + Select = felFavourList.Draw(); + } + + if(Select == LIST_WAS_EMPTY || vSelectableFavours.size()==0) + { +// ADD_MESSAGE("You don't know about any %s favours...", game::GetVerbalPlayerAlignment()); + ADD_MESSAGE(fsMsg.CStr()); + return false; + } + + if(Select & FELIST_ERROR_BIT) + return false; + + god* G = vSelectableFavours[Select].first; + int iFavour = vSelectableFavours[Select].second; + int iDebit=FAVOURDEBIT_AUTO; + int DivineMaster = Char->GetLSquareUnder()->GetDivineMaster(); + if(DivineMaster){ + if(G == game::GetGod(DivineMaster)) + iDebit=FAVOURDEBIT_AUTOHALF; + else + iDebit=FAVOURDEBIT_AUTODOUBLE; + } + + if(G->Favour(iFavour,iDebit)){ + Char->EditAP(-1000); + return true; + } + + return false; +} + truth commandsystem::Pray(character* Char) { felist Panthenon(CONST_S("To Whom you want to address your prayers?")); @@ -1171,9 +1265,9 @@ truth commandsystem::Pray(character* Char) return false; } + festring desc; if(!DivineMaster) { - festring desc; for(int c = 1; c <= GODS; ++c) if(game::GetGod(c)->IsKnown()) { @@ -1188,7 +1282,9 @@ truth commandsystem::Pray(character* Char) else if(game::GetGod(DivineMaster)->IsKnown()) { - Panthenon.AddEntry(game::GetGod(DivineMaster)->GetCompleteDescription(), LIGHT_GRAY, 20, DivineMaster); + desc << game::GetGod(DivineMaster)->GetCompleteDescription(); + if(ivanconfig::IsShowGodInfo())desc << " " << game::GetGod(DivineMaster)->GetLastKnownRelation(); + Panthenon.AddEntry(desc, LIGHT_GRAY, 20, DivineMaster); Panthenon.SetLastEntryHelp(festring() << game::GetGod(DivineMaster)->GetName() << ", the " << game::GetGod(DivineMaster)->GetDescription()); Known[0] = DivineMaster; } @@ -1330,9 +1426,14 @@ truth commandsystem::Offer(character* Char) Item->SendToHell(); Char->DexterityAction(5); /** C **/ return true; - } - else + } else { + if(ivanconfig::IsDropBeforeOffering()) + if(!game::IsQuestItem(Item)){ + Item->MoveTo(Char->GetLSquareUnder()->GetStack()); //drops before offering so non accepted will unclutter player inventory + game::SetDropTag(Item); + } return false; + } } else return false; @@ -1999,7 +2100,7 @@ truth commandsystem::WizardMode(character* Char) truth commandsystem::AutoPlay(character* Char) { - game::IncAutoPlayMode(); + wizautoplay::IncAutoPlayMode(); return false; } diff --git a/Main/Source/curseddeveloper.cpp b/Main/Source/curseddeveloper.cpp new file mode 100644 index 000000000..ac736f4b7 --- /dev/null +++ b/Main/Source/curseddeveloper.cpp @@ -0,0 +1,614 @@ +/* + * + * 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 "dbgmsgproj.h" +#include "devcons.h" + +#include +#include + +/** + * This is a developer environment variable to test the game without wizard mode. + */ + +festring fsKCPrefix="CursedDeveloperKC="; +character* GetPlayerCharWithTorsoForKC() +{ + character* P=game::GetPlayer(); + + while(P->GetTorso()->GetLabel().Find(fsKCPrefix)==festring::NPos){ + if(!P->GetPolymorphBackup()) + break; + DBG2(P->GetID(),P->GetNameSingular().CStr()); + P=P->GetPolymorphBackup(); + DBGEXEC(if(P){DBG2(P->GetID(),P->GetNameSingular().CStr());}); + } + + return P; +} + +bool curseddeveloper::IsCursedDeveloper() +{ +#ifdef CURSEDDEVELOPER + if(bCursedDeveloper)return true; //this is for the real compiled mode +#endif + + static character* Pprevious=NULL; + static int iCursedDeveloperDoubleCheck=0; + + if(game::GetPlayer()!=Pprevious){ + iCursedDeveloperDoubleCheck=0; // to let it be checked again, mainly when coming back from main menu + Pprevious=game::GetPlayer(); + } + + if(iCursedDeveloperDoubleCheck!=0) + return iCursedDeveloperDoubleCheck==1; + + /** + * deep check/validation against a savegame that could have been used initially + * on the cursed developer mode and after on a normal gameplay. + */ + character* P = GetPlayerCharWithTorsoForKC(); + if(P->GetTorso()->GetLabel().Find(fsKCPrefix)!=festring::NPos) + iCursedDeveloperDoubleCheck = 1; //cursed + else + iCursedDeveloperDoubleCheck = 2; //normal + + return iCursedDeveloperDoubleCheck==1; +} + +#ifdef CURSEDDEVELOPER + +bool curseddeveloper::bCursedDeveloper = [](){char* pc=getenv("IVAN_CURSEDDEVELOPER");return strcmp(pc?pc:"","true")==0;}(); +bool curseddeveloper::bCursedDeveloperTeleport = false; +long curseddeveloper::lKillCredit=0; +bool curseddeveloper::bNightmareWakeUp=false; +bool curseddeveloper::bResurrect=false; +character* curseddeveloper::Killer=NULL; +bool bAlwaysTryToWakeup=false; + +#define HEAL_1 1 +#define HEAL_MINOK 2 +#define HEAL_MAX 3 + +/** + * is some special/named/important character + */ +bool IsSpecialCharacter(character* C){return C && !C->CanBeCloned();} + +void curseddeveloper::NightmareWakeUp(character* P) +{ + bNightmareWakeUp=false; + + ADD_MESSAGE("You had a nightmare! And... for some reason, you feel stronger..."); + P->GetLSquareUnder()->SpillFluid(P, liquid::Spawn(SWEAT, 5 * P->GetAttribute(ENDURANCE))); + + if(RAND()%3){ + P->GetLSquareUnder()->SpillFluid(P, liquid::Spawn(OMMEL_URINE, 5 * P->GetAttribute(ENDURANCE))); //ugh.. not ommel's tho.. TODO will this buff the player? + ADD_MESSAGE("You need a bath now..."); + } + + if(lKillCredit!=0) + ResetKillCredit(); +} + +void curseddeveloper::ResetKillCredit(festring fsCmdParams) +{ + ModKillCredit(lKillCredit * -1); +} + +long curseddeveloper::UpdateKillCredit(character* Victim,int iMod) +{ + character* P=game::GetPlayer(); + if(!P)return lKillCredit; + + P=GetPlayerCharWithTorsoForKC(); + festring fsKillCredit = P->GetTorso()->GetLabel(); + fsKillCredit.Erase(0,fsKCPrefix.GetSize()); + DBG1(fsKillCredit.CStr()); + long lKCStored=0; + if(!fsKillCredit.IsEmpty()) + lKCStored = atol(fsKillCredit.CStr()); + DBG1(lKCStored); + + if(Victim){ + int i = Victim->GetRelativeDanger(P)*10; + DBG1(i); + if(i<1)i=1; + lKCStored+=i; + } + + lKCStored+=iMod; + + if(bNightmareWakeUp) + NightmareWakeUp(P); + + DBG1(lKCStored); + P->GetTorso()->SetLabel(festring()<BodyParts; ++c){ //only enough to continue testing normal gameplay + if(!CreateBP(c))continue; + + HealBP(c,iMode); + } + P->CalculateBodyPartMaxHPs(0); //this also calculates the overall current HP + DBG2(P->HP,P->MaxHP); + if(P->HP > P->MaxHP) // it MUST be ok here!!! + ABORT("HP>MaxHP %d>%d",P->HP,P->MaxHP); +} + +bool curseddeveloper::CreateBP(int iIndex) +{ + character* P = game::GetPlayer(); + + if(dynamic_cast(P)) + { + //ok + } + else if(iIndex!=TORSO_INDEX) //can be polymorphed into non humanoid + return false; + + bodypart* bp = P->GetBodyPart(iIndex); + + if(!bp){ + int iMod=1; + for(ulong iOBpID : P->GetOriginalBodyPartID(iIndex)){ + bp = dynamic_cast(game::SearchItem(iOBpID)); + if(bp){ + if(iIndex == HEAD_INDEX){ + /** + * when a kamikaze dwarf explodes and player's head flies away, + * bringing it back apparently causes SEGFAULT at: + * game::run() > pool::be() > character::be() at `BodyPart->Be();` + * there is no null pointer, the `BodyPart` is valid and can be debugged, + * but when BodyPart->Be() is called it SEGFAULTs... + */ + bp->RemoveFromSlot(); + bp->SendToHell(); //so lets just destroy it to let a new one be created + bp=NULL; + } + break; + } + } + + if(bp){ + bp->RemoveFromSlot(); + P->AttachBodyPart(bp); + ADD_MESSAGE("Your creepy %s comes back to you.",bp->GetName(UNARTICLED).CStr()); + ModKillCredit(-1); + iMod=5; + }else{ + if(P->CanCreateBodyPart(iIndex)){ + bp=P->CreateBodyPart(iIndex); + if(bp){ + ADD_MESSAGE("A new cursed %s vibrates and grows on you.",bp->GetName(UNARTICLED).CStr()); + ModKillCredit(-2); + iMod=10; + } + } + } + + if(bp){ + bp->SpillFluid(P,liquid::Spawn(LIQUID_DARKNESS,iMod*2)); + if(iIndex == HEAD_INDEX){ + int iTm=iMod*10; + P->BeginTemporaryState(CONFUSED, iTm + RAND()%iTm); + } + } + } + + if(bp && bp->GetHP() > bp->GetMaxHP()){ //TODO how does this happens??? + DBG4(iIndex,bp->GetHP(),bp->GetMaxHP(),bp->GetBodyPartName().CStr()); + bp->SetHP(-1); //to allow properly fixing + } + + return bp!=NULL; +} + +bool curseddeveloper::HealBP(int iIndex,int iMode,int iResHPoverride) +{ + if(!CreateBP(iIndex))return false; + + character* P = game::GetPlayer(); + + /** + * How to prevent endless die loop? + * Clear the bad effects? better not, let them continue working. + * A bit more of HP to the core body parts may suffice (funny head is not one lol). + */ + bodypart* bp = P->GetBodyPart(iIndex); + if(bp && bp->GetHP() < bp->GetMaxHP()){ + int iDiv=3; + int iHpMinUsable = bp->GetMaxHP()/iDiv + (bp->GetMaxHP()%iDiv>0 ? 1 : 0); //ceil + + int iHpRestore = 0; + switch(iMode){ + case HEAL_1: + iHpRestore = 1; + break; + case HEAL_MINOK: + iHpRestore = iHpMinUsable; + break; + case HEAL_MAX: + iHpRestore = bp->GetMaxHP(); + break; + } + + if(iResHPoverride>0)iHpRestore=iResHPoverride; + + if(bp->GetHP() < iHpRestore){ + DBG4(iIndex,bp->GetHP(),bp->GetMaxHP(),bp->GetBodyPartName().CStr()); + bp->SetHP(iHpRestore); + bp->SignalPossibleUsabilityChange(); + switch(iMode){ + case HEAL_1: + break; + case HEAL_MINOK: + ModKillCredit(-1); + break; + case HEAL_MAX: + ModKillCredit(-iDiv); + break; + } + } + + DBG5(iIndex,iHpMinUsable,bp->GetHP(),bp->GetMaxHP(),bp->GetBodyPartName().CStr()); + +// bp->SetHP(P->GetMaxHP()>iMinHpOK ? iMinHpOK : P->GetMaxHP()); +// DBG4(c,bp->GetHP(),bp->GetMaxHP(),bp->GetBodyPartName().CStr()); + return true; + } + return false; +} +//bool cursedDeveloper::HealTorso(bodypart* bp) +//{ +// character* P = game::GetPlayer(); +// +// /** +// * How to prevent endless die loop? +// * Clear the bad effects? better not, let them continue working. +// * A bit more of HP to the core body parts may suffice (funny head is not one lol). +// */ +// static int iTorsoHpMinOk=10; //this is to fight mustard gas +// if(P->GetBodyPart(TORSO_INDEX)==bp && bp->GetHP() < iTorsoHpMinOk){ +// bp->SetHP(P->GetMaxHP()>iTorsoHpMinOk ? iTorsoHpMinOk : P->GetMaxHP()); +// DBG4(c,bp->GetHP(),bp->GetMaxHP(),bp->GetBodyPartName().CStr()); +// return true; +// } +// return false; +//} + +//curseddeveloper::curseddeveloper() +//{ +// devcons::AddDevCmd("RestoreLimbs",curseddeveloper::RestoreLimbs, +// "[1|2|3] 1=1HP 2=minUsableHP 3=maxHP. Restore missing limbs to the cursed developer, better use only if the game becomes unplayable."); +//} +bool bAllowWakeUp=true; +void SetAllowWakeUp(festring fs) +{ + bAllowWakeUp=true; + if(fs=="no") + bAllowWakeUp=false; + + if(bAllowWakeUp){ + ADD_MESSAGE("You may wakeup..."); + }else{ + ADD_MESSAGE("You won't wakeup!"); + } +} +void SetAlwaysTryToWakeup(festring fs) +{ + bAlwaysTryToWakeup=false; + if(fs=="yes") + bAlwaysTryToWakeup=true; + + if(bAlwaysTryToWakeup){ + ADD_MESSAGE("You will wakeup..."); + }else{ + ADD_MESSAGE("You won't wakeup!"); + } +} +void curseddeveloper::Init(){ + devcons::AddDevCmd("RestoreLimbs",curseddeveloper::RestoreLimbs, + "[1|2|3] 1=1HP 2=minUsableHP 3=maxHP. Restore missing limbs, better use only if the game becomes unplayable (cursed immortal)."); + devcons::AddDevCmd("ResetKC",curseddeveloper::ResetKillCredit, + "to help make tests related to KillCredit's negative value (cursed immortal)."); + devcons::AddDevCmd("AllowWakeup",SetAllowWakeUp, + "[no] to help on making tests ignoring KC negative value (cursed immortal)."); + devcons::AddDevCmd("AlwaysTryToWakeup",SetAlwaysTryToWakeup, + "[yes] to help on making tests (cursed immortal)."); +} + +bool curseddeveloper::LifeSaveJustABitIfRequested() +{ + if(bResurrect){ + bool b=LifeSaveJustABit(Killer); + Killer=NULL; + bResurrect=false; + return b; + } + return false; +} + +bool curseddeveloper::LifeSaveJustABit(character* Killer) +{ + if(!bCursedDeveloper) + return false; + + if(Killer && Killer->IsPlayer()) + ABORT("What? player killer is player? name='%s' id='%d'",Killer->GetNameSingular().CStr(),Killer->GetID()); + + character* P = game::GetPlayer(); + game::DrawEverything(); + + int iKillerBuff=0,iKillerDebuff=0; + bool bRev; + bool bStay = BuffAndDebuffPlayerKiller(Killer,iKillerBuff,iKillerDebuff,bRev); //to spice it up + if(Killer){ + festring fsAN = Killer->GetAssignedName(); + festring fsToken=" <[B"; + ulong pos = fsAN.Find(fsToken); + if(pos!=festring::NPos) + fsAN.Erase(pos,fsAN.GetSize()-pos); + fsAN<"; + Killer->SetAssignedName(fsAN); + } + + // automatic minimal to save life +// HealTorso(P->GetTorso()); + HealBP(HEAD_INDEX,HEAL_1); + HealBP(TORSO_INDEX,0,10);//10hp is min to fight mustard gas + HealBP(GROIN_INDEX,HEAL_1); + + if(P->GetNP() < HUNGER_LEVEL) + P->SetNP(HUNGER_LEVEL); //to avoid endless sleeping + + if(P->HasStateFlag(PANIC)) + P->DeActivateTemporaryState(PANIC); //to be able to do something + + if(P->GetAction()) + P->GetAction()->Terminate(false); //just to avoid messing any action + + // at death spot + if(!game::IsInWilderness()) + P->GetLSquareUnder()->SpillFluid(P, liquid::Spawn(BLOOD, 30 * P->GetAttribute(ENDURANCE))); + + if(!bStay && Killer && !game::IsInWilderness() && iKillerDebuff==0){ + //teleport is required to prevent death loop: killer keeps killing the player forever on every turn + if(Killer->GetSquaresUnder()>1){ //huge foes + bCursedDeveloperTeleport=true; + if(!P->GetLSquareUnder()->GetEngraved()) + game::SetMapNote(P->GetLSquareUnder(),festring("@Your cursed life was saved here at ")<TeleportRandomly(true); + ADD_MESSAGE("You see a flash of dark light and teleport away from the killing blow!"); + bCursedDeveloperTeleport=false; + }else{ + if(IsSpecialCharacter(Killer)){ + bool bRestoreTL=false; + if(Killer->HasStateFlag(TELEPORT_LOCK)){ + Killer->DeActivateTemporaryState(TELEPORT_LOCK); + bRestoreTL=true; + } + Killer->TeleportRandomly(true); + if(bRestoreTL) + Killer->GainIntrinsic(TELEPORT_LOCK); + } + } + } + + // at resurrect spot + if(iKillerDebuff>0) // if enemy got too powerful, buff the player randomly + if(!game::IsInWilderness()) + P->GetLSquareUnder()->SpillFluid(NULL, liquid::Spawn(MAGIC_LIQUID, 30 * P->GetAttribute(WISDOM))); + +// if(bRev || IsSpecialCharacter(Killer)){ + character* M = P->DuplicateToNearestSquare(P, MIRROR_IMAGE|IGNORE_PROHIBITIONS|CHANGE_TEAM); + if(M){ + int x=1; + if(GetKillCredit()>x && P->GetBodyPart(RIGHT_ARM_INDEX))x++; + if(GetKillCredit()>x && P->GetBodyPart(LEFT_ARM_INDEX ))x++; + if(GetKillCredit()>x && P->GetBodyPart(RIGHT_LEG_INDEX))x++; + if(GetKillCredit()>x && P->GetBodyPart(LEFT_LEG_INDEX ))x++; + + static int i1Min=33; //33 is 1 min or 1 turn right? see: game::GetTime() (any relation with 30 frames per second? 30 ticks?) + int iXtra = GetKillCredit()<0 ? 10 : 1; + int iLE = i1Min*5*x*iXtra; + M->SetLifeExpectancy(iLE, iLE*2); + ModKillCredit(-1*x); + } +// } + + ADD_MESSAGE("Your curse forbids you to rest and be remembered..."); + + UpdateKillCredit(); + + game::DrawEverything(); + + int iDung=3,iLvl=0; //tweiraith island + bool bIsAtHomeIsland = P->GetDungeon()->GetIndex()==iDung && P->GetLevel()->GetIndex()==iLvl; + DBG6(P->GetDungeon()->GetIndex(),iDung,P->GetLevel()->GetIndex(),iLvl,lKillCredit,bIsAtHomeIsland); + if(lKillCredit<0 && bIsAtHomeIsland){ + ResetKillCredit(); //to prevent pointless too negative value at home town + }else + if(bAllowWakeUp && lKillCredit<0 && !game::IsInWilderness() && !bIsAtHomeIsland && Killer){ + if(RAND()%10==0 || bAlwaysTryToWakeup){ + for(int i=0;i<10;i++){ + ADD_MESSAGE("You try to wakeup..."); + if(game::TryTravel(iDung, iLvl, DOUBLE_BED, false, true)){ //TODO should be the small bed at the small house + bNightmareWakeUp=true; + ADD_MESSAGE("You finally wakeup."); + UpdateKillCredit(); //to call nightmare wakeup + return true; // after TryTravel() avoid most code... + } + P->TeleportRandomly(true); //try to move away from foes to be able to travel + ADD_MESSAGE("You feel haunted!"); + } + }else{ + ADD_MESSAGE("You feel unconfortable..."); + } + } + + return true; +} + +bool AddState(character* Killer,long Flag,cchar* FlagName,long FlagD,cchar* FlagNameD,int& iB) +{ + if(FlagD && Killer->HasStateFlag(FlagD)){ + DBG5("DEACTIVATING",Killer->GetName(DEFINITE).CStr(),FlagD,FlagNameD,iB); + Killer->DeActivateTemporaryState(FlagD); + } + + if(Flag){ + DBG5("TRYADD",Killer->GetName(DEFINITE).CStr(),Flag,FlagName,iB); + if(!Killer->HasStateFlag(Flag)){ + Killer->GainIntrinsic(Flag); + if(Killer->HasStateFlag(Flag)){ + iB++; + DBG2("SUCCEED TO ADD!!!",iB); + return true; + }else{ + DBG1("FAILED TO ADD"); + } + }else{ + iB++; + DBG1("HAS ALREADY"); + } + } + + return false; +} + +/** + * This will make the Special NPC that kills the player more challenging for every kill. + * Non special NPCs will fall faster. + * TODO could these NPC permanent upgrades be part of the normal gameplay in some way? May be, the life saving ammulet could let these buffs also be applied? + * @return if player should stay (true) or teleport (false) + */ +bool curseddeveloper::BuffAndDebuffPlayerKiller(character* Killer,int& riBuff,int& riDebuff,bool& rbRev) +{ + if(!bCursedDeveloper)return true; + if(!Killer)return true; + if(game::IsInWilderness())return true; + + riDebuff=0; + rbRev=false; + + // BUFFs, every death makes it harder to player: + riBuff=1; +#define ASRET(e,b) if(AddState(Killer,e,#e,0,NULL,b) && IsSpecialCharacter(Killer))return false; +//#define RMS(d,b) if(AddState(Killer,0,NULL,d,#d,b) && IsSpecialCharacter(Killer))return false; +#define RMS(d,b) AddState(Killer,0,NULL,d,#d,b); + bool bAlreadyRev = Killer->GetAssignedName().Find("{R}")!=festring::NPos; + if(IsSpecialCharacter(Killer)){ + if(!bAlreadyRev){ + ASRET(ESP,riBuff); + ASRET(INFRA_VISION,riBuff); + // ASRETD(HASTE,SLOW,riBuff); + ASRET(HASTE,riBuff); + ASRET(SWIMMING,riBuff); + ASRET(ETHEREAL_MOVING,riBuff); + ASRET(REGENERATION,riBuff); + ASRET(LEVITATION,riBuff); + ASRET(GAS_IMMUNITY,riBuff); + ASRET(TELEPORT_LOCK,riBuff); + ASRET(POLYMORPH_LOCK,riBuff); + }else{ + RMS(ESP,riBuff); + RMS(INFRA_VISION,riBuff); + RMS(HASTE,riBuff); + RMS(SWIMMING,riBuff); + RMS(ETHEREAL_MOVING,riBuff); + RMS(REGENERATION,riBuff); + RMS(LEVITATION,riBuff); + RMS(GAS_IMMUNITY,riBuff); + RMS(TELEPORT_LOCK,riBuff); + RMS(POLYMORPH_LOCK,riBuff); + } + } + + /************************* + * DEBUFFs, after player has taken too much it is time to make it stop, but slowly: + */ + riDebuff=1; + ASRET(HICCUPS,riDebuff); +// ASRETD(SLOW,HASTE,riDebuff); + ASRET(SLOW,riDebuff); +//no, adds more mobs... ASRET(PARASITE_TAPE_WORM,riDebuff); + ASRET(CONFUSED,riDebuff); + ASRET(POLYMORPH,riDebuff); //this may be the only way to defeat some special mob +//no, may mess the player... ASRET(LEPROSY,riDebuff); + if(RAND()%10==0){ + ASRET(POISONED,riDebuff); + ModKillCredit(-2); + } + + // Revenge, grant it will stop: + if(bAlreadyRev || RAND()%5==0){ + for(int i=0; i < ( RAND() % (IsSpecialCharacter(Killer)?5:2) + 1 ) ;i++){ + if(Killer->IsDead())break; + game::GetCurrentLevel()->Explosion( + NULL, CONST_S("Killed by cursed fire!"), Killer->GetPos(), 9/*1 square size*/, false, true); + ModKillCredit(-1); + } + } + + if(bAlreadyRev || RAND()%10==0){ + bodypart* bpHit=NULL; + if(dynamic_cast(Killer)){ + switch(RAND()%(10+5)){ + case 0: + bpHit = Killer->GetBodyPart(HEAD_INDEX); + if(bpHit)break; + case 1: + bpHit = Killer->GetBodyPart(GROIN_INDEX); + if(bpHit)break; + case 2: case 3: + bpHit = Killer->GetBodyPart(LEFT_ARM_INDEX); + if(bpHit)break; + case 4: case 5: + bpHit = Killer->GetBodyPart(RIGHT_ARM_INDEX); + if(bpHit)break; + case 6: case 7: + bpHit = Killer->GetBodyPart(LEFT_LEG_INDEX); + if(bpHit)break; + case 8: case 9: + bpHit = Killer->GetBodyPart(RIGHT_LEG_INDEX); + if(bpHit)break; + default: break; + } + } + if(!bpHit) + bpHit=Killer->GetTorso(); + bpHit->SpillFluid(NULL, liquid::Spawn(SULPHURIC_ACID, 5 * game::GetPlayer()->GetAttribute(WISDOM))); + ADD_MESSAGE("Cursed acid hits %s!", Killer->GetName(DEFINITE).CStr()); + ModKillCredit(-3); + } + + rbRev=true; + + UpdateKillCredit(); + + return true; +} + +#endif //CURSEDDEVELOPER diff --git a/Main/Source/devcons.cpp b/Main/Source/devcons.cpp index 7c7ba3088..5ecede14c 100644 --- a/Main/Source/devcons.cpp +++ b/Main/Source/devcons.cpp @@ -28,6 +28,7 @@ #include "specialkeys.h" #include "confdef.h" #include "lterras.h" +#include "dbgmsgproj.h" /** * ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! ATTENTION!!! @@ -46,6 +47,23 @@ std::vector vCharLastSearch; std::vector vItemLastSearch; #ifdef WIZARD +#ifdef DBGMSG +void DbgSetVar(festring fsParams){ + if(!fsParams.IsEmpty()){ + std::string part; + std::stringstream iss(fsParams.CStr()); + + iss >> part; + std::string strId=part; + + iss >> part; + std::string strValue=part; + + DBGSETV(strId,strValue); + DEVCMDMSG2P("DBG ID='%s' Value='%s'",strId.c_str(),strValue.c_str()); + } +} +#endif //DBGMSG truth IsValidChar(character* C){ if(!C->Exists()) return false; @@ -392,7 +410,7 @@ void ListItems(festring fsParams){ } DEVCMDMSG2P("total: Chars=%d Items=%d",vCharLastSearch.size(),vItemLastSearch.size()); } -#endif +#endif //WIZARD void devcons::Init() { @@ -442,9 +460,12 @@ void devcons::OpenCommandsConsole() ADDCMD(ListChars,"[[filterCharID:ulong]|[strCharNamePart:string]] List characters on current dungeon level",true); ADDCMD(ListItems,"[[c|i] <|>] List items on current dungeon level, including on characters ('c' will filter by character ID or name) inventory and containers",true); ADDCMD(SetVar,festring()<<" set a float variable index (max "<<(iVarTot-1)<<") to be used on debug",true); +#ifdef DBGMSG + ADDCMD(DbgSetVar," sets a DBGMSG variable.",true); +#endif //DBGMSG ADDCMD(TeleToChar," teleports near 1st character matching filter.",true); ADDCMD(TeleToMe," teleports all NPCs matching filter to you.",true); -#endif +#endif //WIZARD return true; }(); @@ -472,11 +493,31 @@ void devcons::OpenCommandsConsole() festring fsQ; if(game::WizardModeIsReallyActive()) fsQ="Developer(WIZ) "; - fsQ<<"Console Command (try 'help' or '?'):"; + fsQ<<"Console Command(s) separated by ';' (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(); + festring fsCmd; + DBG1(fsFullCmd.CStr()); + for(;;){ + fsCmd=fsFullCmd; + + if(fsCmd.IsEmpty()) + break; + + int iPos=fsFullCmd.Find(";",0); + if(iPos!=festring::NPos){ //found it + fsCmd.Resize(iPos); //erases from ';' inclusive + DBG1(fsCmd.CStr()); + fsFullCmd.Erase(0,iPos+1); //erases til ';' inclusive + DBG1(fsFullCmd.CStr()); + } + + runCommand(fsCmd); + msgsystem::DrawMessageHistory(); + + if(iPos==festring::NPos) //no more commands to be run + break; + } }else break; } diff --git a/Main/Source/dungeon.cpp b/Main/Source/dungeon.cpp index 95bf89cc5..abf2463a4 100644 --- a/Main/Source/dungeon.cpp +++ b/Main/Source/dungeon.cpp @@ -24,6 +24,7 @@ #include "message.h" #include "save.h" #include "script.h" +#include "wizautoplay.h" dungeon::dungeon(int Index) : Index(Index) @@ -127,7 +128,7 @@ truth dungeon::PrepareLevel(int Index, truth Visual) true, &game::BusyAnimation); game::TextScreen(CONST_S("Entering ") + GetLevelDescription(Index) + CONST_S("...\n\nPress any key to continue."), - Displacement, WHITE, game::GetAutoPlayMode()<2, + Displacement, WHITE, wizautoplay::GetAutoPlayMode()GetAudioPlayList()->Size; ++i ) { festring Music = LevelScript->GetAudioPlayList()->Data[i]; @@ -202,7 +203,7 @@ void dungeon::PrepareMusic(int Index) if( hasCurrentTrack == false ) { audio::SetPlaybackStatus(audio::STOPPED); - audio::ClearMIDIPlaylist(); + audio::ClearMIDIPlaylist(); //clear it all for( int i = 0; i < LevelScript->GetAudioPlayList()->Size; ++i ) { festring Music = LevelScript->GetAudioPlayList()->Data[i]; @@ -211,9 +212,6 @@ void dungeon::PrepareMusic(int Index) audio::SetPlaybackStatus(audio::PLAYING); } - - - } void dungeon::SaveLevel(cfestring& SaveName, int Number, truth DeleteAfterwards) diff --git a/Main/Source/fluid.cpp b/Main/Source/fluid.cpp index bec487267..883ab39f4 100644 --- a/Main/Source/fluid.cpp +++ b/Main/Source/fluid.cpp @@ -675,6 +675,11 @@ void fluid::Destroy() SendToHell(); } +int fluid::GetTrapType() const +{ + return Liquid->GetType() | FLUID_TRAP; +} + truth fluid::UseImage() const { return !(Flags & FLUID_INSIDE) diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index 321449fd4..5ae2c91ee 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -41,6 +41,7 @@ #include "bugworkaround.h" #include "confdef.h" #include "command.h" +#include "curseddeveloper.h" #include "definesvalidator.h" #include "feio.h" #include "felist.h" @@ -67,6 +68,7 @@ #include "stack.h" #include "team.h" #include "whandler.h" +#include "wizautoplay.h" #include "wsquare.h" #include "dbgmsgproj.h" @@ -193,7 +195,6 @@ festring game::DefaultWish; festring game::DefaultChangeMaterial; festring game::DefaultDetectMaterial; truth game::WizardMode; -int game::AutoPlayMode=0; int game::SeeWholeMapCheatMode; truth game::GoThroughWallsCheat; int game::QuestMonstersFound; @@ -298,6 +299,7 @@ int game::GetScreenYSize() { //actually dugeon visible height in tiles count void game::AddCharacterID(character* Char, ulong ID) { + DBG2(ID,Char->GetID()); // do not use GetName() here, will crash!: ,Char->GetName(INDEFINITE).CStr()); CharacterIDMap.insert(std::make_pair(ID, Char)); } void game::RemoveCharacterID(ulong ID) @@ -311,6 +313,7 @@ void game::RemoveCharacterID(ulong ID) } void game::AddItemID(item* Item, ulong ID) { + DBG2(ID,Item->GetID());// do not use GetName() here, will crash!: ,Item->GetName(INDEFINITE).CStr()); ItemIDMap.insert(std::make_pair(ID, Item)); } @@ -341,7 +344,10 @@ void game::UpdateItemID(item* Item, ulong ID) } void game::AddTrapID(entity* Trap, ulong ID) { - if(ID) TrapIDMap.insert(std::make_pair(ID, Trap)); + if(ID){ + DBG2(ID,Trap->GetTrapID()); // do not use GetTrapType() here, will crash!: ,Trap->GetTrapType()); + TrapIDMap.insert(std::make_pair(ID, Trap)); + } } void game::RemoveTrapID(ulong ID) { @@ -350,8 +356,10 @@ void game::RemoveTrapID(ulong ID) if(itr == TrapIDMap.end() || itr->second == NULL){ if(!bugfixdp::IsFixing()) ABORT("AlreadyErased:TrapID %lu",ID); - }else + }else{ + DBG1(ID); TrapIDMap.erase(itr); + } } } void game::UpdateTrapID(entity* Trap, ulong ID) @@ -385,7 +393,7 @@ void game::InitScript() truth game::IsQuestItem(item* it) //dont protect against null item* it may be a problem outside here. { - return it->IsQuestItem(); + return it->IsQuestItem(); //TODO this code (used outside here) should suffice instead of this game::IsQuestItem() whole function } void game::PrepareToClearNonVisibleSquaresAround(v2 v2SqrPos) { @@ -957,6 +965,8 @@ void game::DeInit() delete GameScript; msgsystem::Format(); DangerMap.clear(); + + Player = 0; } void game::Run() @@ -1260,6 +1270,19 @@ truth game::OnScreen(v2 Pos) && Pos.X < GetCamera().X + GetScreenXSize() && Pos.Y < GetCamera().Y + GetScreenYSize(); } +//static int iMaxNoteLength=100; +//void game::AppendMapNote(lsquare* lsqrN,festring What) +//{ +// festring finalWhat; +// finalWhat << game::MapNoteToken(); +// finalWhat << What; +// if(lsqrN->GetEngraved()) +// finalWhat << " " << lsqrN->GetEngraved(); +// static int iMaxLength=100; +// if(finalWhat.GetSize()>iMaxLength) +// finalWhat.Resize(iMaxLength); +// lsqrN->Engrave(finalWhat); +//} void game::SetMapNote(lsquare* lsqrN,festring What) { festring finalWhat; @@ -1324,40 +1347,82 @@ int game::RotateMapNotes() return iMapNotesRotation; } -std::vector afsAutoPickupMatch; -pcre *reAutoPickup=NULL; -void game::UpdateAutoPickUpMatching() //simple matching syntax +void game::SetDropTag(item* it) { - afsAutoPickupMatch.clear(); - - bool bSimple=false; - if(bSimple){ //TODO just drop the simple code? or start the string with something to let it be used instead of regex? tho is cool to let ppl learn regex :) - if(ivanconfig::GetAutoPickUpMatching().GetSize()==0 || ivanconfig::GetAutoPickUpMatching()[0]=='!')return; + if(ivanconfig::IsAutoPickupThrownItems()){ + it->ClearTag('t'); //throw: to avoid auto-pickup + if(game::IsAutoPickupMatch(it->GetName(DEFINITE))){ + it->SetTag('d'); //intentionally dropped: this will let user decide specific items to NOT auto-pickup regex matching + } + } +} - std::stringstream ss(ivanconfig::GetAutoPickUpMatching().CStr()); - std::string match; - while(std::getline(ss,match,'|')) - afsAutoPickupMatch.push_back(festring(match.c_str())); +/** + * + * @param it + * @param bUnarticled if false is GetNameSingular() + * @return + */ +cchar* game::StoreMatchNameKey(item* it,bool bUnarticled) +{ + static festring fsRet,fsSpace=" ",fsEmpty=""; + static cchar* cToken="+"; + + fsRet=""; + fsRet<GetName(UNARTICLED|UNLABELED|STRIPPED|NOPOSTFIX); + fsRet<GetNameToMatch(); }else{ - //TODO test regex about: ignoring broken lanterns and bottles, ignore sticks on fire but pickup scrolls on fire - // static bool bDummyInit = [](){reAutoPickup=NULL;return true;}(); - const char *errMsg; - int iErrOffset; - if(reAutoPickup)pcre_free(reAutoPickup); - reAutoPickup = pcre_compile( - ivanconfig::GetAutoPickUpMatching().CStr(), //pattern - 0, //no options - &errMsg, &iErrOffset, - 0); // default char tables - if (!reAutoPickup){ - std::vector afsFullProblems; - afsFullProblems.push_back(festring(errMsg)); - afsFullProblems.push_back(festring()+"offset:"+iErrOffset); - bool bDummy = iosystem::AlertConfirmMsg("regex validation failed, if ignored will just not work at all",afsFullProblems,false); + fsRet<GetNameSingular()<IsPlayer()) + return; + + static itemvector vit;vit.clear(); + C->GetStack()->FillItemVector(vit); + for(int i=0;i(vit[i]); + if(itc){ + long lRemainingVol = itc->GetStorageVolume() - itc->GetContained()->GetVolume(); + DBG3(lRemainingVol,itToStore->GetVolume(),itc->GetLabel().CStr()); + if(lRemainingVolGetVolume()) + continue; + + if(itc->IsAutoStoreMatch(itToStore->GetName(DEFINITE))){ + itToStore->RemoveFromSlot(); + itc->GetContained()->AddItem(itToStore); + ADD_MESSAGE("%s was safely stored in %s",itToStore->GetName(DEFINITE).CStr(),itc->GetName(DEFINITE).CStr()); + break; + } } } } + +pcre *reAutoPickup=NULL; +void game::UpdateAutoPickUpRegex() +{ + //TODO test regex about: ignoring broken lanterns and bottles, ignore sticks on fire but pickup scrolls on fire + festring fsErr; + reAutoPickup = festring::CompilePCRE(reAutoPickup,ivanconfig::GetAutoPickUpMatching(),&fsErr); + if(!fsErr.IsEmpty()){ + std::vector afsFullProblems; + afsFullProblems.push_back(fsErr); + bool bDummy = iosystem::AlertConfirmMsg("Failed updating auto-pickup regex.",afsFullProblems,false); + } +} bool game::IsAutoPickupMatch(cfestring fsName) { + if(!reAutoPickup) + return false; return pcre_exec(reAutoPickup, 0, fsName.CStr(), fsName.GetSize(), 0, 0, NULL, 0) >= 0; } int game::CheckAutoPickup(square* sqr) @@ -1370,34 +1435,31 @@ int game::CheckAutoPickup(square* sqr) lsquare* lsqr = (lsquare*)sqr; - static bool bDummyInit = [](){UpdateAutoPickUpMatching();return true;}(); + static bool bDummyInit = [](){UpdateAutoPickUpRegex();return true;}(); itemvector iv; lsqr->GetStack()->FillItemVector(iv); int iTot=0; for(int i=0;iGetRoom() && it->GetRoom()->GetMaster())continue; //not from owned rooms - if(it->GetSpoilLevel()>0)continue; + if(it->GetSpoilLevel()>0)continue; //just a guess no one auto-wants it + bool b=false; if(!b && ivanconfig::IsAutoPickupThrownItems() && it->HasTag('t') )b=true; //was thrown - if(!b && !it->HasTag('d')){ - if(reAutoPickup!=NULL){ - if(IsAutoPickupMatch(it->GetName(DEFINITE))){ - b=true; - } - } - } - if(!b){ //TODO use player's perception, in case of a stack of items, to allow random pickup based on item volume (size) where smaller = harder like tiny rings, to compensate for the easiness of not losing a round having to pick up the item interactively - for(int i=0;iGetNameSingular().Find(afsAutoPickupMatch[i].CStr(),0) != festring::NPos){ - b=true; - break; //each simple match loop - } - } + if(!b && !it->HasTag('d')){ //was NOT intentionally dropped (dropping adds such tag) + /** + * TODO ? + * Use player's perception, in case of a stack of items, + * to allow random pickup based on item volume (size) where smaller = harder like tiny rings, + * to compensate for the easiness of not losing a round having to pick up the item interactively. + * But it may just be annnoying... + */ + b = IsAutoPickupMatch(it->GetName(DEFINITE)); } if(b){ it->MoveTo(PLAYER->GetStack()); ADD_MESSAGE("%s picked up.", it->GetName(INDEFINITE).CStr()); + AutoStoreItemInContainer(it,PLAYER); iTot++; } } @@ -1528,7 +1590,7 @@ void game::DrawMapNotesOverlay(bitmap* buffer) //TODO draw to a bitmap in the 1st call and just fast blit it later (with mask), unless it becomes animated in some way. int iLineHeightPixels=15; //line height in pixels - int iFontWidth=8; //font width + int iFontWidth=FONT->GetFontSize().X; int iM=3; //margin const static int iTotCol=5; @@ -1589,6 +1651,15 @@ void game::DrawMapNotesOverlay(bitmap* buffer) // col16 colBkg = iNoteHighlight==i ? colBkg=YELLOW : colMapNoteBkg; if(validateV2(bkgTL,buffer,bkgB)){ col16 colMapNoteBkg2=colMapNoteBkg; + if(festring(vMapNotes[i].note).Find("@")!=festring::NPos) + colMapNoteBkg2=BLACK; + else + if(festring(vMapNotes[i].note).Find("?")!=festring::NPos) + colMapNoteBkg2=GREEN; + else + if(festring(vMapNotes[i].note).Find("!!!")!=festring::NPos) + colMapNoteBkg2=YELLOW; + else if(festring(vMapNotes[i].note).Find("!!")!=festring::NPos) colMapNoteBkg2=RED; else @@ -1668,6 +1739,7 @@ void game::DrawMapOverlay(bitmap* buffer) static v2 v2BmpSize(0,0); static v2 v2TopLeftFinal(0,0); static v2 v2MapScrSizeFinal(0,0); + static v2 v2MinTopLeft(10,10); // this prevents a crash related to drawing lines too near top and left game gfx buffer edges static bitmap* bmpFinal; bool bTransparentMap = bPositionQuestionMode && (CursorPos != PLAYER->GetPos()) && ivanconfig::IsTransparentMapLM(); @@ -1739,8 +1811,8 @@ void game::DrawMapOverlay(bitmap* buffer) // v2 v2VisibleDungeonScrSize=v2CL*TILE_SIZE; v2Center = area::getTopLeftCorner() +v2DungeonScrSize/2; v2TopLeft = v2Center -v2MapScrSize/2; - if(v2TopLeft.X<0)v2TopLeft.X=0; - if(v2TopLeft.Y<0)v2TopLeft.Y=0; + if(v2TopLeft.X RES.X)v2TopLeftFinal.X=RES.X-v2MapScrSizeFinal.X; if((v2TopLeftFinal.Y+v2MapScrSizeFinal.Y) > RES.Y)v2TopLeftFinal.Y=RES.Y-v2MapScrSizeFinal.Y; - if(v2TopLeftFinal.X<0)v2TopLeftFinal.X=0; - if(v2TopLeftFinal.Y<0)v2TopLeftFinal.Y=0; + if(v2TopLeftFinal.X0) - graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, v2StretchedPos, v2StretchedBorder, DARK_GRAY, false); + if(ivanconfig::GetAltSilhouettePreventColorGlitch()>0){ + col16 col = DARK_GRAY; + if(PLAYER->StateIsActivated(PANIC)){ + static float fMaxSec=1.5; //it will flash every half of this time + static clock_t tmMax = (clock_t)CLOCKS_PER_SEC*fMaxSec; + static clock_t tmHalf = (clock_t)tmMax/2.0; + col = ((clock()%tmMax) < tmHalf) ? YELLOW : DARK_GRAY; + } + graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, v2StretchedPos, v2StretchedBorder, col, false); + } iAltSilBlitCount++; } @@ -3101,6 +3181,8 @@ bitmap* PrepareItemsUnder(bool bUseDB, stack* su, int iMax, v2 v2PosIni, int iDi blitdata B = DEFAULT_BLITDATA; B.CustomData = ALLOW_ANIMATE; + if(ivanconfig::IsAllowContrastBackground()) + B.CustomData |= ALLOW_CONTRAST; B.Stretch = 1; //ignored? anyway this will work only from/to 16x16... B.Border = { TILE_SIZE, TILE_SIZE }; B.Luminance = ivanconfig::GetContrastLuminance(); @@ -3583,21 +3665,6 @@ int game::Load(cfestring& saveName) return LOADED; } -/** - * this prevents all possibly troublesome characters in all OSs - */ -void fixChars(festring& fs) -{ - for(festring::sizetype i = 0; i < fs.GetSize(); ++i) - { - if(fs[i]>='A' && fs[i]<='Z')continue; - if(fs[i]>='a' && fs[i]<='z')continue; - if(fs[i]>='0' && fs[i]<='9')continue; - - fs[i] = '_'; - } -} - bool chkAutoSaveSuffix(festring& fs,bool bAlsoFixIt=false){DBG1(fs.CStr()); std::string strChk; strChk = fs.CStr(); @@ -3635,7 +3702,7 @@ festring game::SaveName(cfestring& Base,bool bLoadingFromAnAutosave) else { // this is important in case player name changes like when using the fantasy name generator - festring fsPN; fsPN<Exists() && Player->GetTorso()) + NP->GetTorso()->SetLabel( Player->GetTorso()->GetLabel() ); +#endif + Player = NP; if(Player) Player->AddFlags(C_PLAYER); + +#ifdef CURSEDDEVELOPER + if(Player) + curseddeveloper::UpdateKillCredit(); +#endif } void game::InitDungeons() @@ -4068,6 +4145,38 @@ int game::GetPlayerAlignment() return -4; } +int game::GetGodAlignmentVsPlayer(god* G) +{ + switch(G->GetAlignment()){ + case ALPP: return 4; //L++ + case ALP: return 3; //L+ + case AL: return 2; //L + case ALM: return 1; //L- + + /** + * There are 15 gods, 11 god alignments and 9 player alignments, so lets make it work: + * Neutrality has 1 less god, so 1 less source of favours (and no major/extreme god). + * These 3 neutral gods returning 0 will compensate by using less favour cost, to all of them, + * for a neutrally aligned player at gods.cpp/CalcDebit(). + * (the alternative would be to expand player alignments to 11 to match gods' ones, anyone up to it?) + * + * Btw, these "N" matches the base Neutral alignment for these gods. + * And... it won't match very well the description at GetVerbalPlayerAlignment(), but that shouldn't be a problem. + */ + case ANP: return 0; //N+ + case AN: return 0; //N= + case ANM: return 0; //N- + + case ACP: return -1; //C+ + case AC: return -2; //C + case ACM: return -3; //C- + case ACMM: return -4; //C-- + } + + ABORT("unsupported alignment %d",G->GetAlignment()); + return -1000; //dummy +} + cchar* game::GetVerbalPlayerAlignment() { switch(GetPlayerAlignment()){ @@ -4214,7 +4323,7 @@ int game::AskForKeyPress(cfestring& Topic) int Key = GET_KEY(); #ifdef FELIST_WAITKEYUP //not actually felist here but is the waitkeyup event - if(game::GetAutoPlayMode()==0) + if(wizautoplay::GetAutoPlayMode()==AUTOPLAYMODE_DISABLED) for(;;){if(WAIT_FOR_KEY_UP())break;}; #endif @@ -4512,7 +4621,11 @@ v2 game::LookKeyHandler(v2 CursorPos, int Key) stack* Stack = LSquare->GetStack(); if(LSquare->IsTransparent() && Stack->GetVisibleItems(Player)) - Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO)); + Stack->DrawContents(Player, "Items here", + NO_SELECT| // that square may have an adjacent square with a wall lantern, checking for Stack->GetItem(0) prevents a SEGFAULT + (GetSeeWholeMapCheatMode() || (Stack->GetItem(0) && Stack->GetItem(0)->GetRoom()) || Player->GetLSquareUnder()==LSquare ? + 0 : NO_SPECIAL_INFO) + ); else ADD_MESSAGE("You see no items here."); } @@ -4789,7 +4902,6 @@ truth game::LeaveArea(charactervector& Group, truth AllowHostiles, truth AlliesF } /* Used always when the player enters an area. */ - void game::EnterArea(charactervector& Group, int Area, int EntryIndex) { if(Area != WORLD_MAP) @@ -5532,69 +5644,6 @@ truth game::MassacreListsEmpty() } #ifdef WIZARD - -void game::AutoPlayModeApply(){ - int iTimeout=0; - bool bPlayInBackground=false; - - const char* msg; - switch(game::AutoPlayMode){ - case 0: - // disabled - msg="%s says \"I can rest now.\""; - break; - case 1: - // no timeout, user needs to hit '.' to it autoplay once, the behavior is controled by AutoPlayMode AND the timeout delay that if 0 will have no timeout but will still autoplay. - msg="%s says \"I won't rest!\""; - break; - case 2: // TIMEOUTs key press from here to below - msg="%s says \"I can't wait anymore!\""; - iTimeout=(1000); - bPlayInBackground=true; - break; - case 3: - msg="%s says \"I am in a hurry!\""; - iTimeout=(1000/2); - bPlayInBackground=true; - break; - case 4: - msg="%s says \"I... *frenzy* yeah! Try to follow me now! Hahaha!\""; - iTimeout=10;//min possible to be fastest //(1000/10); // like 10 FPS, so user has 100ms chance to disable it - bPlayInBackground=true; - break; - } - ADD_MESSAGE(msg, game::GetPlayer()->CHAR_NAME(DEFINITE)); - - globalwindowhandler::SetPlayInBackground(bPlayInBackground); - - if(!ivanconfig::IsXBRZScale()){ - /** - * TODO - * This is an horrible gum solution... - * I still have no idea why this happens. - * Autoplay will timeout 2 times slower if xBRZ is disabled! why!??!?!? - * But the debug log shows the correct timeouts :(, clueless for now... - */ - iTimeout/=2; - } - - globalwindowhandler::SetKeyTimeout(iTimeout,'.');//,'~'); -} - -void game::IncAutoPlayMode() { -// if(!globalwindowhandler::IsKeyTimeoutEnabled()){ -// if(AutoPlayMode>=2){ -// AutoPlayMode=0; // TIMEOUT was disabled there at window handler! so reset here. -// AutoPlayModeApply(); -// } -// } - - ++AutoPlayMode; - if(AutoPlayMode>4)AutoPlayMode=0; - - AutoPlayModeApply(); -} - void game::SeeWholeMap() { if(SeeWholeMapCheatMode < 2) @@ -5604,7 +5653,6 @@ void game::SeeWholeMap() GetCurrentArea()->SendNewDrawRequest(); } - #endif void game::CreateBone() @@ -6151,8 +6199,12 @@ void game::ItemEntryDrawer(bitmap* Bitmap, v2 Pos, uint I) if(ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA; + if(ivanconfig::IsAllowContrastBackground()) + B.CustomData |= ALLOW_CONTRAST; + ItemVector[c]->Draw(B); B.CustomData &= ~ALLOW_ALPHA; + B.CustomData &= ~ALLOW_CONTRAST; } if(ItemVector.size() > 3) @@ -6469,7 +6521,7 @@ ulong game::IncreaseSquarePartEmitationTicks() int game::Wish(character* Wisher, cchar* MsgSingle, cchar* MsgPair, truth AllowExit) { - if(Wisher->IsPlayerAutoPlay())return ABORTED; + if(wizautoplay::IsPlayerAutoPlay(Wisher))return ABORTED; for(;;) { @@ -6930,6 +6982,7 @@ double game::GetGameSituationDanger() { double SituationDanger = 0; character* Player = GetPlayer(); + Player->ValidateTrapData(); truth PlayerStuck = Player->IsStuck(); v2 PlayerPos = Player->GetPos(); character* TruePlayer = Player; @@ -6944,6 +6997,7 @@ double game::GetGameSituationDanger() if(Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) { + Enemy->ValidateTrapData(); truth EnemyStuck = Enemy->IsStuck(); v2 EnemyPos = Enemy->GetPos(); truth Sees = TruePlayer->CanBeSeenBy(Enemy); @@ -6964,6 +7018,7 @@ double game::GetGameSituationDanger() v2 FriendPos = Friend->GetPos(); truth Sees = TrueEnemy->CanBeSeenBy(Friend); + Friend->ValidateTrapData(); if(Friend->IsStuck()) { Friend = Friend->Duplicate(IGNORE_PROHIBITIONS); diff --git a/Main/Source/gear.cpp b/Main/Source/gear.cpp index 48642ae6a..680f139b5 100644 --- a/Main/Source/gear.cpp +++ b/Main/Source/gear.cpp @@ -170,6 +170,7 @@ truth pickaxe::Apply(character* User) } int Dir = game::DirectionQuestion(CONST_S("What direction do you want to dig? [press a direction key]"), false); + if(wizautoplay::IsPlayerAutoPlay(User))Dir = clock()%8; if(Dir == DIR_ERROR) return false; diff --git a/Main/Source/god.cpp b/Main/Source/god.cpp index b90a0fae0..88968ca78 100644 --- a/Main/Source/god.cpp +++ b/Main/Source/god.cpp @@ -21,7 +21,7 @@ int god::GetBasicAlignment() const { return NEUTRAL; } void god::Pray() { LastPray = 0; - if(!Timer) + if(!Timer){ if(Relation >= -RAND_N(500)) { ADD_MESSAGE("You feel %s is pleased.", GetName()); @@ -79,7 +79,7 @@ void god::Pray() game::ApplyDivineAlignmentBonuses(this, 10, false); PLAYER->EditExperience(WISDOM, -50, 1 << 10); } - else + }else{ if(Relation > RAND_N(500) && Timer < RAND_N(500000)) { ADD_MESSAGE("You feel %s is displeased, but tries to help you anyway.", GetName()); @@ -109,6 +109,9 @@ void god::Pray() ADD_MESSAGE("%s seems to be hostile.", Angel->CHAR_DESCRIPTION(DEFINITE)); } } + } + + fsLastKnownRelation = PrintRelation(); } festring god::GetCompleteDescription() const @@ -302,7 +305,7 @@ cfestring god::PrintRelation() const VerbalRelation = "you more than any other mortal."; } - ADD_MESSAGE("%s %s %s", GetName(), fsIs, VerbalRelation); + ADD_MESSAGE("%s %s %s", GetPersonalPronoun(), fsIs, VerbalRelation); festring fsLKR; fsLKR<GetAttachedGod() == GetType() ? 50 : 100; if(OfferValue > 0 && Relation > 250 && !(RAND() % RandModifier)) @@ -553,6 +557,7 @@ void god::Save(outputfile& SaveFile) const SaveFile << static_cast(GetType()); SaveFile << Relation << Timer << Known << LastPray; SaveFile << fsLastKnownRelation; + SaveFile << knownSpellsID; } void god::Load(inputfile& SaveFile) @@ -561,6 +566,9 @@ void god::Load(inputfile& SaveFile) if(game::GetCurrentSavefileVersion()>=134){ SaveFile >> fsLastKnownRelation; } + if(game::GetCurrentSavefileVersion()>=135){ + SaveFile >> knownSpellsID; + } } void god::ApplyDivineTick() @@ -570,3 +578,45 @@ void god::ApplyDivineTick() if(LastPray > -1 && LastPray < 336000) ++LastPray; } + +std::vector> god::vFavID; + +bool god::Favour(int iWhat, int iDebit) +{ + if(Relation < 0){ + ADD_MESSAGE("%s ignores your plea and makes sure you understand it...",GetName()); + PrayBadEffect(); + fsLastKnownRelation = PrintRelation(); + return false; + } + + if(Relation < iDebit){ // warns, punishes and provides a last favour before becoming negative relation + ADD_MESSAGE("You hear a booming voice: \"Don't push your luck... puny mortal!\""); + PrayBadEffect(); + return true; + } + + return true; +} + +festring god::GetFavourName(int iID) +{ + if(vFavID.size()==0) FavourInit(); + + for(auto FI = vFavID.begin(); FI != vFavID.end(); ++FI){ + if(FI->first==iID) + return FI->second; + } + + ABORT("invalid favour ID %d",iID); + return ""; //dummy +} + +void god::AddFavourID(int i,festring fs) +{ +// std::pair IDname; +// IDname.first = i; +// IDname.second = fs; +// vFavID.push_back(IDname); + vFavID.push_back(std::make_pair(i,fs)); +} diff --git a/Main/Source/gods.cpp b/Main/Source/gods.cpp index ebe40e47f..ed5c72e72 100644 --- a/Main/Source/gods.cpp +++ b/Main/Source/gods.cpp @@ -10,6 +10,9 @@ * */ +#include + + /* Compiled through godset.cpp */ #define LAWFUL_BASIC_COLOR MakeRGB16(160, 160, 0) @@ -126,17 +129,189 @@ int mortifer::GetBasicAlignment() const { return EVIL; } col16 mortifer::GetColor() const { return CHAOS_BASIC_COLOR; } col16 mortifer::GetEliteColor() const { return CHAOS_ELITE_COLOR; } -void sophos::PrayGoodEffect() +/** + * changing the order of these enums will mess importing old savegames (but wont break them) + * prefer sorting on the initialization of the strings FavourInit() + */ +enum eFavours { + FAVOUR_CALLRAIN = 1, + FAVOUR_CONFUSE, + FAVOUR_CURELEPROSY, + FAVOUR_CURELYCANTHROPY, + FAVOUR_CUREMINDWORM, + FAVOUR_CUREPOISON, + FAVOUR_CURESLOWNESS, + FAVOUR_CURETAPEWORM, + FAVOUR_CUREWOUNDS, + FAVOUR_DISEASEIMMUNITY, + FAVOUR_EARTHQUAKE, + FAVOUR_ENCHANT, + FAVOUR_ETHEREALMOV, + FAVOUR_EXTINGUISHFIRE, + FAVOUR_FEED, + FAVOUR_FIRESTORM, + FAVOUR_FIXEQUIPMENT, + FAVOUR_HEALBURNS, + FAVOUR_HOLYGREN, + FAVOUR_INFRAVISION, + FAVOUR_INVIGORATE, + FAVOUR_INVISIBILITY, + FAVOUR_SHOPPING, + FAVOUR_SPEEDUP, + FAVOUR_STOPFIRE, + FAVOUR_SUMMONWOLF, + FAVOUR_TAME, + FAVOUR_TELEPORT, + FAVOUR_BURNENEMIES, + FAVOUR_FEELENEMIES, + FAVOUR_POLYCONTROL, + FAVOUR_TELEPCONTROL, + FAVOUR_ENRAGE, + FAVOUR_CAUSEFEAR, + FAVOUR_CUREVAMP, +}; + +void god::FavourInit() //this one is better on this file { - truth DidHelp = false; + AddFavourID(FAVOUR_BURNENEMIES,"Immolation"); + AddFavourID(FAVOUR_CALLRAIN,"Call Rain"); + AddFavourID(FAVOUR_CAUSEFEAR,"Inspire Fear"); + AddFavourID(FAVOUR_CONFUSE,"Spread Confusion"); + AddFavourID(FAVOUR_CURELEPROSY,"Cure Leprosy"); + AddFavourID(FAVOUR_CURELYCANTHROPY,"Cure Lycanthropy"); + AddFavourID(FAVOUR_CUREMINDWORM,"Remove Brain Parasite"); + AddFavourID(FAVOUR_CUREPOISON,"Cure Poison"); + AddFavourID(FAVOUR_CURESLOWNESS,"Cure Slowness"); + AddFavourID(FAVOUR_CURETAPEWORM,"Remove Stomach Parasite"); + AddFavourID(FAVOUR_CUREVAMP,"Cure Vampirism"); + AddFavourID(FAVOUR_CUREWOUNDS,"Heal"); + AddFavourID(FAVOUR_DISEASEIMMUNITY,"Ward Off Disease"); + AddFavourID(FAVOUR_EARTHQUAKE,"Quake the Earth"); + AddFavourID(FAVOUR_ENCHANT,"Enchant Equipment"); + AddFavourID(FAVOUR_ENRAGE,"Second Wind"); + AddFavourID(FAVOUR_ETHEREALMOV,"Join the Shadows"); + AddFavourID(FAVOUR_EXTINGUISHFIRE,"Quench Flames"); //TODO: consider price vs FAVOUR_HEALBURNS + AddFavourID(FAVOUR_FEED,"Calm Hunger"); + AddFavourID(FAVOUR_FEELENEMIES,"Sense Thy Foes"); + AddFavourID(FAVOUR_FIRESTORM,"Holy Flames"); + AddFavourID(FAVOUR_FIXEQUIPMENT,"Repair Item"); + AddFavourID(FAVOUR_HEALBURNS,"Remove Burns"); + AddFavourID(FAVOUR_HOLYGREN,"Paladin's Gift"); + AddFavourID(FAVOUR_INFRAVISION,"See Thy Foes"); + AddFavourID(FAVOUR_INVIGORATE,"Invigorate"); + AddFavourID(FAVOUR_INVISIBILITY,"Become Invisible"); + AddFavourID(FAVOUR_POLYCONTROL,"Control Shape"); + AddFavourID(FAVOUR_SHOPPING,"Bounty"); //"Black Friday" + AddFavourID(FAVOUR_SPEEDUP,"Haste"); + AddFavourID(FAVOUR_STOPFIRE,"Repair Burns"); + AddFavourID(FAVOUR_SUMMONWOLF,"Summon Nature's Ally"); + AddFavourID(FAVOUR_TAME,"Song of Taming"); + AddFavourID(FAVOUR_TELEPCONTROL,"Control Warp"); + AddFavourID(FAVOUR_TELEPORT,"Teleport"); +} + +int god::CalcDebit(int iDebit,int iDefault){ + if(iDebit!=0){ + switch(iDebit){ + case FAVOURDEBIT_AUTO: iDebit=iDefault; break; + case FAVOURDEBIT_AUTOHALF: iDebit=iDefault/2; break; + case FAVOURDEBIT_AUTODOUBLE: iDebit=iDefault*2; break; + } + + // can ask more favours if very well aligned + if(game::GetPlayerAlignment() == game::GetGodAlignmentVsPlayer(this)) + iDebit/=2; + + /** + * if enough time has passed, a normal pray could provide the favour freely + * and even with relation benefits, so make it cheaper too, but not costless. + */ + if(Timer==0) + iDebit/=2; // /=3 too cheap? + + // skilled in manipulative praying :) + iDebit -= game::GetPlayer()->GetAttribute(WISDOM); + + /** + * max of 20 vafours (50*20=1000) (too much?) + * in the best case (master prayer) only + */ + if(iDebit<50) + iDebit=50; + } + return iDebit; +} + +void AddKnownSpell(std::vector& ks,int iNew) +{ + for(auto pfsSpell = ks.begin(); pfsSpell != ks.end(); pfsSpell++){ + if(*pfsSpell == iNew)return; + } + ks.push_back(iNew); +} +bool FavourTeleport(god* G) +{ if(!PLAYER->StateIsActivated(TELEPORT_LOCK)) { ADD_MESSAGE("Suddenly, the fabric of space experiences an unnaturally powerful quantum displacement!"); game::AskForKeyPress(CONST_S("You teleport! [press any key to continue]")); PLAYER->Move(game::GetCurrentLevel()->GetRandomSquare(PLAYER), true); - DidHelp = true; + return true; } + return false; +} + +bool god::CallFavour(CallFavourType call, int iCallFavour, int iWhat, int iDebit, int iDbtDefault) +{ + if(iCallFavour!=iWhat) + return false; + + if(iDebit==0) //came thru normal praying + AddKnownSpell(knownSpellsID,iCallFavour); + + iDebit = CalcDebit(iDebit,iDbtDefault); + + if(iDebit>0) + if(!god::Favour(iWhat,iDebit)) + return false; + + bool bWorked = false; + if((*call)(this)){ + if(iDebit>0){ //was a favour + int iTm = 10000 - Relation*10; //by reaching here, Relation is always > 0 + if(iTm<1000)iTm=1000; + AdjustTimer(iTm); // this is a kind of debit too (counts against next safe pray time) + + LastPray=0; // to make it count as a pray too + + Relation-=iDebit; + } + bWorked = true; + } + + fsLastKnownRelation = PrintRelation(); + return bWorked; +} + +/** + * + * @param fsWhat + * @param iDebit if -1 will be automatic + * @return + */ +bool sophos::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourTeleport,FAVOUR_TELEPORT,iWhat,iDebit,100)) + return true; + return false; +} + +void sophos::PrayGoodEffect() +{ + truth DidHelp = false; + + DidHelp = Favour(FAVOUR_TELEPORT); // Give a little attribute experience (Cha already given by Dulcis and not Wis, // as we want to check Wis to give the experience). @@ -168,6 +343,8 @@ void sophos::PrayGoodEffect() DidHelp = true; } + //TODO: If still didn't help, reveal a bit of the level? Or detect material? + if(!DidHelp) ADD_MESSAGE("You hear a booming voice: \"Alas, I cannot help thee, mortal.\""); @@ -181,6 +358,20 @@ void sophos::PrayBadEffect() PLAYER->CheckDeath(CONST_S("shattered to pieces by the wrath of ") + GetName(), 0); } +bool FavourHolyGrenade(god* G) +{ + ADD_MESSAGE("You hear a booming voice: \"I GRANT THEE THIS HOLY HAND GRENADE " + "THAT WITH IT THOU MAYEST BLOW THY ENEMIES TO TINY BITS, MY PALADIN!\""); + PLAYER->GetGiftStack()->AddItem(holyhandgrenade::Spawn()); + return true; +} + +bool valpurus::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourHolyGrenade,FAVOUR_HOLYGREN,iWhat,iDebit,300))return true; + return false; +} + void valpurus::PrayGoodEffect() { if(!game::PlayerIsGodChampion()) @@ -201,9 +392,7 @@ void valpurus::PrayGoodEffect() } else // Player already received championship gift, give holy handgrenade instead. { - ADD_MESSAGE("You hear a booming voice: \"I GRANT THEE THIS HOLY HAND GRENADE " - "THAT WITH IT THOU MAYEST BLOW THY ENEMIES TO TINY BITS, MY PALADIN!\""); - PLAYER->GetGiftStack()->AddItem(holyhandgrenade::Spawn()); + Favour(FAVOUR_HOLYGREN); } } @@ -214,14 +403,26 @@ void valpurus::PrayBadEffect() PLAYER->CheckDeath(CONST_S("faced the hammer of Justice from the hand of ") + GetName(), 0); } -void legifer::PrayGoodEffect() +bool FavourFirestorm(god* G) { // I think this is a remnant of past development that you call upon Inlux rather than Legifer. --red_kangaroo // No, my bad. Inlux is an anagram of Linux, which will hopefully save us from the horrid Bill. ;) ADD_MESSAGE("A booming voice echoes: \"Inlux! Inlux! Save us!\" A huge firestorm engulfs everything around you."); //ADD_MESSAGE("You are surrounded by the righteous flames of %s.", GetName()); - game::GetCurrentLevel()->Explosion(PLAYER, CONST_S("killed by the holy flames of ") + GetName(), PLAYER->GetPos(), - (Max(20 * PLAYER->GetAttribute(WISDOM), 1) + Max(GetRelation(), 0)) >> 3, false); + game::GetCurrentLevel()->Explosion(PLAYER, CONST_S("killed by the holy flames of ") + G->GetName(), PLAYER->GetPos(), + (Max(20 * PLAYER->GetAttribute(WISDOM), 1) + Max(G->GetRelation(), 0)) >> 3, false); + return true; +} + +bool legifer::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourFirestorm,FAVOUR_FIRESTORM,iWhat,iDebit,200))return true; + return false; +} + +void legifer::PrayGoodEffect() +{ + Favour(FAVOUR_FIRESTORM); } void legifer::PrayBadEffect() @@ -231,43 +432,15 @@ void legifer::PrayBadEffect() PLAYER->CheckDeath(CONST_S("burned to death by the holy flames of ") + GetName(), 0); } -void dulcis::PrayGoodEffect() +bool FavourExtinguishFire(god* G) { - truth HasHelped = false; - - for(int d = 0; d < PLAYER->GetNeighbourSquares(); ++d) - { - square* Square = PLAYER->GetNeighbourSquare(d); - - if(Square) - { - character* Char = Square->GetCharacter(); - - if(Char) - if(Char->IsBurning()) - if(Char->GetTeam() == PLAYER->GetTeam()) - { - Char->Extinguish(true); - HasHelped = true; - } - } - } - if(PLAYER->IsBurning()) - { - PLAYER->Extinguish(true); - if(HasHelped) - ADD_MESSAGE("Dulcis helps you and your companions to put out the flames."); - else - ADD_MESSAGE("Dulcis helps you to put out the flames."); + PLAYER->Extinguish(true); + return true; +} - HasHelped = true; - } - else if(HasHelped) - ADD_MESSAGE("Dulcis helps your companions to put out the flames."); - if(HasHelped) - return; - else - ADD_MESSAGE("A beautiful melody echoes around you."); +bool FavourTame(god* G) +{ + bool HasHelped = false; for(int d = 0; d < PLAYER->GetNeighbourSquares(); ++d) { @@ -309,8 +482,59 @@ void dulcis::PrayGoodEffect() } } } + + return HasHelped; +} + +bool dulcis::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourExtinguishFire,FAVOUR_EXTINGUISHFIRE,iWhat,iDebit,50))return true; + if(CallFavour(&FavourTame,FAVOUR_TAME,iWhat,iDebit,250))return true; + return false; +} + +void dulcis::PrayGoodEffect() +{ + truth HasHelped = false; + + for(int d = 0; d < PLAYER->GetNeighbourSquares(); ++d) + { + square* Square = PLAYER->GetNeighbourSquare(d); + + if(Square) + { + character* Char = Square->GetCharacter(); + + if(Char) + if(Char->IsBurning()) + if(Char->GetTeam() == PLAYER->GetTeam()) + { + Char->Extinguish(true); + HasHelped = true; + } + } + } + if(PLAYER->IsBurning()) + { + Favour(FAVOUR_EXTINGUISHFIRE); + if(HasHelped) + ADD_MESSAGE("Dulcis helps you and your companions to put out the flames."); + else + ADD_MESSAGE("Dulcis helps you to put out the flames."); + + HasHelped = true; + } + else if(HasHelped) + ADD_MESSAGE("Dulcis helps your companions to put out the flames."); if(HasHelped) return; + else + ADD_MESSAGE("A beautiful melody echoes around you."); + + HasHelped = Favour(FAVOUR_TAME); + if(HasHelped) + return; + if (GetRelation() >= 50) { ADD_MESSAGE("You feel the music resonate within you.", GetName()); @@ -326,78 +550,155 @@ void dulcis::PrayBadEffect() PLAYER->CheckDeath(CONST_S("became insane by listening ") + GetName() + " too much", 0); } +bool FavourCureWounds(god* G) +{ + ADD_MESSAGE("%s cures your wounds.", G->GetName()); + PLAYER->RestoreLivingHP(); + return true; +} +bool FavourCurePoison(god* G) +{ + ADD_MESSAGE("%s removes the foul liquid in your veins.", G->GetName()); + PLAYER->DeActivateTemporaryState(POISONED); + return true; +} +bool FavourCureLeprosy(god* G) +{ + ADD_MESSAGE("%s cures your leprosy.", G->GetName()); + PLAYER->DeActivateTemporaryState(LEPROSY); + return true; +} +bool FavourCureLycanthropy(god* G) +{ + ADD_MESSAGE("%s cures your animalistic urges.", G->GetName()); + PLAYER->DeActivateTemporaryState(LYCANTHROPY); + return true; +} +bool FavourCureTapeworm(god* G) +{ + ADD_MESSAGE("%s removes the evil hidden in your guts.", G->GetName()); + PLAYER->DeActivateTemporaryState(PARASITE_TAPE_WORM); + return true; +} +bool FavourCureMindworm(god* G) +{ + ADD_MESSAGE("%s removes the evil hidden in your brain.", G->GetName()); + PLAYER->DeActivateTemporaryState(PARASITE_MIND_WORM); + return true; +} +bool FavourHealBurns(god* G) +{ + ADD_MESSAGE("%s heals your burns.", G->GetName()); + //PLAYER->RemoveBurns(); // removes the burns and restores HP + if(!PLAYER->IsBurning()) // the player would do well to put the flames out himself first + PLAYER->ResetThermalEnergies(); + PLAYER->ResetLivingBurning(); // In keeping with Seges' au natural theme. Does roughly the same as RemoveBurns(), + // only without the message(?) and it resets the burn level counter + return true; +} +bool FavourInvigorate(god* G) +{ + ADD_MESSAGE("You don't feel a bit tired anymore."); + PLAYER->RestoreStamina(); + return true; +} +bool FavourFeed(god* G) +{ + if(dynamic_cast(G)){ + ADD_MESSAGE("Your stomach feels full again."); + PLAYER->SetNP(BLOATED_LEVEL); + }else{ + if(dynamic_cast(G)) + ADD_MESSAGE("%s feeds you fruits and wild berries.", G->GetName()); + + if(dynamic_cast(G)) + ADD_MESSAGE("%s breast-feeds you.", G->GetName()); + + PLAYER->SetNP(SATIATED_LEVEL); + } + + return true; +} +bool FavourCureVampirism(god* G) +{ + ADD_MESSAGE("%s cures your bloodlust.", G->GetName()); + PLAYER->DeActivateTemporaryState(VAMPIRISM); + return true; +} + +bool seges::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourCureWounds,FAVOUR_CUREWOUNDS,iWhat,iDebit,150))return true; + if(CallFavour(&FavourCurePoison,FAVOUR_CUREPOISON,iWhat,iDebit,200))return true; + if(CallFavour(&FavourCureLeprosy,FAVOUR_CURELEPROSY,iWhat,iDebit,250))return true; + if(CallFavour(&FavourCureLycanthropy,FAVOUR_CURELYCANTHROPY,iWhat,iDebit,300))return true; + if(CallFavour(&FavourCureVampirism,FAVOUR_CUREVAMP,iWhat,iDebit,100))return true; + if(CallFavour(&FavourCureTapeworm,FAVOUR_CURETAPEWORM,iWhat,iDebit,250))return true; + if(CallFavour(&FavourCureMindworm,FAVOUR_CUREMINDWORM,iWhat,iDebit,500))return true; + if(CallFavour(&FavourFeed,FAVOUR_FEED,iWhat,iDebit,300))return true; //bloats + if(CallFavour(&FavourHealBurns,FAVOUR_HEALBURNS,iWhat,iDebit,50))return true; + if(CallFavour(&FavourInvigorate,FAVOUR_INVIGORATE,iWhat,iDebit,250))return true; + return false; +} + void seges::PrayGoodEffect() { if(PLAYER->IsInBadCondition()) { - ADD_MESSAGE("%s cures your wounds.", GetName()); - PLAYER->RestoreLivingHP(); + Favour(FAVOUR_CUREWOUNDS); return; } if(PLAYER->TemporaryStateIsActivated(POISONED)) { - ADD_MESSAGE("%s removes the foul liquid in your veins.", GetName()); - PLAYER->DeActivateTemporaryState(POISONED); + Favour(FAVOUR_CUREPOISON); return; } if(PLAYER->StateIsActivated(LEPROSY)) { - ADD_MESSAGE("%s cures your leprosy.", GetName()); - PLAYER->DeActivateTemporaryState(LEPROSY); + Favour(FAVOUR_CURELEPROSY); return; } if(PLAYER->TemporaryStateIsActivated(LYCANTHROPY)) { - ADD_MESSAGE("%s cures your animalistic urges.", GetName()); - PLAYER->DeActivateTemporaryState(LYCANTHROPY); + Favour(FAVOUR_CURELYCANTHROPY); return; } if(PLAYER->TemporaryStateIsActivated(VAMPIRISM)) { - ADD_MESSAGE("%s cures your bloodlust.", GetName()); - PLAYER->DeActivateTemporaryState(VAMPIRISM); + Favour(FAVOUR_CUREVAMP); return; } if(PLAYER->TemporaryStateIsActivated(PARASITE_TAPE_WORM)) { - ADD_MESSAGE("%s removes the evil hidden in your guts.", GetName()); - PLAYER->DeActivateTemporaryState(PARASITE_TAPE_WORM); + Favour(FAVOUR_CURETAPEWORM); return; } if(PLAYER->TemporaryStateIsActivated(PARASITE_MIND_WORM)) { - ADD_MESSAGE("%s removes the evil hidden in your brain.", GetName()); - PLAYER->DeActivateTemporaryState(PARASITE_MIND_WORM); + Favour(FAVOUR_CUREMINDWORM); return; } if(PLAYER->GetNP() < SATIATED_LEVEL) { - ADD_MESSAGE("Your stomach feels full again."); - PLAYER->SetNP(BLOATED_LEVEL); + Favour(FAVOUR_FEED); return; } if(PLAYER->IsBurnt()) { - ADD_MESSAGE("%s heals your burns.", GetName()); - //PLAYER->RemoveBurns(); // removes the burns and restores HP - if(!PLAYER->IsBurning()) // the player would do well to put the flames out himself first - PLAYER->ResetThermalEnergies(); - PLAYER->ResetLivingBurning(); // In keeping with Seges' au natural theme. Does roughly the same as RemoveBurns(), - // only without the message(?) and it resets the burn level counter + Favour(FAVOUR_HEALBURNS); return; } // Always return at least some message. - ADD_MESSAGE("You don't feel a bit tired anymore."); - PLAYER->RestoreStamina(); + Favour(FAVOUR_INVIGORATE); return; } @@ -413,7 +714,7 @@ void seges::PrayBadEffect() ADD_MESSAGE("Seges tries to alter the contents of your stomach, but fails."); } -void atavus::PrayGoodEffect() +bool FavourEnchantEquipment(god* G) { item* Enchantable; item* PairEnchantable; @@ -442,7 +743,7 @@ void atavus::PrayGoodEffect() } if(LowEnchant < 99) { - int EnchDiff = ((Enchantable->GetEnchantment()+2)*250 - GetRelation()) / 50; + int EnchDiff = ((Enchantable->GetEnchantment()+2)*250 - G->GetRelation()) / 50; if(EnchDiff <= 1 || !RAND_N(EnchDiff)) { if(Pair) { @@ -455,9 +756,22 @@ void atavus::PrayGoodEffect() ADD_MESSAGE("Your %s glows briefly blue. It feels very warm now.", Enchantable->CHAR_NAME(UNARTICLED)); Enchantable->EditEnchantment(1); } - return; + return true; } } + + return false; +} + +bool atavus::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourEnchantEquipment,FAVOUR_ENCHANT,iWhat,iDebit,250))return true; + return false; +} + +void atavus::PrayGoodEffect() +{ + if(Favour(FAVOUR_ENCHANT))return; ADD_MESSAGE("You feel that %s is watching your actions closely.", GetName()); } @@ -496,20 +810,12 @@ void atavus::PrayBadEffect() PLAYER->CheckDeath(CONST_S("killed by Atavus's humour")); } -void silva::PrayGoodEffect() +bool FavourCallRain(god* G) { - if(PLAYER->GetNP() < HUNGER_LEVEL) - { - ADD_MESSAGE("%s feeds you fruits and wild berries.", GetName()); - PLAYER->SetNP(SATIATED_LEVEL); - } - - if(PLAYER->IsBurning() || PLAYER->PossessesItem(&item::IsOnFire)) - { beamdata Beam ( 0, - CONST_S("drowned by the tears of ") + GetName(), + CONST_S("drowned by the tears of ") + G->GetName(), YOURSELF, 0 ); @@ -518,118 +824,19 @@ void silva::PrayGoodEffect() PLAYER->SpillFluid(0, liquid::Spawn(WATER, 400 + RAND() % 800)); Square->LiquidRain(Beam, WATER); - ADD_MESSAGE("Silva allows a little spell of gentle rain to pour down from above."); - } - else if(!game::GetCurrentLevel()->IsOnGround()) - { - ADD_MESSAGE("Suddenly a horrible earthquake shakes the level."); - int c, Tunnels = 2 + RAND() % 3; - if(!game::GetCurrentLevel()->EarthquakesAffectTunnels()) - Tunnels = 0; - - for(c = 0; c < Tunnels; ++c) - game::GetCurrentLevel()->AttachPos(game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE|ATTACHABLE)); - - int ToEmpty = 10 + RAND() % 11; - - for(c = 0; c < ToEmpty; ++c) - for(int i = 0; i < 50; ++i) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE); - truth Correct = false; - - for(int d = 0; d < 8; ++d) - { - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - - if(Square && Square->IsFlyable()) - { - Correct = true; - break; - } - } - - if(Correct) - { - game::GetCurrentLevel()->GetLSquare(Pos)->ChangeOLTerrainAndUpdateLights(0); - break; - } - } - - int ToGround = 20 + RAND() % 21; - - for(c = 0; c < ToGround; ++c) - for(int i = 0; i < 50; ++i) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, RAND() & 1 ? 0 : HAS_CHARACTER); - - if(Pos == ERROR_V2) - continue; - - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* Char = Square->GetCharacter(); - - if(Square->GetOLTerrain() || (Char && (Char->IsPlayer() || PLAYER->GetRelation(Char) != HOSTILE))) - continue; - - int Walkables = 0; - - for(int d = 0; d < 8; ++d) - { - lsquare* NearSquare = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - - if(NearSquare && NearSquare->IsFlyable()) - ++Walkables; - } - - if(Walkables > 6) - { - Square->ChangeOLTerrainAndUpdateLights(earth::Spawn()); - - if(Char) - { - if(Char->CanBeSeenByPlayer()) - ADD_MESSAGE("%s is hit by a rock falling from the ceiling!", Char->CHAR_NAME(DEFINITE)); - - Char->ReceiveDamage(0, 20 + RAND() % 21, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); - Char->CheckDeath(CONST_S("killed by an earthquake"), 0); - } - - Square->KickAnyoneStandingHereAway(); - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 41, PHYSICAL_DAMAGE); - break; - } - } - - // Generate a few boulders in the level - - int BoulderNumber = 10 + RAND() % 10; - - for(c = 0; c < BoulderNumber; ++c) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(); - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* MonsterHere = Square->GetCharacter(); - - if(!Square->GetOLTerrain() && (!MonsterHere || MonsterHere->GetRelation(PLAYER) == HOSTILE)) - { - Square->ChangeOLTerrainAndUpdateLights(boulder::Spawn(1 + (RAND() & 1))); - - if(MonsterHere) - MonsterHere->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); + ADD_MESSAGE("Silva allows a little spell of gentle rain to pour down from above."); - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE); - } - } + return true; +} - // Damage to items in the level +bool FavourEarthQuake(god* G) +{ + scrollofearthquake::EarthQuakeMagic(); + return true; +} - for(int x = 0; x < game::GetCurrentLevel()->GetXSize(); ++x) - for(int y = 0; y < game::GetCurrentLevel()->GetYSize(); ++y) - game::GetCurrentLevel()->GetLSquare(x, y)->ReceiveEarthQuakeDamage(); - } - else - { +bool FavourSummonWolf(god* G) +{ int TryToCreate = 1 + RAND() % 7; int Created = 0; @@ -656,6 +863,38 @@ void silva::PrayGoodEffect() if(Created > 1) ADD_MESSAGE("Suddenly some tame wolves materialize around you."); + + return true; +} + +bool silva::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourFeed,FAVOUR_FEED,iWhat,iDebit,200))return true; //satiated + if(CallFavour(&FavourCallRain,FAVOUR_CALLRAIN,iWhat,iDebit,75))return true; + if(CallFavour(&FavourEarthQuake,FAVOUR_EARTHQUAKE,iWhat,iDebit,500))return true; + if(CallFavour(&FavourSummonWolf,FAVOUR_SUMMONWOLF,iWhat,iDebit,250))return true; + + return false; +} + +void silva::PrayGoodEffect() +{ + if(PLAYER->GetNP() < HUNGER_LEVEL) + { + Favour(FAVOUR_FEED); + } + + if(PLAYER->IsBurning() || PLAYER->PossessesItem(&item::IsOnFire)) + { + Favour(FAVOUR_CALLRAIN); + } + else if(!game::GetCurrentLevel()->IsOnGround()) + { + Favour(FAVOUR_EARTHQUAKE); + } + else + { + Favour(FAVOUR_SUMMONWOLF); } } @@ -675,6 +914,51 @@ void silva::PrayBadEffect() } } +bool FavourFixEquipment(god* G) +{ + for(int c = 0; c < PLAYER->GetEquipments(); ++c) + { + item* Equipment = PLAYER->GetEquipment(c); + + if(Equipment && Equipment->IsBroken()) + { + ADD_MESSAGE("%s fixes your %s.", G->GetName(), Equipment->CHAR_NAME(UNARTICLED)); + Equipment->Fix(); + break; + } + } + + return true; +} + +bool FavourStopFire(god* G) +{ + for(int c = 0; c < PLAYER->GetEquipments(); ++c) + { + item* Equipment = PLAYER->GetEquipment(c); + + if(Equipment && Equipment->IsBurnt()) + { + ADD_MESSAGE("%s repairs the burns on your %s.", G->GetName(), Equipment->CHAR_NAME(UNARTICLED)); + Equipment->RemoveBurns(); + if(!Equipment->IsBurning()) + Equipment->ResetThermalEnergies(); + Equipment->ResetBurning(); + break; + } + } + + return true; +} + +bool loricatus::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourFixEquipment,FAVOUR_FIXEQUIPMENT,iWhat,iDebit,250))return true; + if(CallFavour(&FavourStopFire,FAVOUR_STOPFIRE,iWhat,iDebit,50))return true; + + return false; +} + void loricatus::PrayGoodEffect() { item* MainWielded = PLAYER->GetMainWielded(); @@ -745,32 +1029,11 @@ void loricatus::PrayGoodEffect() } } - for(int c = 0; c < PLAYER->GetEquipments(); ++c) - { - item* Equipment = PLAYER->GetEquipment(c); - - if(Equipment && Equipment->IsBroken()) - { - ADD_MESSAGE("%s fixes your %s.", GetName(), Equipment->CHAR_NAME(UNARTICLED)); - Equipment->Fix(); - return; - } - } - - for(int c = 0; c < PLAYER->GetEquipments(); ++c) - { - item* Equipment = PLAYER->GetEquipment(c); + if(Favour(FAVOUR_FIXEQUIPMENT)) + return; - if(Equipment && Equipment->IsBurnt()) - { - ADD_MESSAGE("%s repairs the burns on your %s.", GetName(), Equipment->CHAR_NAME(UNARTICLED)); - Equipment->RemoveBurns(); - if(!Equipment->IsBurning()) - Equipment->ResetThermalEnergies(); - Equipment->ResetBurning(); - return; - } - } + if(Favour(FAVOUR_STOPFIRE)) + return; if(PLAYER->GetUsableArms()) ADD_MESSAGE("You feel a slight tingling in your hands."); @@ -816,36 +1079,78 @@ void loricatus::PrayBadEffect() } } +int CalcDuration(god* G) +{ + if(dynamic_cast(G)) + return 200 * PLAYER->GetAttribute(WISDOM) + Max(G->GetRelation(), 0); + + if(dynamic_cast(G) || dynamic_cast(G)) + return 300 * PLAYER->GetAttribute(WISDOM) + G->GetRelation() * 5; + + ABORT("duration calc for god %s is not available here!",G->GetName()); +} +bool FavourCureSlowness(god* G) +{ + ADD_MESSAGE("%s restores the swiftness of your movement.", G->GetName()); + PLAYER->DeActivateTemporaryState(SLOW); + return true; +} +bool FavourSpeedUp(god* G) +{ + int Duration = CalcDuration(G); + ADD_MESSAGE("%s gives you the talent for speed.", G->GetName()); + PLAYER->BeginTemporaryState(HASTE, Duration); + return true; +} +bool FavourInvisible(god* G) +{ + int Duration = CalcDuration(G); + ADD_MESSAGE("%s hides you from your enemies.", G->GetName()); + PLAYER->BeginTemporaryState(INVISIBLE, Duration); + return true; +} +bool FavourInfravision(god* G) +{ + int Duration = CalcDuration(G); + ADD_MESSAGE("%s orders darkness to hinder you no more.", G->GetName()); + PLAYER->BeginTemporaryState(INFRA_VISION, Duration); + return true; +} + +bool cleptia::Favour(int iWhat, int iDebit) +{ + if(CallFavour(FavourCureSlowness,FAVOUR_CURESLOWNESS,iWhat,iDebit,100))return true; + if(CallFavour(FavourSpeedUp,FAVOUR_SPEEDUP,iWhat,iDebit,150))return true; + if(CallFavour(FavourInvisible,FAVOUR_INVISIBILITY,iWhat,iDebit,250))return true; + if(CallFavour(FavourInfravision,FAVOUR_INFRAVISION,iWhat,iDebit,150))return true; + return false; +} + void cleptia::PrayGoodEffect() { - int Duration = 200 * PLAYER->GetAttribute(WISDOM) + Max(Relation, 0); PLAYER->RestoreStamina(); if(PLAYER->StateIsActivated(SLOW)) { - ADD_MESSAGE("%s restores the swiftness of your movement.", GetName()); - PLAYER->DeActivateTemporaryState(SLOW); + Favour(FAVOUR_CURESLOWNESS); return; } if(!PLAYER->StateIsActivated(HASTE)) { - ADD_MESSAGE("%s gives you the talent for speed.", GetName()); - PLAYER->BeginTemporaryState(HASTE, Duration); + Favour(FAVOUR_SPEEDUP); return; } if(!PLAYER->StateIsActivated(INVISIBLE)) { - ADD_MESSAGE("%s hides you from your enemies.", GetName()); - PLAYER->BeginTemporaryState(INVISIBLE, Duration); + Favour(FAVOUR_INVISIBILITY); return; } if(!PLAYER->StateIsActivated(INFRA_VISION)) { - ADD_MESSAGE("%s orders darkness to hinder you no more.", GetName()); - PLAYER->BeginTemporaryState(INFRA_VISION, Duration); + Favour(FAVOUR_INFRAVISION); return; } @@ -863,6 +1168,27 @@ void cleptia::PrayBadEffect() PLAYER->BeginTemporaryState(SLOW, 250); } +bool FavourEtherealMov(god* G) +{ + ADD_MESSAGE("The air suddenly feels much colder. A terrible undead voice shreds " + "the silence: \"I aM PlEaSeD By tHy sQuIrMiNg, WoRm! WaLkEtH WiTh mE " + "ThRoUgH ThE ShAdOwS As oNe oF ThE DeAd!\""); + + if(!PLAYER->StateIsActivated(ETHEREAL_MOVING)) + PLAYER->BeginTemporaryState(ETHEREAL_MOVING, PLAYER->GetAttribute(WISDOM) * 300); + else + PLAYER->EditTemporaryStateCounter(ETHEREAL_MOVING, + PLAYER->GetTemporaryStateCounter(ETHEREAL_MOVING) + (PLAYER->GetAttribute(WISDOM) * 100)); + + return true; +} + +bool mortifer::Favour(int iWhat, int iDebit) +{ + if(CallFavour(FavourEtherealMov,FAVOUR_ETHEREALMOV,iWhat,iDebit,350))return true; + return false; +} + void mortifer::PrayGoodEffect() { if(!game::PlayerIsGodChampion()) @@ -876,15 +1202,7 @@ void mortifer::PrayGoodEffect() } else { - ADD_MESSAGE("The air suddenly feels much colder. A terrible undead voice shreds " - "the silence: \"I aM PlEaSeD By tHy sQuIrMiNg, WoRm! WaLkEtH WiTh mE " - "ThRoUgH ThE ShAdOwS As oNe oF ThE DeAd!\""); - - if(!PLAYER->StateIsActivated(ETHEREAL_MOVING)) - PLAYER->BeginTemporaryState(ETHEREAL_MOVING, PLAYER->GetAttribute(WISDOM) * 300); - else - PLAYER->EditTemporaryStateCounter(ETHEREAL_MOVING, - PLAYER->GetTemporaryStateCounter(ETHEREAL_MOVING) + (PLAYER->GetAttribute(WISDOM) * 100)); + Favour(FAVOUR_ETHEREALMOV); } } @@ -900,36 +1218,49 @@ void mortifer::PrayBadEffect() PLAYER->CheckDeath(CONST_S("obliterated by the unholy power of ") + GetName(), 0); } -void mellis::PrayGoodEffect() +bool FavourShopping(god* G) { truth Success = false; + itemvector OKItems; - if(!RAND_2) + for(stackiterator i = PLAYER->GetStack()->GetBottom(); i.HasItem(); ++i) { - itemvector OKItems; + if(!i->HasBetterVersion()) + continue; - for(stackiterator i = PLAYER->GetStack()->GetBottom(); i.HasItem(); ++i) - { - if(!i->HasBetterVersion()) - continue; + OKItems.push_back(*i); + Success = true; + } - OKItems.push_back(*i); - Success = true; - } + item* NewVersion; - item* NewVersion; + for(int c = 0; !OKItems.empty() && c < 4; ++c) + { + item* ToBeDeleted = OKItems[RAND() % OKItems.size()]; + NewVersion = ToBeDeleted->BetterVersion(); + ADD_MESSAGE("%s manages to trade %s into %s.", G->GetName(), + ToBeDeleted->CHAR_NAME(DEFINITE), NewVersion->CHAR_NAME(INDEFINITE)); + PLAYER->GetStack()->AddItem(NewVersion); + ToBeDeleted->RemoveFromSlot(); + ToBeDeleted->SendToHell(); + OKItems.erase(std::find(OKItems.begin(), OKItems.end(), ToBeDeleted)); + } - for(int c = 0; !OKItems.empty() && c < 4; ++c) - { - item* ToBeDeleted = OKItems[RAND() % OKItems.size()]; - NewVersion = ToBeDeleted->BetterVersion(); - ADD_MESSAGE("%s manages to trade %s into %s.", GetName(), - ToBeDeleted->CHAR_NAME(DEFINITE), NewVersion->CHAR_NAME(INDEFINITE)); - PLAYER->GetStack()->AddItem(NewVersion); - ToBeDeleted->RemoveFromSlot(); - ToBeDeleted->SendToHell(); - OKItems.erase(std::find(OKItems.begin(), OKItems.end(), ToBeDeleted)); - } + return Success; +} +bool mellis::Favour(int iWhat, int iDebit) +{ + if(CallFavour(FavourShopping,FAVOUR_SHOPPING,iWhat,iDebit,250))return true; + return false; +} + +void mellis::PrayGoodEffect() +{ + truth Success = false; + + if(!RAND_2) + { + Success = Favour(FAVOUR_SHOPPING); } if((Success && !(RAND() % 5)) || (!Success && !(RAND() % 3))) @@ -1077,15 +1408,8 @@ void infuscor::PrayBadEffect() PLAYER->LoseConsciousness(1000 + RAND_N(1000)); } -void nefas::PrayGoodEffect() +bool FavourConfusion(god* G) { - if(PLAYER->GetNP() < HUNGER_LEVEL) - { - ADD_MESSAGE("%s breast-feeds you.", GetName()); - PLAYER->SetNP(SATIATED_LEVEL); - return; - } - rect Rect; femath::CalculateEnvironmentRectangle(Rect, game::GetCurrentLevel()->GetBorder(), PLAYER->GetPos(), 10); truth AudiencePresent = false; @@ -1119,13 +1443,32 @@ void nefas::PrayGoodEffect() && Audience->CanBeConfused() && PLAYER->GetRelation(Audience) == HOSTILE) { if(Audience->CanBeSeenByPlayer()) - ADD_MESSAGE("%s confuses %s with her sweet lies.", GetName(), Audience->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s confuses %s with her sweet lies.", G->GetName(), Audience->CHAR_NAME(DEFINITE)); Audience->BeginTemporaryState(CONFUSED, 30 * PLAYER->GetAttribute(WISDOM) + RAND() % 500); } } } + return true; +} +bool nefas::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourFeed,FAVOUR_FEED,iWhat,iDebit,200))return true; //satiated + if(CallFavour(&FavourConfusion,FAVOUR_CONFUSE,iWhat,iDebit,200))return true; //satiated + return false; +} + +void nefas::PrayGoodEffect() +{ + if(PLAYER->GetNP() < HUNGER_LEVEL) + { + Favour(FAVOUR_FEED); + return; + } + + Favour(FAVOUR_CONFUSE); + if((GetRelation() > 200) && RAND_N(5)) { int Chief = 3000/GetRelation(); @@ -1165,6 +1508,19 @@ void nefas::PrayBadEffect() PLAYER->CheckDeath(CONST_S("killed while enjoying the company of ") + GetName(), 0); } +bool FavourDiseaseImmunity(god* G) +{ + int Duration = CalcDuration(G); + PLAYER->BeginTemporaryState(DISEASE_IMMUNITY, Duration); + ADD_MESSAGE("%s chuckles in your mind: \"No need to fall apart, my dear.\"", G->GetName()); + return true; +} +bool scabies::Favour(int iWhat, int iDebit) +{ + if(CallFavour(&FavourDiseaseImmunity,FAVOUR_DISEASEIMMUNITY,iWhat,iDebit,350))return true; //satiated + return false; +} + void scabies::PrayGoodEffect() { if(PLAYER->IsImmuneToLeprosy()) // Spread leprosy whenever you won't harm your followers. @@ -1180,7 +1536,7 @@ void scabies::PrayGoodEffect() ADD_MESSAGE("You feel a horrible disease spreading."); } - int Duration = 300 * PLAYER->GetAttribute(WISDOM) + Relation * 5; + int Duration = CalcDuration(this); if((PLAYER->GetNP() < HUNGER_LEVEL) && (!PLAYER->StateIsActivated(FASTING) || PLAYER->GetTemporaryStateCounter(FASTING) < Duration)) @@ -1196,8 +1552,7 @@ void scabies::PrayGoodEffect() // Scabies wants followers who can spread her word, not those who just lie on thr ground, missing limbs. if(PLAYER->StateIsActivated(LEPROSY) && !PLAYER->IsImmuneToLeprosy()) { - PLAYER->BeginTemporaryState(DISEASE_IMMUNITY, Duration); - ADD_MESSAGE("%s chuckles in your mind: \"No need to fall apart, my dear.\"", GetName()); + Favour(FAVOUR_DISEASEIMMUNITY); return; } @@ -1268,7 +1623,7 @@ void scabies::PrayBadEffect() } } -void infuscor::PrayGoodEffect() +bool FavourBurnYourEnemies(god* G) { truth Success = false; @@ -1296,7 +1651,7 @@ void infuscor::PrayGoodEffect() && (BodyPart->GetMainMaterial()->GetInteractionFlags() & CAN_BURN) && !BodyPart->IsBurning()) { - if(BodyPart->TestActivationEnergy(20 + GetRelation() / 10)) + if(BodyPart->TestActivationEnergy(20 + G->GetRelation() / 10)) { Success = true; Burned = true; @@ -1304,45 +1659,77 @@ void infuscor::PrayGoodEffect() } } if(Burned) - ADD_MESSAGE("%s savagely sets fire to %s!", GetName(), Victim->CHAR_DESCRIPTION(DEFINITE)); + ADD_MESSAGE("%s savagely sets fire to %s!", G->GetName(), Victim->CHAR_DESCRIPTION(DEFINITE)); } } } + return Success; +} + +int InfuscorFavourDuration = 0; +bool FavourFeelYourEnemies(god* G) +{ + if(!PLAYER->StateIsActivated(ESP)) + PLAYER->BeginTemporaryState(ESP, InfuscorFavourDuration); + else + PLAYER->EditTemporaryStateCounter(ESP, PLAYER->GetTemporaryStateCounter(ESP)+InfuscorFavourDuration); + ADD_MESSAGE("You feel %s whisper in your mind.", G->GetName()); + return true; +} +bool FavourControlWhatYouAre(god* G) +{ + if(!PLAYER->StateIsActivated(POLYMORPH_CONTROL)) + PLAYER->BeginTemporaryState(POLYMORPH_CONTROL, InfuscorFavourDuration); + else + PLAYER->EditTemporaryStateCounter(POLYMORPH_CONTROL, PLAYER->GetTemporaryStateCounter(POLYMORPH_CONTROL)+InfuscorFavourDuration); + ADD_MESSAGE("You feel %s gently touch your body.", G->GetName()); + return true; +} +bool FavourTeleportControl(god* G) +{ + if(!PLAYER->StateIsActivated(TELEPORT_CONTROL)) + PLAYER->BeginTemporaryState(TELEPORT_CONTROL, InfuscorFavourDuration); + else + PLAYER->EditTemporaryStateCounter(TELEPORT_CONTROL, PLAYER->GetTemporaryStateCounter(TELEPORT_CONTROL)+InfuscorFavourDuration); + ADD_MESSAGE("You feel %s gently touch your soul.", G->GetName()); + return true; +} +bool infuscor::Favour(int iWhat, int iDebit) +{ + if(CallFavour(FavourBurnYourEnemies,FAVOUR_BURNENEMIES,iWhat,iDebit,200))return true; + if(CallFavour(FavourFeelYourEnemies,FAVOUR_FEELENEMIES,iWhat,iDebit,250))return true; + if(CallFavour(FavourControlWhatYouAre,FAVOUR_POLYCONTROL,iWhat,iDebit,300))return true; + if(CallFavour(FavourTeleportControl,FAVOUR_TELEPCONTROL,iWhat,iDebit,300))return true; + return false; +} + +void infuscor::PrayGoodEffect() +{ + truth Success = Favour(FAVOUR_BURNENEMIES); + if(!Success) { - int Duration = 300 * PLAYER->GetAttribute(WISDOM) + Relation * 5; + InfuscorFavourDuration = CalcDuration(this); if(!PLAYER->StateIsActivated(ESP) || - PLAYER->GetTemporaryStateCounter(ESP) < Duration) + PLAYER->GetTemporaryStateCounter(ESP) < InfuscorFavourDuration) { - if(!PLAYER->StateIsActivated(ESP)) - PLAYER->BeginTemporaryState(ESP, Duration); - else - PLAYER->EditTemporaryStateCounter(ESP, PLAYER->GetTemporaryStateCounter(ESP)+Duration); - ADD_MESSAGE("You feel %s whisper in your mind.", GetName()); + Favour(FAVOUR_FEELENEMIES); return; } if(!PLAYER->StateIsActivated(POLYMORPH_CONTROL) || - PLAYER->GetTemporaryStateCounter(POLYMORPH_CONTROL) < Duration) + PLAYER->GetTemporaryStateCounter(POLYMORPH_CONTROL) < InfuscorFavourDuration) { - if(!PLAYER->StateIsActivated(POLYMORPH_CONTROL)) - PLAYER->BeginTemporaryState(POLYMORPH_CONTROL, Duration); - else - PLAYER->EditTemporaryStateCounter(POLYMORPH_CONTROL, PLAYER->GetTemporaryStateCounter(POLYMORPH_CONTROL)+Duration); - ADD_MESSAGE("You feel %s gently touch your body.", GetName()); + Favour(FAVOUR_POLYCONTROL); return; } if(!PLAYER->StateIsActivated(TELEPORT_CONTROL) || - PLAYER->GetTemporaryStateCounter(TELEPORT_CONTROL) < Duration) + PLAYER->GetTemporaryStateCounter(TELEPORT_CONTROL) < InfuscorFavourDuration) { - if(!PLAYER->StateIsActivated(TELEPORT_CONTROL)) - PLAYER->BeginTemporaryState(TELEPORT_CONTROL, Duration); - else - PLAYER->EditTemporaryStateCounter(TELEPORT_CONTROL, PLAYER->GetTemporaryStateCounter(TELEPORT_CONTROL)+Duration); - ADD_MESSAGE("You feel %s gently touch your soul.", GetName()); + Favour(FAVOUR_TELEPCONTROL); return; } @@ -1352,37 +1739,16 @@ void infuscor::PrayGoodEffect() return; } -void cruentus::PrayGoodEffect() +bool FavourEnrage(god* G) +{ + ADD_MESSAGE("%s snarls: \"Fight, you lousy coward!\"", G->GetName()); + PLAYER->DeActivateTemporaryState(PANIC); + PLAYER->BeginTemporaryState(REGENERATION, 200 * PLAYER->GetAttribute(WISDOM) + G->GetRelation() * 3); + PLAYER->BeginTemporaryState(FEARLESS, 200 * PLAYER->GetAttribute(WISDOM) + G->GetRelation() * 3); + return true; +} +bool FavourCauseFear(god* G) { - // Blood for the god of blood! - if(!RAND_4 || Relation == 1000) - { - beamdata Beam - ( - 0, - CONST_S("drowned by the blood of ") + GetName(), - YOURSELF, - 0 - ); - lsquare* Square = PLAYER->GetLSquareUnder(); - Square->LiquidRain(Beam, BLOOD); - - if(PLAYER->HasHead()) - ADD_MESSAGE("A torrential rain of blood descends on your head."); - else - ADD_MESSAGE("A rain of blood drizzles all around you."); - } - - // A little bit of healing, but only usable when panicked. - if(PLAYER->StateIsActivated(PANIC)) - { - ADD_MESSAGE("%s snarls: \"Fight, you lousy coward!\"", GetName()); - PLAYER->DeActivateTemporaryState(PANIC); - PLAYER->BeginTemporaryState(REGENERATION, 200 * PLAYER->GetAttribute(WISDOM) + Relation * 3); - PLAYER->BeginTemporaryState(FEARLESS, 200 * PLAYER->GetAttribute(WISDOM) + Relation * 3); - return; - } - rect Rect; femath::CalculateEnvironmentRectangle(Rect, game::GetCurrentLevel()->GetBorder(), PLAYER->GetPos(), 10); truth AudiencePresent = false; @@ -1421,9 +1787,49 @@ void cruentus::PrayGoodEffect() Audience->BeginTemporaryState(PANIC, 30 * PLAYER->GetAttribute(WISDOM) + RAND() % 500); } + return true; + } + + return false; +} +bool cruentus::Favour(int iWhat, int iDebit) +{ + if(CallFavour(FavourEnrage,FAVOUR_ENRAGE,iWhat,iDebit,200))return true; + if(CallFavour(FavourCauseFear,FAVOUR_CAUSEFEAR,iWhat,iDebit,500))return true; + return false; +} + +void cruentus::PrayGoodEffect() +{ + // Blood for the god of blood! + if(!RAND_4 || Relation == 1000) + { + beamdata Beam + ( + 0, + CONST_S("drowned by the blood of ") + GetName(), + YOURSELF, + 0 + ); + lsquare* Square = PLAYER->GetLSquareUnder(); + Square->LiquidRain(Beam, BLOOD); + + if(PLAYER->HasHead()) + ADD_MESSAGE("A torrential rain of blood descends on your head."); + else + ADD_MESSAGE("A rain of blood drizzles all around you."); + } + + // A little bit of healing, but only usable when panicked. + if(PLAYER->StateIsActivated(PANIC)) + { + Favour(FAVOUR_ENRAGE); return; } + if(Favour(FAVOUR_CAUSEFEAR)) + return; + if(!RAND_2) { item* Weapon = PLAYER->GetMainWielded(); diff --git a/Main/Source/human.cpp b/Main/Source/human.cpp index de8f4088a..151bd9389 100644 --- a/Main/Source/human.cpp +++ b/Main/Source/human.cpp @@ -464,8 +464,7 @@ truth humanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit()) return false; - if(GetBurdenState() == OVER_LOADED) - { + if(!ivanconfig::IsOverloadedFight() && GetBurdenState() == OVER_LOADED){ if(IsPlayer()) ADD_MESSAGE("You cannot fight while carrying so much."); @@ -496,6 +495,11 @@ truth humanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) if(Chosen == USE_LEGS && HasTwoUsableLegs()) Chosen = USE_HEAD; } + + if(ivanconfig::IsOverloadedFight() && GetBurdenState() == OVER_LOADED && GetHead()){ + if(Chosen == USE_LEGS) + Chosen = USE_HEAD; //TODO why this is not working for player!?!?!?!? + } switch(Chosen) { @@ -548,6 +552,9 @@ truth humanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) case USE_LEGS: if(HasTwoUsableLegs()) { + if(OverloadedKickFailCheck()) + return false; + msgsystem::EnterBigMessageMode(); Hostility(Enemy); Kick(GetNearLSquare(HitPos), Direction, Flags & SADIST_HIT); @@ -2136,9 +2143,12 @@ void humanoid::DrawSilhouette(truth AnimationDraw) const if(Equipment->AllowAlphaEverywhere()) B1.CustomData |= ALLOW_ALPHA; - + + if(ivanconfig::IsAllowContrastBackground()) + B1.CustomData |= ALLOW_CONTRAST; Equipment->Draw(B1); B1.CustomData &= ~ALLOW_ALPHA; + B1.CustomData &= ~ALLOW_CONTRAST; // if(eqMHover==Equipment){ // v2 v2M = globalwindowhandler::GetMouseLocation(); @@ -2671,6 +2681,7 @@ truth humanoid::CanWield() const truth humanoid::CheckBalance(double KickDamage) { + ValidateTrapData(); return !CanMove() || IsStuck() || !KickDamage @@ -3670,166 +3681,6 @@ long skeleton::GetBodyPartVolume(int I) const return 0; } -truth humanoid::AutoPlayAIequip() -{ - item* iL = GetEquipment(LEFT_WIELDED_INDEX); - item* iR = GetEquipment(RIGHT_WIELDED_INDEX); - - //every X turns remove all equipments - bool bTryWieldNow=false; - static int iLastReEquipAllTurn=-1; - if(game::GetTurn()>(iLastReEquipAllTurn+150)){ DBG2(game::GetTurn(),iLastReEquipAllTurn); - iLastReEquipAllTurn=game::GetTurn(); - for(int i=0;iMoveTo(GetStack());SetEquipment(i,NULL);} //eq is moved to end of stack! - if(iL==eq)iL=NULL; - if(iR==eq)iR=NULL; - } -// if(iL!=NULL){iL->MoveTo(GetStack());iL=NULL;SetEquipment(LEFT_WIELDED_INDEX ,NULL);DBGLN;} -// if(iR!=NULL){iR->MoveTo(GetStack());iR=NULL;SetEquipment(RIGHT_WIELDED_INDEX,NULL);DBGLN;} - bTryWieldNow=true; - } - - //wield some weapon from the inventory as the NPC AI is not working for the player TODO why? - //every X turns try to wield - static int iLastTryToWieldTurn=-1; - if(bTryWieldNow || game::GetTurn()>(iLastTryToWieldTurn+10)){ DBG2(game::GetTurn(),iLastTryToWieldTurn); - iLastTryToWieldTurn=game::GetTurn(); - bool bDoneLR=false; - bool bL2H = iL && iL->IsTwoHanded(); - bool bR2H = iR && iR->IsTwoHanded(); - - //2handed - static int iTryWieldWhat=0; iTryWieldWhat++; DBG1(iTryWieldWhat); - if(iTryWieldWhat%2==0){ //will try 2handed first, alternating. If player has only 2handeds, the 1handeds will not be wielded and it will use punches, what is good too for tests. - if( !bDoneLR && - iL==NULL && GetBodyPartOfEquipment(LEFT_WIELDED_INDEX )!=NULL && - iR==NULL && GetBodyPartOfEquipment(RIGHT_WIELDED_INDEX)!=NULL - ){ - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW); - for(uint c = 0; c < vitEqW.size(); ++c){ - if(vitEqW[c]->IsWeapon(this) && vitEqW[c]->IsTwoHanded()){ DBG1(vitEqW[c]->GetNameSingular().CStr()); - vitEqW[c]->RemoveFromSlot(); - SetEquipment(clock()%2==0 ? LEFT_WIELDED_INDEX : RIGHT_WIELDED_INDEX, vitEqW[c]); //DBG3("Wield",iEqIndex,vitEqW[c]->GetName(DEFINITE).CStr()); - bDoneLR=true; - break; - } - } - } - } - - //dual 1handed (if not 2hd already) - if(!bDoneLR){ - for(int i=0;i<2;i++){ - int iChk=-1; - if(i==0)iChk=LEFT_WIELDED_INDEX; - if(i==1)iChk=RIGHT_WIELDED_INDEX; - - if( - !bDoneLR && - ( - (iChk==LEFT_WIELDED_INDEX && iL==NULL && GetBodyPartOfEquipment(LEFT_WIELDED_INDEX ) && !bR2H) - || - (iChk==RIGHT_WIELDED_INDEX && iR==NULL && GetBodyPartOfEquipment(RIGHT_WIELDED_INDEX) && !bL2H) - ) - ){ - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW); - for(uint c = 0; c < vitEqW.size(); ++c){ - if( - (vitEqW[c]->IsWeapon(this) && !vitEqW[c]->IsTwoHanded()) - || - vitEqW[c]->IsShield(this) - ){ DBG1(vitEqW[c]->GetNameSingular().CStr()); - vitEqW[c]->RemoveFromSlot(); - SetEquipment(iChk, vitEqW[c]); - bDoneLR=true; - break; - } - } - } - } - } - - } - - //every X turns try to use stuff from inv - static int iLastTryToUseInvTurn=-1; - if(game::GetTurn()>(iLastTryToUseInvTurn+5)){ DBG2(game::GetTurn(),iLastTryToUseInvTurn); - iLastTryToUseInvTurn=game::GetTurn(); - - //////////////////////////////// consume food/drink - { //TODO let this happen for non-human too? - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW);DBGLN; - for(uint c = 0; c < vitEqW.size(); ++c){DBGLN; - if(clock()%3!=0 && GetHungerState() >= BLOATED)break;DBGLN; //randomly let it vomit and activate all related flows *eew* xD - - //if(TryToConsume(vitEqW[c])) - material* ConsumeMaterial = vitEqW[c]->GetConsumeMaterial(this); - if( - ConsumeMaterial!=NULL && - vitEqW[c]->IsConsumable() && - ConsumeItem(vitEqW[c], vitEqW[c]->GetConsumeMaterial(this)->GetConsumeVerb()) - ){ - DBG2("AutoPlayConsumed",vitEqW[c]->GetNameSingular().CStr()); - return true; - }DBGLN; - } - } - - //////////////////////////////// equip - {DBGLN; - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW);DBGLN; - for(uint c = 0; c < vitEqW.size(); ++c){DBGLN; - if(TryToEquip(vitEqW[c],true)){ DBG1(vitEqW[c]->GetNameSingular().CStr()); - return true; - }else{DBGLN; - vitEqW[c]->MoveTo(GetStack()); //was dropped, get back, will be in the end of the stack! :) - } - } - } - - //////////////////////////////// zap - static int iLastZapTurn=-1; - if(game::GetTurn()>(iLastZapTurn+100)){ DBG2(game::GetTurn(),iLastZapTurn); //every X turns try to use stuff from inv - iLastZapTurn=game::GetTurn(); - - int iDir=clock()%(8+1); // index 8 is the macro YOURSELF already... if(iDir==8)iDir=YOURSELF; - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW); - for(uint c = 0; c < vitEqW.size(); ++c){ - if(!vitEqW[c]->IsZappable(this))continue; - - if(vitEqW[c]->Zap(this, GetPos(), iDir)){ DBG1(vitEqW[c]->GetNameSingular().CStr()); //TODO try to aim at NPCs - return true; - } - - if(vitEqW[c]->Apply(this)){ DBG1(vitEqW[c]->GetNameSingular().CStr()); - return true; - } - } - } - - //////////////////////////////// read book - static int iLastReadTurn=-1; - if(game::GetTurn()>(iLastReadTurn+50)){ DBG2(game::GetTurn(),iLastReadTurn); //every X turns try to use stuff from inv - iLastReadTurn=game::GetTurn(); - - static itemvector vitEqW;vitEqW.clear();GetStack()->FillItemVector(vitEqW); - for(uint c = 0; c < vitEqW.size(); ++c){ - if(!vitEqW[c]->IsReadable(this))continue; - static holybook* hb;hb = dynamic_cast(vitEqW[c]); - if(hb==NULL)continue; - - if(vitEqW[c]->Read(this)){ DBG1(vitEqW[c]->GetNameSingular().CStr()); //TODO try to aim at NPCs - return true; - } - } - } - } - - return false; -} - truth humanoid::CheckIfEquipmentIsNotUsable(int I) const { return (I == RIGHT_WIELDED_INDEX && GetRightArm()->CheckIfWeaponTooHeavy("this item")) @@ -5395,11 +5246,17 @@ void FixSumoWrestlerHouse(festring fsCmdParams) for(int d = 0; d < SM->GetNeighbourSquares(); ++d) { lsquare* Square = SM->GetNeighbourLSquare(d); - if(Square){ - character* C2 = Square->GetCharacter(); - if(C2 && dynamic_cast(C2)){ - C2->TeleportRandomly(true); + for(int d2 = 0; d2 < 8; ++d2) + { + lsquare* Square2 = Square->GetNeighbourLSquare(d2); + + if(Square2){ + character* C2 = Square2->GetCharacter(); + if(C2 && dynamic_cast(C2)){ //TODO teleport tourists also + C2->TeleportRandomly(true); + } + } } } } @@ -6713,14 +6570,12 @@ void guard::BeTalkedTo() { itemvector Item; - if(!PLAYER->SelectFromPossessions(Item, CONST_S("\"Do you have something to give me?\""), 0, &item::IsBeverage) - || Item.empty()) - - - for(size_t c = 0; c < Item.size(); ++c) - { - Item[c]->RemoveFromSlot(); - GetStack()->AddItem(Item[c]); + if(PLAYER->SelectFromPossessions(Item, CONST_S("Do you have something to give me?"), 0, &item::IsBeverage)){ + for(size_t c = 0; c < Item.size(); ++c) + { + Item[c]->RemoveFromSlot(); + GetStack()->AddItem(Item[c]); + } } } @@ -6785,112 +6640,8 @@ void xinrochghost::CreateCorpse(lsquare* Square) /*This needs to be a function someday*/ if(!game::GetCurrentLevel()->IsOnGround()) { - ADD_MESSAGE("Suddenly a horrible earthquake shakes the level."); + scrollofearthquake::EarthQuakeMagic("%s is hit by a brick of earth falling from the roof!"); ADD_MESSAGE("You hear an eerie scream: \"Ahh! Free at last! FREE TO BE REBORN!\""); - int c, Tunnels = 2 + RAND() % 3; - if(!game::GetCurrentLevel()->EarthquakesAffectTunnels()) - Tunnels = 0; - - for(c = 0; c < Tunnels; ++c) - game::GetCurrentLevel()->AttachPos(game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE|ATTACHABLE)); - - int ToEmpty = 10 + RAND() % 11; - - for(c = 0; c < ToEmpty; ++c) - for(int i = 0; i < 50; ++i) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE); - truth Correct = false; - - for(int d = 0; d < 8; ++d) - { - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - - if(Square && Square->IsFlyable()) - { - Correct = true; - break; - } - } - - if(Correct) - { - game::GetCurrentLevel()->GetLSquare(Pos)->ChangeOLTerrainAndUpdateLights(0); - break; - } - } - - int ToGround = 20 + RAND() % 21; - - for(c = 0; c < ToGround; ++c) - for(int i = 0; i < 50; ++i) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, RAND() & 1 ? 0 : HAS_CHARACTER); - - if(Pos == ERROR_V2) - continue; - - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* Char = Square->GetCharacter(); - - if(Square->GetOLTerrain() || (Char && (Char->IsPlayer() || PLAYER->GetRelation(Char) != HOSTILE))) - continue; - - int Walkables = 0; - - for(int d = 0; d < 8; ++d) - { - lsquare* NearSquare = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - - if(NearSquare && NearSquare->IsFlyable()) - ++Walkables; - } - - if(Walkables > 6) - { - Square->ChangeOLTerrainAndUpdateLights(earth::Spawn()); - - if(Char) - { - if(Char->CanBeSeenByPlayer()) - ADD_MESSAGE("%s is hit by a brick of earth falling from the roof!", Char->CHAR_NAME(DEFINITE)); - - Char->ReceiveDamage(0, 20 + RAND() % 21, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); - Char->CheckDeath(CONST_S("killed by an earthquake"), 0); - } - - Square->KickAnyoneStandingHereAway(); - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 41, PHYSICAL_DAMAGE); - break; - } - } - - // Generate a few boulders in the level - - int BoulderNumber = 10 + RAND() % 10; - - for(c = 0; c < BoulderNumber; ++c) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(); - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* MonsterHere = Square->GetCharacter(); - - if(!Square->GetOLTerrain() && (!MonsterHere || MonsterHere->GetRelation(PLAYER) == HOSTILE)) - { - Square->ChangeOLTerrainAndUpdateLights(boulder::Spawn(1 + (RAND() & 1))); - - if(MonsterHere) - MonsterHere->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); - - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE); - } - } - - // Damage to items in the level - - for(int x = 0; x < game::GetCurrentLevel()->GetXSize(); ++x) - for(int y = 0; y < game::GetCurrentLevel()->GetYSize(); ++y) - game::GetCurrentLevel()->GetLSquare(x, y)->ReceiveEarthQuakeDamage(); } } diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index 3d98f062d..a6aa3540a 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -13,12 +13,14 @@ #include "area.h" #include "audio.h" #include "bitmap.h" +#include "command.h" #include "feio.h" #include "felist.h" #include "game.h" #include "graphics.h" #include "iconf.h" #include "igraph.h" +#include "message.h" #include "save.h" #include "stack.h" #include "whandler.h" @@ -52,7 +54,11 @@ stringoption ivanconfig::SelectedBkgColor("SelectedBkgColor", &SelectedBkgColorChanger); stringoption ivanconfig::AutoPickUpMatching("AutoPickUpMatching", "Auto pick up regex", - "Automatically pick up items according to a regular expression. To disable something, you can invalidate it with '_' without removing it from the expression (eg. '_dagger'). To disable everything at once, begin this config option with '!'. Due to current constraints on length of options, editing is easier to do externally for now.", //TODO if multiline text editing is implemented, remove the last help statement. + "Automatically pick up items according to a regular expression.\n" + " To disable something, you can invalidate it with '_' without removing it from the expression (eg. '_dagger').\n" + " To disable everything at once, begin this config option with '!'.\n" + " Containers can also auto store items when inscribed with a regular expression (regex).\n" + " Due to current constraints on length of options, editing is easier to do externally for now.", //TODO if multiline text editing is implemented, remove the last help statement. "!((book|can|dagger|grenade|horn of|kiwi|key|ring|scroll|wand|whistle)|^(?:(?!(broken|empty)).)*(bottle|vial)|sol stone)", &configsystem::NormalStringDisplayer, &AutoPickUpMatchingChangeInterface, @@ -143,6 +149,10 @@ truthoption ivanconfig::ShowMapAtDetectMaterial("ShowMapAtDetectMaterial", "Show map while detecting material", "", false); +truthoption ivanconfig::OverloadedFight( "OverloadedFight", + "Allow fighting while overloaded", + "Moving is, of course, still denied.", + false); truthoption ivanconfig::AutoPickupThrownItems("AutoPickupThrownItems", "Auto pick up thrown weapons", "Automatically annotate any thrown weapon and pick it up without loosing a turn when you step on its square.", @@ -163,6 +173,14 @@ truthoption ivanconfig::AllWeightIsRelevant("AllWeightIsRelevant", "Only pile items with equal weight on lists", //clutter are useful now for crafting so their weight matters... "", false); +truthoption ivanconfig::DropBeforeOffering("DropBeforeOffering", + "Drop the item on altar in case it is not accepted", + "Automatically drop offered items on an altar to prevent them from cluttering your inventory should the god not accept your gift. Beware it may be owned floor!", + false); +truthoption ivanconfig::AllowContrastBackground("AllowContrastBackground", + "Better contrast background to dark items and materials", + "", + false); truthoption ivanconfig::ShowVolume( "ShowVolume", "Show item volume in cm3", "", @@ -250,7 +268,7 @@ cycleoption ivanconfig::FontGfx( "FontGfx", &FontGfxChanger); cycleoption ivanconfig::DistLimitMagicMushrooms("DistLimitMagicMushrooms", "Breeders' active range", - "Select the maximum distance where breeding monsters will spawn more of their own. This option can be used to prevent lag from high number of creatures on slow computers, but may impact the intended game balance negatively.", + "Select the maximum distance where breeding monsters will spawn more of their own. This option can be used to prevent lag from high number of creatures on slow computers, but may impact the intended game balance negatively. After you lower this option value to the minimum, you have to wait for all magic clouds (from magic mushrooms) to disappear as they are the main source of lag.", 0, 5, &DistLimitMagicMushroomsDisplayer); cycleoption ivanconfig::SaveGameSortMode( "SaveGameSortMode", @@ -386,7 +404,10 @@ col24 ivanconfig::ContrastLuminance = NORMAL_LUMINANCE; truthoption ivanconfig::PlaySounds( "PlaySounds", "Use sound effects", "Use sound effects for combat, explosions and more.", - true); + true, + &configsystem::NormalTruthDisplayer, + &configsystem::NormalTruthChangeInterface, + &EnableSFX); truthoption ivanconfig::ShowTurn( "ShowTurn", "Show game turn on message log", "Add a game turn number to each action described in the message log.", @@ -419,16 +440,11 @@ void ivanconfig::FrameSkipDisplayer(const numberoption* O, festring& Entry) void ivanconfig::DistLimitMagicMushroomsDisplayer(const cycleoption* O, festring& Entry) { - if(O->Value == 0) - Entry << "everywhere"; - else if(O->Value == 1) - Entry << "close"; - else if(O->Value == 2) - Entry << "near"; - else if(O->Value == 3) - Entry << "medium"; - else if(O->Value == 4) - Entry << "far"; + if (O->Value == 0) Entry << "everywhere"; + else if(O->Value == 1) Entry << "close"; + else if(O->Value == 2) Entry << "near"; + else if(O->Value == 3) Entry << "medium"; + else if(O->Value == 4) Entry << "far"; else Entry << O->Value; } @@ -906,12 +922,31 @@ void ivanconfig::SelectedBkgColorChanger(stringoption* O, cfestring& What) } } +col16 ivanconfig::CheckChangeColor(col16 col) +{ + if(IsAllowContrastBackground()){ + static col16 colRef = DARK_GRAY; + static col16 iR = GetRed16(colRef); + static col16 iG = GetGreen16(colRef); + static col16 iB = GetBlue16(colRef); + if( + GetRed16(col) Value.Empty(); O->Value<Value){ @@ -1248,6 +1288,8 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&DistLimitMagicMushrooms); configsystem::AddOption(fsCategory,&AutoPickupThrownItems); configsystem::AddOption(fsCategory,&AutoPickUpMatching); + configsystem::AddOption(fsCategory,&DropBeforeOffering); + configsystem::AddOption(fsCategory,&OverloadedFight); fsCategory="Game Window"; configsystem::AddOption(fsCategory,&Contrast); @@ -1280,6 +1322,7 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&HitIndicator); configsystem::AddOption(fsCategory,&ShowMap); configsystem::AddOption(fsCategory,&TransparentMapLM); + configsystem::AddOption(fsCategory,&AllowContrastBackground); configsystem::AddOption(fsCategory,&UseExtraMenuGraphics); fsCategory="Sounds"; diff --git a/Main/Source/id.cpp b/Main/Source/id.cpp index e9f705321..21060e057 100644 --- a/Main/Source/id.cpp +++ b/Main/Source/id.cpp @@ -63,7 +63,18 @@ void id::AddName(festring& Name, int Case) const else AddNameSingular(Name, Articled); - AddPostFix(Name, Case); + if(!(Case & NOPOSTFIX)) + AddPostFix(Name, Case); +} + +festring id::GetNameToMatch() const +{ + static festring Name; + Name.Empty(); + AddAdjective(Name, false); + AddNameSingular(Name, false); + AddMaterialDescription(Name, false); + return Name; } festring id::GetName(int Case) const diff --git a/Main/Source/item.cpp b/Main/Source/item.cpp index 5f44f2d1a..61945fa7c 100644 --- a/Main/Source/item.cpp +++ b/Main/Source/item.cpp @@ -404,7 +404,7 @@ truth item::Alchemize(character* Midas, stack* CurrentStack) long Price = GetTruePrice(); - if(Price) + if(Midas && Price) { Price /= 4; /* slightly lower than with 10 Cha */ ADD_MESSAGE("Gold pieces clatter on the floor."); @@ -530,7 +530,7 @@ void item::AddName(festring& Name, int Case) const { object::AddName(Name,Case); - if(label.GetSize()) + if(!(Case&UNLABELED) && label.GetSize()) Name << " inscribed " << label; } @@ -1513,6 +1513,12 @@ void item::Draw(blitdata& BlitData) const } cbitmap* P = bmp; + if(GetMainMaterial() && (BlitData.CustomData & ALLOW_CONTRAST)){ + col16 col = ivanconfig::CheckChangeColor(GetMainMaterial()->GetColor()); + if(col!=GetMainMaterial()->GetColor()) + BlitData.Bitmap->Fill(BlitData.Dest,TILE_V2,col); + } + if(BlitData.CustomData & ALLOW_ALPHA) P->AlphaLuminanceBlit(BlitData); else @@ -1704,8 +1710,12 @@ void item::SendNewDrawAndMemorizedUpdateRequest() const if(Slot[c]) { lsquare* Square = GetLSquareUnder(c); - Square->SendNewDrawRequest(); - Square->SendMemorizedUpdateRequest(); + if(Square){ //TODO is this fix ok? let it crash elsewhere better to track the problem... + Square->SendNewDrawRequest(); + Square->SendMemorizedUpdateRequest(); + }else{ + DBG4("Is nowhere to be found, how!?",Square,GetNameSingular().CStr(),SquaresUnder); + } } } diff --git a/Main/Source/itemset.cpp b/Main/Source/itemset.cpp index 7385afd7f..a18b5d9be 100644 --- a/Main/Source/itemset.cpp +++ b/Main/Source/itemset.cpp @@ -28,28 +28,28 @@ EXTENDED_SYSTEM_SPECIALIZATIONS(item)(0, 0, 0, "item"); #include #include +#include "balance.h" +#include "bitmap.h" #include "char.h" -#include "message.h" -#include "stack.h" -#include "lterras.h" -#include "felist.h" #include "confdef.h" -#include "room.h" +#include "felist.h" +#include "fluid.h" #include "game.h" -#include "materias.h" +#include "god.h" #include "human.h" +#include "iconf.h" +#include "lterras.h" +#include "materias.h" +#include "message.h" #include "nonhuman.h" -#include "team.h" -#include "god.h" -#include "team.h" -#include "smoke.h" +#include "rawbit.h" +#include "room.h" #include "save.h" +#include "smoke.h" +#include "stack.h" +#include "team.h" #include "whandler.h" -#include "bitmap.h" -#include "fluid.h" -#include "rawbit.h" -#include "balance.h" -#include "iconf.h" +#include "wizautoplay.h" #include "worldmap.h" #include "wterras.h" diff --git a/Main/Source/level.cpp b/Main/Source/level.cpp index 1b99b16d4..84979f00b 100644 --- a/Main/Source/level.cpp +++ b/Main/Source/level.cpp @@ -1254,13 +1254,15 @@ truth level::CollectCreatures(charactervector& CharacterArray, character* Leader if(!AllowHostiles) for(c = 0; c < game::GetTeams(); ++c) if(Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) - for(character* p : game::GetTeam(c)->GetMember()) + for(character* p : game::GetTeam(c)->GetMember()){ + p->ValidateTrapData(); if(p->IsEnabled() && Leader->CanBeSeenBy(p) && Leader->SquareUnderCanBeSeenBy(p, true) && p->CanFollow()) { ADD_MESSAGE("You can't escape when there are hostile creatures nearby."); return false; } + } truth TakeAll = true; @@ -1274,7 +1276,8 @@ truth level::CollectCreatures(charactervector& CharacterArray, character* Leader for(c = 0; c < game::GetTeams(); ++c) if(game::GetTeam(c) == Leader->GetTeam() || Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) - for(character* p : game::GetTeam(c)->GetMember()) + for(character* p : game::GetTeam(c)->GetMember()){ + p->ValidateTrapData(); if(p->IsEnabled() && p != Leader && (TakeAll || (Leader->CanBeSeenBy(p) @@ -1292,6 +1295,7 @@ truth level::CollectCreatures(charactervector& CharacterArray, character* Leader p->Remove(); } } + } return true; } diff --git a/Main/Source/lterras.cpp b/Main/Source/lterras.cpp index 2ad66258e..66649be05 100644 --- a/Main/Source/lterras.cpp +++ b/Main/Source/lterras.cpp @@ -1233,28 +1233,7 @@ truth olterraincontainer::Open(character* Opener) if(!Opener->IsPlayer()) return false; - truth Success; - - switch(game::KeyQuestion(CONST_S("Do you want to (t)ake something from or " - "(p)ut something in this container? [t,p]"), - KEY_ESC, 3, 't', 'p', KEY_ESC)) - { - case 't': - case 'T': - Success = GetContained()->TakeSomethingFrom(Opener, GetName(DEFINITE)); - break; - case 'p': - case 'P': - Success = GetContained()->PutSomethingIn(Opener, GetName(DEFINITE), GetStorageVolume(), 0); - break; - default: - return false; - } - - if(Success) - Opener->DexterityAction(Opener->OpenMultiplier() * 5); - - return Success; + return itemcontainer::OpenGeneric(Opener,GetContained(),GetName(DEFINITE),GetStorageVolume(),0); } void olterraincontainer::SetItemsInside(const fearray>& ItemArray, int SpecialFlags) diff --git a/Main/Source/main.cpp b/Main/Source/main.cpp index 146f3cc40..494d19a1d 100644 --- a/Main/Source/main.cpp +++ b/Main/Source/main.cpp @@ -30,6 +30,7 @@ #endif #include "game.h" +#include "curseddeveloper.h" #include "database.h" #include "definesvalidator.h" #include "devcons.h" @@ -121,6 +122,9 @@ int main(int argc, char** argv) std::cout << "IVAN_DebugGenDungeonLevelLoopMax=[integer] #DEBUG generate the dungeon level how many times" << std::endl; #ifdef WIZARD std::cout << "IVAN_DebugStayOnDungeonLevel=[DungeonLevelIndex] #DEBUG wizard auto play AI will not leave that Dungeon Level after entering it" << std::endl; +#endif +#ifdef CURSEDDEVELOPER + std::cout << "IVAN_CURSEDDEVELOPER=[true] #DEBUG (only for developer tests) this will prevent player's death (and scoring) in normal (non wizard) gameplay" << std::endl; #endif return 0; } @@ -149,6 +153,9 @@ int main(int argc, char** argv) specialkeys::init(); bugfixdp::init(); devcons::Init(); +#ifdef CURSEDDEVELOPER + curseddeveloper::Init(); +#endif definesvalidator::init(); msgsystem::Init(); protosystem::Initialize(); diff --git a/Main/Source/materia.cpp b/Main/Source/materia.cpp index c336c7cc0..b3bcf7e06 100644 --- a/Main/Source/materia.cpp +++ b/Main/Source/materia.cpp @@ -12,6 +12,8 @@ /* Compiled through materset.cpp */ +#include "dbgmsgproj.h" + materialprototype::materialprototype(const materialprototype* Base, materialspawner Spawner, materialcloner Cloner, @@ -83,6 +85,22 @@ truth material::Effect(character* Char, int BodyPart, long Amount) if(!Amount) return false; + /** + * Pos is used to prepare a seed for temporary random state. + * Some rare times, mother entity is nowhere (square under is NULL) and the game would crash... + * So... why not just use the Char pos? it is random anyway... right? + */ + v2 Pos; + if(Pos.Is0() && GetMotherEntity() && GetMotherEntity()->GetSquareUnderEntity()){ + Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos(); + }else{ + DBGSTK;DBG3("_BUG_TRACK_:Workaround for mother entity being nowhere to prevent crash",GetMotherEntity(),Char); + } + if(Pos.Is0() && Char && Char->GetSquareUnder()) + Pos=Char->GetPos(); + if(Pos.Is0() && game::GetCurrentLevel()) + Pos=game::GetCurrentLevel()->GetRandomSquare(); //TODO any better idea? + switch(GetEffect()) { case EFFECT_POISON: Char->BeginTemporaryState(POISONED, Amount); break; @@ -111,7 +129,6 @@ truth material::Effect(character* Char, int BodyPart, long Amount) case EFFECT_SKUNK_SMELL: Char->BeginTemporaryState(POISONED, Amount); break; case EFFECT_MAGIC_MUSHROOM: { - v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos(); Char->ActivateRandomState(SRC_MAGIC_MUSHROOM, Amount, Volume % 250 + Pos.X + Pos.Y + 1); break; @@ -124,14 +141,12 @@ truth material::Effect(character* Char, int BodyPart, long Amount) case EFFECT_HOLY_BANANA: Char->ReceiveHolyBanana(Amount); break; case EFFECT_EVIL_WONDER_STAFF_VAPOUR: { - v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos(); Char->ActivateRandomState(SRC_EVIL, Amount, Volume % 250 + Pos.X + Pos.Y + 1); break; } case EFFECT_GOOD_WONDER_STAFF_VAPOUR: { - v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos(); Char->ActivateRandomState(SRC_GOOD, Amount, Volume % 250 + Pos.X + Pos.Y + 1); break; @@ -143,7 +158,6 @@ truth material::Effect(character* Char, int BodyPart, long Amount) case EFFECT_TELEPORT_CONTROL: Char->BeginTemporaryState(TELEPORT_CONTROL, Amount); break; case EFFECT_MUSHROOM: { - v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos(); Char->ActivateRandomState(SRC_MUSHROOM, Amount, Volume % 250 + Pos.X + Pos.Y + 1); break; diff --git a/Main/Source/miscitem.cpp b/Main/Source/miscitem.cpp index 72e88b3b8..3a6b6b7e6 100644 --- a/Main/Source/miscitem.cpp +++ b/Main/Source/miscitem.cpp @@ -12,6 +12,8 @@ /* Compiled through itemset.cpp */ +#include "dbgmsgproj.h" + material* materialcontainer::SetSecondaryMaterial(material* What, int SpecialFlags) { return SetMaterial(SecondaryMaterial, What, GetDefaultSecondaryVolume(), SpecialFlags); } void materialcontainer::InitMaterials(material* M1, material* M2, truth CUP) @@ -61,8 +63,9 @@ long nuke::GetTotalExplosivePower() const { return GetSecondaryMaterial() ? GetSecondaryMaterial()->GetTotalExplosivePower() : 0; } long stone::GetTruePrice() const { return item::GetTruePrice() << 1; } - -//long ingot::GetTruePrice() const { return item::GetTruePrice() << 1; } +/* +long nail::GetTruePrice() const { return item::GetTruePrice() << 1; } +*/ col16 whistle::GetMaterialColorB(int) const { return MakeRGB16(80, 32, 16); } @@ -188,115 +191,126 @@ void scrolloffireballs::FinishReading(character* Reader) Square->FireBall(Beam); } -void scrollofearthquake::FinishReading(character* Reader) +void scrollofearthquake::EarthQuakeMagic(festring fsMsgHitNPC) { - if(!game::GetCurrentLevel()->IsOnGround()) - { - ADD_MESSAGE("Suddenly a horrible earthquake shakes the level!"); - int c, Tunnels = 2 + RAND() % 3; - if(!game::GetCurrentLevel()->EarthquakesAffectTunnels()) - Tunnels = 0; + ADD_MESSAGE("Suddenly a horrible earthquake shakes the level!"); + int c, Tunnels = 2 + RAND() % 3; + if(!game::GetCurrentLevel()->EarthquakesAffectTunnels()) + Tunnels = 0; - for(c = 0; c < Tunnels; ++c) - game::GetCurrentLevel()->AttachPos(game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE|ATTACHABLE)); + for(c = 0; c < Tunnels; ++c){ + v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE|ATTACHABLE); + if(Pos == ERROR_V2)continue; + game::GetCurrentLevel()->AttachPos(Pos); + } - int ToEmpty = 10 + RAND() % 11; + int ToEmpty = 10 + RAND() % 11; - for(c = 0; c < ToEmpty; ++c) - for(int i = 0; i < 50; ++i) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE); - truth Correct = false; + for(c = 0; c < ToEmpty; ++c){ + for(int i = 0; i < 50; ++i) + { + v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, NOT_WALKABLE); + if(Pos == ERROR_V2)continue; //this may probably happen at Spider Nest dungeon level - for(int d = 0; d < 8; ++d) - { - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); + truth Correct = false; - if(Square && Square->IsFlyable()) - { - Correct = true; - break; - } - } + for(int d = 0; d < 8; ++d) + { + lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - if(Correct) + if(Square && Square->IsFlyable()) { - game::GetCurrentLevel()->GetLSquare(Pos)->ChangeOLTerrainAndUpdateLights(0); + Correct = true; break; } } - int ToGround = 20 + RAND() % 21; - - for(c = 0; c < ToGround; ++c) - for(int i = 0; i < 50; ++i) + if(Correct) { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, RAND() & 1 ? 0 : HAS_CHARACTER); + game::GetCurrentLevel()->GetLSquare(Pos)->ChangeOLTerrainAndUpdateLights(0); + break; + } + } + } - if(Pos == ERROR_V2) - continue; + int ToGround = 20 + RAND() % 21; - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* Char = Square->GetCharacter(); + for(c = 0; c < ToGround; ++c) + for(int i = 0; i < 50; ++i) + { + v2 Pos = game::GetCurrentLevel()->GetRandomSquare(0, RAND() & 1 ? 0 : HAS_CHARACTER); - if(Square->GetOLTerrain() || (Char && (Char->IsPlayer() || PLAYER->GetRelation(Char) != HOSTILE))) - continue; + if(Pos == ERROR_V2) + continue; - int Walkables = 0; + lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); + character* Char = Square->GetCharacter(); - for(int d = 0; d < 8; ++d) - { - lsquare* NearSquare = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); + if(Square->GetOLTerrain() || (Char && (Char->IsPlayer() || PLAYER->GetRelation(Char) != HOSTILE))) + continue; - if(NearSquare && NearSquare->IsFlyable()) - ++Walkables; - } + int Walkables = 0; - if(Walkables > 6) - { - Square->ChangeOLTerrainAndUpdateLights(earth::Spawn()); + for(int d = 0; d < 8; ++d) + { + lsquare* NearSquare = game::GetCurrentLevel()->GetLSquare(Pos)->GetNeighbourLSquare(d); - if(Char) - { - if(Char->CanBeSeenByPlayer()) - ADD_MESSAGE("%s is hit by a rock falling from the ceiling!", Char->CHAR_NAME(DEFINITE)); + if(NearSquare && NearSquare->IsFlyable()) + ++Walkables; + } - Char->ReceiveDamage(0, 20 + RAND() % 21, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); - Char->CheckDeath(CONST_S("killed by an earthquake"), 0); - } + if(Walkables > 6) + { + Square->ChangeOLTerrainAndUpdateLights(earth::Spawn()); - Square->KickAnyoneStandingHereAway(); - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 41, PHYSICAL_DAMAGE); - break; + if(Char) + { + if(Char->CanBeSeenByPlayer()) + ADD_MESSAGE(fsMsgHitNPC.IsEmpty()?"%s is hit by a rock falling from the ceiling!":fsMsgHitNPC.CStr(), + Char->CHAR_NAME(DEFINITE)); + + Char->ReceiveDamage(0, 20 + RAND() % 21, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); + Char->CheckDeath(CONST_S("killed by an earthquake"), 0); } + + Square->KickAnyoneStandingHereAway(); + Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 41, PHYSICAL_DAMAGE); + break; } + } - // Generate a few boulders in the level + // Generate a few boulders in the level - int BoulderNumber = 10 + RAND() % 10; + int BoulderNumber = 10 + RAND() % 10; - for(c = 0; c < BoulderNumber; ++c) - { - v2 Pos = game::GetCurrentLevel()->GetRandomSquare(); - lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); - character* MonsterHere = Square->GetCharacter(); + for(c = 0; c < BoulderNumber; ++c) + { + v2 Pos = game::GetCurrentLevel()->GetRandomSquare(); + lsquare* Square = game::GetCurrentLevel()->GetLSquare(Pos); + character* MonsterHere = Square->GetCharacter(); - if(!Square->GetOLTerrain() && (!MonsterHere || MonsterHere->GetRelation(PLAYER) == HOSTILE)) - { - Square->ChangeOLTerrainAndUpdateLights(boulder::Spawn(1 + (RAND() & 1))); + if(!Square->GetOLTerrain() && (!MonsterHere || MonsterHere->GetRelation(PLAYER) == HOSTILE)) + { + Square->ChangeOLTerrainAndUpdateLights(boulder::Spawn(1 + (RAND() & 1))); - if(MonsterHere) - MonsterHere->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); + if(MonsterHere) + MonsterHere->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE, HEAD|TORSO, 8, true); - Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE); - } + Square->GetStack()->ReceiveDamage(0, 10 + RAND() % 10, PHYSICAL_DAMAGE); } + } - // Damage to items in the level + // Damage to items in the level - for(int x = 0; x < game::GetCurrentLevel()->GetXSize(); ++x) - for(int y = 0; y < game::GetCurrentLevel()->GetYSize(); ++y) - game::GetCurrentLevel()->GetLSquare(x, y)->ReceiveEarthQuakeDamage(); + for(int x = 0; x < game::GetCurrentLevel()->GetXSize(); ++x) + for(int y = 0; y < game::GetCurrentLevel()->GetYSize(); ++y) + game::GetCurrentLevel()->GetLSquare(x, y)->ReceiveEarthQuakeDamage(); +} +void scrollofearthquake::FinishReading(character* Reader) +{ + if(!game::GetCurrentLevel()->IsOnGround()) + { + EarthQuakeMagic(); } else { @@ -1170,7 +1184,7 @@ truth key::Apply(character* User) } v2 DirVect = game::GetDirectionVectorForKey(Key); - + if(DirVect != ERROR_V2 && User->GetArea()->IsValidPos(User->GetPos() + DirVect)) return GetLevel()->GetLSquare(User->GetPos() + DirVect)->TryKey(this, User); } @@ -1330,6 +1344,8 @@ void magicalwhistle::BlowEffect(character* Whistler) itemcontainer::itemcontainer() { Contained = new stack(0, this, HIDDEN); + pcreAutoStoreRegex = festring::CompilePCRE(NULL,GetLabel()); + bLazyInitPcre=true; } void itemcontainer::PostConstruct() @@ -1374,16 +1390,8 @@ void materialcontainer::GenerateMaterials() GetDefaultSecondaryVolume()); } -/* Returns true if container opens fine else false */ - -truth itemcontainer::Open(character* Opener) +truth itemcontainer::OpenGeneric(character* Opener, stack* Stk, festring fsName, long volume, ulong ID) { - if(IsLocked()) - { - ADD_MESSAGE("%s seems to be locked.", CHAR_NAME(DEFINITE)); - return false; - } - festring Question = CONST_S("Do you want to (t)ake something from or " "(p)ut something in this container? [t,p]"); truth Success; @@ -1392,11 +1400,11 @@ truth itemcontainer::Open(character* Opener) { case 't': case 'T': - Success = GetContained()->TakeSomethingFrom(Opener, GetName(DEFINITE)); + Success = Stk->TakeSomethingFrom(Opener, fsName); break; case 'p': case 'P': - Success = GetContained()->PutSomethingIn(Opener, GetName(DEFINITE), GetStorageVolume(), GetID()); + Success = Stk->PutSomethingIn(Opener, fsName, volume, ID); break; default: return false; @@ -1404,10 +1412,23 @@ truth itemcontainer::Open(character* Opener) if(Success) Opener->DexterityAction(Opener->OpenMultiplier() * 5); - + return Success; } +/* Returns true if container opens fine else false */ + +truth itemcontainer::Open(character* Opener) +{ + if(IsLocked()) + { + ADD_MESSAGE("%s seems to be locked.", CHAR_NAME(DEFINITE)); + return false; + } + + return OpenGeneric(Opener,GetContained(),GetName(DEFINITE),GetStorageVolume(),ID); +} + void itemcontainer::Save(outputfile& SaveFile) const { lockableitem::Save(SaveFile); @@ -1590,7 +1611,7 @@ void beartrap::StepOnEffect(character* Stepper) truth beartrap::CheckPickUpEffect(character* Picker) { - if(Picker->IsStuckToTrap(GetTrapID())) + if(Picker->ValidateTrapData() && Picker->IsStuckToTrap(GetTrapID())) { if(Picker->IsPlayer()) ADD_MESSAGE("You are tightly stuck in %s.", CHAR_NAME(DEFINITE)); @@ -1624,7 +1645,6 @@ truth stethoscope::Apply(character* Doctor) ABORT("Doctor is not here, man, but these pills taste just as good anyway."); int Dir = game::DirectionQuestion(CONST_S("What do you want to inspect? [press a direction key]"), false, true); - if(Dir == DIR_ERROR) return false; @@ -1660,8 +1680,8 @@ truth itemcontainer::ContentsCanBeSeenBy(ccharacter* Viewer) const truth mine::Apply(character* User) { - if(User->IsPlayer() && !game::TruthQuestion(CONST_S("Are you sure you want to plant ") - + GetName(DEFINITE) + "? [y/N]")) + if(User->IsPlayer() && (!wizautoplay::IsPlayerAutoPlay(User)) && + !game::TruthQuestion(CONST_S("Are you sure you want to plant ") + GetName(DEFINITE) + "? [y/N]")) return false; room* Room = GetRoom(); @@ -1695,8 +1715,8 @@ truth beartrap::Apply(character* User) return false; } - if(User->IsPlayer() - && !game::TruthQuestion(CONST_S("Are you sure you want to plant ") + GetName(DEFINITE) + "? [y/N]")) + if(User->IsPlayer() && (!wizautoplay::IsPlayerAutoPlay(User)) && + !game::TruthQuestion(CONST_S("Are you sure you want to plant ") + GetName(DEFINITE) + "? [y/N]")) return false; room* Room = GetRoom(); @@ -1769,9 +1789,33 @@ materialcontainer::materialcontainer(const materialcontainer& MC) : mybase(MC) itemcontainer::itemcontainer(const itemcontainer& Container) : mybase(Container) { Contained = new stack(0, this, HIDDEN); + pcreAutoStoreRegex = festring::CompilePCRE(NULL,Container.GetLabel()); + bLazyInitPcre=true; + CalculateAll(); } +void itemcontainer::SetLabel(cfestring& What) +{ + item::SetLabel(What); + pcreAutoStoreRegex = festring::CompilePCRE(pcreAutoStoreRegex,GetLabel()); +} + +truth itemcontainer::IsAutoStoreMatch(cfestring fs) { + if(bLazyInitPcre){ + if(!pcreAutoStoreRegex && !GetLabel().IsEmpty()){ + pcreAutoStoreRegex = festring::CompilePCRE(pcreAutoStoreRegex,GetLabel()); + DBGEXEC( if(!pcreAutoStoreRegex){ DBG2("InvalidRegex",GetLabel().CStr()); } ); + } + bLazyInitPcre=false; + } + + if(!pcreAutoStoreRegex) + return false; + + return pcre_exec(pcreAutoStoreRegex, 0, fs.CStr(), fs.GetSize(), 0, 0, NULL, 0) >= 0; +} + oillamp::oillamp(const oillamp& Lamp) : mybase(Lamp), InhabitedByGenie(false) { } @@ -3764,7 +3808,7 @@ void materialmanual::FinishReading(character* Reader) Entry << Material[c]->GetFlexibility(); Entry.Resize(70); Entry << Material[c]->GetDensity(); - List.AddEntry(Entry, Material[c]->GetColor()); + List.AddEntry(Entry, ivanconfig::CheckChangeColor(Material[c]->GetColor())); } List.Draw(); @@ -4057,8 +4101,8 @@ truth gastrap::Apply(character* User) return false; } - if(User->IsPlayer() - && !game::TruthQuestion(CONST_S("Are you sure you want to plant ") + GetName(DEFINITE) + "? [y/N]")) + if(User->IsPlayer() && (!wizautoplay::IsPlayerAutoPlay(User)) && + !game::TruthQuestion(CONST_S("Are you sure you want to plant ") + GetName(DEFINITE) + "? [y/N]")) return false; room* Room = GetRoom(); diff --git a/Main/Source/nonhuman.cpp b/Main/Source/nonhuman.cpp index 326d97d51..c108964f6 100644 --- a/Main/Source/nonhuman.cpp +++ b/Main/Source/nonhuman.cpp @@ -313,12 +313,14 @@ truth nonhumanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit()) return false; - if(GetBurdenState() == OVER_LOADED) - { - if(IsPlayer()) - ADD_MESSAGE("You cannot fight while carrying so much."); + if(!ivanconfig::IsOverloadedFight()){ + if(GetBurdenState() == OVER_LOADED) + { + if(IsPlayer()) + ADD_MESSAGE("You cannot fight while carrying so much."); - return false; + return false; + } } /* Behold this Terrible Father of Gum Solutions! */ @@ -328,9 +330,11 @@ truth nonhumanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) if(AttackStyle & USE_LEGS) { room* Room = GetNearLSquare(HitPos)->GetRoom(); - if(Room && !Room->AllowKick(this, GetNearLSquare(HitPos))) AttackStyle &= ~USE_LEGS; + + if(ivanconfig::IsOverloadedFight() && GetBurdenState() == OVER_LOADED && RAND()%3!=0) + AttackStyle &= ~USE_LEGS; } int c, AttackStyles; @@ -357,6 +361,8 @@ truth nonhumanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags) msgsystem::LeaveBigMessageMode(); return true; case USE_LEGS: + if(OverloadedKickFailCheck()) + return false; msgsystem::EnterBigMessageMode(); Hostility(Enemy); Kick(GetNearLSquare(HitPos), Direction, Flags & SADIST_HIT); @@ -1137,6 +1143,53 @@ truth eddy::Hit(character* Enemy, v2, int, int) return true; } +/** + * This is not gameplay wise as far AI events will not happen, + * but will allow the game to still be playable at least... + * Use this on any NPC class that may encumber the CPU too much. + * + * TODO Confirm if the main lag problem is related to magic clouds *animations* + * or other magic cloud calculations? If the problem is about animations, + * non visible (or simply far away) ones could just be skipped w/o problem! + */ +bool CPUwiseAI(nonhumanoid* nh) +{ + if(!nh->IsRooted())return true; //only NPCs that can't move + + int iDist = ivanconfig::GetDistLimitMagicMushrooms(); + if(iDist==0)return true; //everywhere allowed + + iDist*=4; // this gives min AI = 64, max = 1024 but the lag seems related to magic clouds (non visible animations?) from magic mushrooms, as soon the clouds vanish, lag goes away too + + int iSqDist = nh->GetDistanceSquareFrom(PLAYER); + int iSqLim = iDist*iDist; + int iMaxActiveAI = iDist*2 * iDist*2; + + static int iPreviousTurnActivatedAIs=0; + static int iTurnChkAI = 0; + if(iTurnChkAI != game::GetTurn()){ DBG6(iTurnChkAI,iPreviousTurnActivatedAIs,iDist,iSqDist,iSqLim,iMaxActiveAI); + iPreviousTurnActivatedAIs=0; + iTurnChkAI = game::GetTurn(); + } + + bool bActivated = false; + if(iTurnChkAI==game::GetTurn()){ + if(iPreviousTurnActivatedAIsStateIsActivated(LEVITATION)){ + iPreviousTurnActivatedAIs++; + bActivated=true; + } + } + } + + return bActivated; +} + void mushroom::Save(outputfile& SaveFile) const { nonhumanoid::Save(SaveFile); @@ -1151,6 +1204,8 @@ void mushroom::Load(inputfile& SaveFile) void mushroom::GetAICommand() { + if(!CPUwiseAI(this))return; + SeekLeader(GetLeader()); if(FollowLeader(GetLeader())) @@ -1215,45 +1270,9 @@ void mushroom::PostConstruct() } } -/** - * This is not gameplay wise as far AI events will not happen, - * but will allow the game to still be playable at least... - * Use this on any NPC class that may encumber the CPU too much. - */ -bool CPUwiseAI(nonhumanoid* nh) -{ - if(!nh->IsRooted())return true; //only NPCs that can't move - if(nh->StateIsActivated(LEVITATION))return true; //this keeps levitating ones still active what may be good TODO add user option to deny them? - - int iDist = ivanconfig::GetDistLimitMagicMushrooms(); - if(iDist==0)return true; - - int iSqDist = nh->GetDistanceSquareFrom(PLAYER); - int iSqLim = iDist*iDist; - int iMaxActiveAI = iDist*2 * iDist*2; - - static int iPreviousTurnActivatedAIs=0; - static int iTurnChkAI = 0; - if(iTurnChkAI != game::GetTurn()){ DBG4(iTurnChkAI,iPreviousTurnActivatedAIs,iSqLim,iMaxActiveAI); - iPreviousTurnActivatedAIs=0; - iTurnChkAI = game::GetTurn(); - } - - bool bActivated = false; - if(iTurnChkAI==game::GetTurn()){ - if(iPreviousTurnActivatedAIs= Enemy->GetAttackWisdomLimit()) return false; - if(GetBurdenState() == OVER_LOADED) - { - if(IsPlayer()) - ADD_MESSAGE("You cannot fight while carrying so much."); + if(!ivanconfig::IsOverloadedFight()){ + if(GetBurdenState() == OVER_LOADED) + { + if(IsPlayer()) + ADD_MESSAGE("You cannot fight while carrying so much."); - return false; + return false; + } } Hostility(Enemy); @@ -2467,6 +2488,7 @@ void spider::GetAICommand() if(NearestChar) { + NearestChar->ValidateTrapData(); if(NearestChar->IsStuck() || GetConfig() == ARANEA || (GetConfig() == PHASE && !CanBeSeenBy(NearestChar))) SetGoingTo(NearestChar->GetPos()); @@ -2521,23 +2543,7 @@ truth largecat::SpecialSaveLife() if(!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears!", CHAR_NAME(INDEFINITE)); - if(IsPlayer()) - game::AskForKeyPress(CONST_S("Life saved! [press any key to continue]")); - - RestoreBodyParts(); - ResetSpoiling(); - ResetBurning(); - RestoreHP(); - RestoreStamina(); - ResetStates(); - - if(GetNP() < SATIATED_LEVEL) - SetNP(SATIATED_LEVEL); - - SendNewDrawRequest(); - - if(GetAction()) - GetAction()->Terminate(false); + SaveLifeBase(); return true; } @@ -2706,6 +2712,7 @@ void lobhse::GetAICommand() if(NearestChar) { + NearestChar->ValidateTrapData(); if(NearestChar->IsStuck()) SetGoingTo(NearestChar->GetPos()); else diff --git a/Main/Source/traps.cpp b/Main/Source/traps.cpp index 4f21e195a..95a27e4d3 100644 --- a/Main/Source/traps.cpp +++ b/Main/Source/traps.cpp @@ -42,6 +42,12 @@ int web::GetRemoveTrapModifier(character* C) + C->GetAttribute(ARM_STRENGTH), 1); } +/** + * + * @param Actor who tears down the web (from self or another character*) + * @param Modifier + * @return + */ truth web::TryToTearDown(character* Actor,int Modifier) { if(Modifier==-1) @@ -50,16 +56,21 @@ truth web::TryToTearDown(character* Actor,int Modifier) if(!RAND_N(Max(Modifier << 1, 2))) { //if(GetLSquareUnder()->GetPos()==Actor->GetPos())C->RemoveTrap(GetTrapID());else - if(GetLSquareUnder()->GetCharacter()) - GetLSquareUnder()->GetCharacter()->RemoveTrap(GetTrapID()); + character* C = GetLSquareUnder()->GetCharacter(); + if(C && C->ValidateTrapData() && C->IsStuckToTrap(GetTrapID())) + C->RemoveTrap(GetTrapID()); TrapData.VictimID = 0; GetLSquareUnder()->RemoveTrap(this); SendToHell(); - + + festring fsOther; + if(C && Actor!=C) + fsOther=CONST_S(" from ")+C->CHAR_NAME(DEFINITE); + if(Actor->IsPlayer()) - ADD_MESSAGE("You tear the web down."); + ADD_MESSAGE("You tear the web down%s.",fsOther.CStr()); else if(Actor->CanBeSeenByPlayer()) - ADD_MESSAGE("%s tears the web down.", Actor->CHAR_NAME(DEFINITE)); + ADD_MESSAGE("%s tears the web down%s.", Actor->CHAR_NAME(DEFINITE),fsOther.CStr()); Actor->EditAP(-500); return true; diff --git a/Main/Source/wizautoplay.cpp b/Main/Source/wizautoplay.cpp new file mode 100644 index 000000000..55ac34a32 --- /dev/null +++ b/Main/Source/wizautoplay.cpp @@ -0,0 +1,1370 @@ +/* + * + * 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 + * + */ + +/* Compiled through charset.cpp */ + +#include "dbgmsgproj.h" + +/** + * 5 seems good, broken cheap weapons, stones, very cheap weapons non broken etc + * btw, lantern price is currently 10. + */ +int wizautoplay::iMaxValueless = 5; + +#ifdef WIZARD + +int wizautoplay::AutoPlayMode=AUTOPLAYMODE_DISABLED; + +character* wizautoplay::P=NULL; + +bool bSafePrayOnce=false; +void wizautoplay::AutoPlayAITeleport(bool bDeathCountBased) +{ + bool bTeleportNow=false; + + if(bDeathCountBased){ // this is good to prevent autoplay AI getting stuck endless dieing + static int iDieMax=10; + static int iDieTeleportCountDown=iDieMax; + if(iDieTeleportCountDown==0){ //this helps on defeating not so strong enemies in spot + if(IsPlayerAutoPlay()) + bTeleportNow=true; + iDieTeleportCountDown=iDieMax; + bSafePrayOnce=true; + }else{ + static v2 v2DiePos(0,0); + if(v2DiePos==P->GetPos()){ + iDieTeleportCountDown--; + }else{ + v2DiePos=P->GetPos(); + iDieTeleportCountDown=iDieMax; + } + } + } + + if(bTeleportNow) + P->Move(P->GetLevel()->GetRandomSquare(P), true); //not using teleport function to avoid prompts, but this code is from there +} + +character* AutoPlayLastChar=NULL; +const int iMaxWanderTurns=20; +const int iMinWanderTurns=3; + +v2 v2KeepGoingTo=v2(0,0); +v2 v2TravelingToAnotherDungeon=v2(0,0); +int iWanderTurns=iMinWanderTurns; +bool bAutoPlayUseRandomNavTargetOnce=false; +std::vector vv2DebugDrawSqrPrevious; +v2 v2LastDropPlayerWasAt=v2(0,0); +std::vector vv2FailTravelToTargets; +std::vector vv2WrongGoingTo; + +void wizautoplay::AutoPlayAIReset(bool bFailedToo) +{ DBG7(bFailedToo,iWanderTurns,DBGAV2(v2KeepGoingTo),DBGAV2(v2TravelingToAnotherDungeon),DBGAV2(v2LastDropPlayerWasAt),vv2FailTravelToTargets.size(),vv2DebugDrawSqrPrevious.size()); + v2KeepGoingTo=v2(0,0); //will retry + v2TravelingToAnotherDungeon=v2(0,0); + iWanderTurns=0; // warning: this other code was messing the logic ---> if(iWanderTurnsTerminateGoingTo(); + + if(bFailedToo){ + vv2FailTravelToTargets.clear(); + vv2WrongGoingTo.clear(); + } +} +truth wizautoplay::AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo) +{ + v2KeepGoingTo=v2KGTo; + + bool bOk=true; + + if(bOk){ + lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo); + if(!P->CanTheoreticallyMoveOn(lsqr)) + bOk=false; +// olterrain* olt = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo)->GetOLTerrain(); +// if(olt){ +// if(bOk && !CanMoveOn(olt)){ +// DBG4(DBGAV2(v2KeepGoingTo),"olterrain? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr()); +// bOk=false; +// } +// +// /**** +// * keep these commented for awhile, may be useful later +// * +// if(bOk && olt->IsWall()){ //TODO this may be unnecessary cuz of above test +// //TODO is this a bug in the CanMoveOn() code? navigation AI is disabled when player is ghost TODO confirm about ethereal state, ammy of phasing +// DBG4(DBGAV2(v2KeepGoingTo),"walls? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr()); +// bOk=false; +// } +// +// if(bOk && (olt->GetWalkability() & ETHEREAL)){ //TODO this may be too much unnecessary test +// bOk=false; +// } +// */ +// } + } + + if(bOk){ + P->SetGoingTo(v2KeepGoingTo); DBG3(DBGAV2(P->GetPos()),DBGAV2(P->GoingTo),DBGAV2(v2KeepGoingTo)); + P->CreateRoute(); + if(P->Route.empty()){ + P->TerminateGoingTo(); //redundant? + bOk=false; + } + } + + if(!bOk){ + DBG1("RouteCreationFailed"); + vv2FailTravelToTargets.push_back(v2KeepGoingTo); DBG3("BlockGoToDestination",DBGAV2(v2KeepGoingTo),vv2FailTravelToTargets.size()); + bAutoPlayUseRandomNavTargetOnce=true; + + AutoPlayAIReset(false); //v2KeepGoingTo is reset here too + } + + return bOk; +} + +void wizautoplay::AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex, bool bWide, bool bKeepColor) +{ + static v2 v2ScrPos=v2(0,0); //static to avoid instancing + static int iAddPos;iAddPos=bWide?2:1; + static int iSubBorder;iSubBorder=bWide?3:2; + if(game::OnScreen(v2SqrPos)){ + v2ScrPos=game::CalculateScreenCoordinates(v2SqrPos); + + DOUBLE_BUFFER->DrawRectangle( + v2ScrPos.X+iAddPos, v2ScrPos.Y+iAddPos, + v2ScrPos.X+TILE_SIZE-iSubBorder, v2ScrPos.Y+TILE_SIZE-iSubBorder, + color, bWide); + + if(iPrintIndex>-1) + FONT->Printf(DOUBLE_BUFFER, v2(v2ScrPos.X+1,v2ScrPos.Y+5), DARK_GRAY, "%d", iPrintIndex); + + if(!bKeepColor) + vv2DebugDrawSqrPrevious.push_back(v2SqrPos); + } +} + +const int iVisitAgainMax=10; +int iVisitAgainCount=iVisitAgainMax; +std::vector vv2AllDungeonSquares; +bool wizautoplay::AutoPlayAICheckAreaLevelChangedAndReset() +{ + static area* areaPrevious=NULL; + area* Area = game::GetCurrentArea(); + if(Area != areaPrevious){ + areaPrevious=Area; + + iVisitAgainCount=iVisitAgainMax; + + vv2DebugDrawSqrPrevious.clear(); + + vv2AllDungeonSquares.clear(); + if(!game::IsInWilderness()) + for(int iY=0;iYGetYSize();iY++){ for(int iX=0;iXGetXSize();iX++){ + vv2AllDungeonSquares.push_back(game::GetCurrentLevel()->GetLSquare(iX, iY)); + }} + + return true; + } + + return false; +} + +void wizautoplay::AutoPlayAIDebugDrawOverlay() +{ + if(!game::WizardModeIsActive())return; + + AutoPlayAICheckAreaLevelChangedAndReset(); + + // redraw previous to clean them + area* Area = game::GetCurrentArea(); //got the Area to draw in the wilderness too and TODO navigate there one day + std::vector vv2DebugDrawSqrPreviousCopy(vv2DebugDrawSqrPrevious); + for(int i=0;iGetSquare(vv2DebugDrawSqrPrevious[i])->SendNewDrawRequest(); +// square* sqr = Area->GetSquare(vv2DebugDrawSqrPrevious[i]); +// if(sqr)sqr->SendStrongNewDrawRequest(); //TODO sqr NULL? + AutoPlayAIDebugDrawSquareRect(vv2DebugDrawSqrPreviousCopy[i],DARK_GRAY); + } + + // draw new ones + vv2DebugDrawSqrPrevious.clear(); //empty before fillup below + + for(int i=0;iRoute.empty()) + for(int i=0;iRoute.size();i++) + AutoPlayAIDebugDrawSquareRect(PLAYER->Route[i],GREEN); + + if(!v2KeepGoingTo.Is0()) + AutoPlayAIDebugDrawSquareRect(v2KeepGoingTo,BLUE,PLAYER->Route.size(),true); + else if(iWanderTurns>0) + AutoPlayAIDebugDrawSquareRect(PLAYER->GetPos(),YELLOW,iWanderTurns); + + for(int i=0;iIsAppliable(P))return false; + if(it->IsZappable(P))return false; //not here, see zap section + if(dynamic_cast(it))return false; // too complex to make it auto work + if(dynamic_cast(it))return false; // too complex to make it auto work + return true; +} + +truth wizautoplay::AutoPlayAIDropThings() +{ +// level* lvl = game::GetCurrentLevel(); DBG1(lvl); +// area* Area = game::GetCurrentArea(); + + /** + * unburden + */ + bool bDropSomething = false; + static item* eqDropChk=NULL; + item* eqBroken=NULL; + for(int i=0;iGetEquipments();i++){ + eqDropChk=P->GetEquipment(i); + if(eqDropChk!=NULL && eqDropChk->IsBroken()){ DBG2("chkDropBroken",eqDropChk); + eqBroken=eqDropChk; + bDropSomething=true; + break; + } + } + + if(!bDropSomething && P->GetBurdenState() == STRESSED){ + if(clock()%100<5){ //5% chance to drop something weighty randomly every turn + bDropSomething=true; DBGLN; + } + } + + if(!bDropSomething && P->GetBurdenState() == OVER_LOADED){ + bDropSomething=true; + } + + if(bDropSomething){ DBG1("DropSomething"); + item* dropMe=NULL; + if(eqBroken!=NULL)dropMe=eqBroken; + + item* heaviest=NULL; + item* cheapest=NULL; + +// bool bFound=false; +// for(int k=0;k<2;k++){ +// if(dropMe!=NULL)break; +// static item* eqDropChk=NULL; +// for(int i=0;iIsBroken()){ +// dropMe=eqDropChk; +// break; +// } +// } + + if(dropMe==NULL){ + static itemvector vit;vit.clear();P->GetStack()->FillItemVector(vit); + for(int i=0;iGetName(DEFINITE).CStr(),vit[i]->GetTruePrice(),vit[i]->GetWeight()); + if(vit[i]->IsEncryptedScroll())continue; +// if(!bPlayerHasLantern && dynamic_cast(vit[i])!=NULL){ +// bPlayerHasLantern=true; //will keep only the 1st lantern +// continue; +// } + + if(vit[i]->IsBroken()){ //TODO use repair scroll? + dropMe=vit[i]; + break; + } + + if(heaviest==NULL)heaviest=vit[i]; + if(cheapest==NULL)cheapest=vit[i]; + +// switch(k){ +// case 0: //better not implement this as a user function as that will remove the doubt about items values what is another fun challenge :) + if(vit[i]->GetTruePrice() < cheapest->GetTruePrice()) //cheapest + cheapest=vit[i]; +// break; +// case 1: //this could be added as user function to avoid browsing the drop list, but may not be that good... + if(vit[i]->GetWeight() > heaviest->GetWeight()) //heaviest + heaviest=vit[i]; +// break; +// } + } + } + + if(heaviest!=NULL && cheapest!=NULL){ + if(dropMe==NULL && heaviest==cheapest) + dropMe=heaviest; + + if(dropMe==NULL && cheapest->GetTruePrice()<=iMaxValueless){ DBG2("DropValueless",cheapest->GetName(DEFINITE).CStr()); + dropMe=cheapest; + } + + if(dropMe==NULL){ + // the worst price VS weight will be dropped + float fC = cheapest ->GetTruePrice()/(float)cheapest ->GetWeight(); + float fW = heaviest->GetTruePrice()/(float)heaviest->GetWeight(); DBG3("PriceVsWeightRatio",fC,fW); + if(fC < fW){ + dropMe = cheapest; + }else{ + dropMe = heaviest; + } + } + + if(dropMe==NULL) + dropMe = clock()%2==0 ? heaviest : cheapest; + } + + // chose a throw direction + if(dropMe!=NULL){ + static std::vector vv2DirBase;static bool bDummyInit = [](){for(int i=0;i<8;i++)vv2DirBase.push_back(i);return true;}(); + std::vector vv2Dir(vv2DirBase); + int iDirOk=-1; + v2 v2DropAt(0,0); + lsquare* lsqrDropAt=NULL; + for(int i=0;i<8;i++){ + int k = clock()%vv2Dir.size(); //random chose from remaining TODO could be where there is NPC foe + int iDir = vv2Dir[k]; //collect direction value + vv2Dir.erase(vv2Dir.begin() + k); //remove using the chosen index to prepare next random choice + + v2 v2Dir = game::GetMoveVector(iDir); + v2 v2Chk = P->GetPos() + v2Dir; + if(game::GetCurrentLevel()->IsValidPos(v2Chk)){ + lsquare* lsqrChk=game::GetCurrentLevel()->GetLSquare(v2Chk); + if(lsqrChk->IsFlyable()){ + iDirOk = iDir; + v2DropAt = v2Chk; + lsqrDropAt=lsqrChk; + break; + } + } + };DBGLN; + + if(iDirOk==-1){iDirOk=clock()%8;DBG2("RandomDir",iDirOk);}DBGLN; //TODO should just drop may be? unless hitting w/e is there could help + + bool bApplyDropped=false; //or vanished + if(AutoPlayAIcanApply(dropMe) && dropMe->Apply(P)){ + static itemvector ivChkDrop;ivChkDrop.clear(); + P->GetStack()->FillItemVector(ivChkDrop); + bApplyDropped=true; + for(int i6=0;i6-1){DBG2("KickOrThrow",iDirOk); + static itemcontainer* itc;itc = dynamic_cast(dropMe);DBGLN; + static humanoid* h;h = dynamic_cast(P);DBGLN; + DBG8("CanKickLockedChest",lsqrDropAt,itc,itc?itc->IsLocked():-1,P->CanKick(),h,h?h->GetLeftLeg():0,h?h->GetRightLeg():0); + if(lsqrDropAt && itc && itc->IsLocked() && P->CanKick() && h && h->GetLeftLeg() && h->GetRightLeg()){DBGLN; + dropMe->MoveTo(lsqrDropAt->GetStack());DBGLN; //drop in front.. + P->Kick(lsqrDropAt,iDirOk,true);DBGLN; // ..to kick it + }else{DBGLN; + P->ThrowItem(iDirOk, dropMe); DBG5("DropThrow",iDirOk,dropMe->GetName(DEFINITE).CStr(),dropMe->GetTruePrice(),dropMe->GetWeight()); + } + }else{DBGLN; + dropMe->MoveTo(P->GetLSquareUnder()->GetStack());DBGLN; //just drop + } + + v2LastDropPlayerWasAt=P->GetPos();DBGSV2(v2LastDropPlayerWasAt); + } + + return true; + } + + DBG1("AutoPlayNeedsImprovement:DropItem"); + ADD_MESSAGE("%s says \"I need more intelligence to drop trash...\"", P->GetName(DEFINITE).CStr()); // improve the dropping AI + //TODO stop autoplay mode? if not, something random may happen some time and wont reach here ex.: spoil, fire, etc.. + } + + return false; +} + +bool wizautoplay::IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern) +{ + if(!it->CanBeSeenBy(P))return false; + + P->ValidateTrapData(); + if(!it->IsPickable(P))return false; + + if(it->GetSquaresUnder()!=1)return false; //avoid big corpses 2x2 + + if(!bPlayerHasLantern && (it->IsOnFire(P) || it->GetEmitation()>0)){ + //pickup priority + }else{ + if(it->IsBroken())return false; + if(it->GetTruePrice()<=iMaxValueless)return false; //mainly to avoid all rocks from broken walls + if(clock()%3!=0 && it->GetSpoilLevel()>0)return false; //some spoiled may be consumed to randomly test diseases flows + } + + return true; +} + +truth wizautoplay::AutoPlayAIEquipAndPickup(bool bPlayerHasLantern) +{ + static humanoid* h;h = dynamic_cast(P); + if(h==NULL)return false; + // other invalid equippers + if(dynamic_cast(P) != NULL)return false; + if(dynamic_cast(P) != NULL)return false; + + if(AutoPlayAIequipConsumeZapReadApply()) + return true; + + if(P->GetBurdenState()!=OVER_LOADED){ DBG4(P->CommandFlags&DONT_CHANGE_EQUIPMENT,P,P->GetNameSingular().CStr(),P->GetSquareUnder()); + if(v2LastDropPlayerWasAt!=P->GetPos()){ + static bool bHoarder=true; //TODO wizard autoplay AI config exclusive felist + + if(P->CheckForUsefulItemsOnGround(false)) + if(!bHoarder) + return true; + + //just pick up any useful stuff + static itemvector vit;vit.clear();P->GetStackUnder()->FillItemVector(vit); + for(uint c = 0; c < vit.size(); ++c){ + if(!IsAutoplayAICanPickup(vit[c],bPlayerHasLantern))continue; + + static itemcontainer* itc;itc = dynamic_cast(vit[c]); + if(itc && !itc->IsLocked()){ //get items from unlocked container + static itemvector vitItc;vitItc.clear();itc->GetContained()->FillItemVector(vitItc); + for(uint d = 0; d < vitItc.size(); ++d) + vitItc[d]->MoveTo(itc->GetLSquareUnder()->GetStack()); + continue; + } + + vit[c]->MoveTo(P->GetStack()); DBG2("pickup",vit[c]->GetNameSingular().CStr()); +// if(GetBurdenState()==OVER_LOADED)ThrowItem(clock()%8,ItemVector[c]); +// return true; + if(!bHoarder) + return true; + } + } + } + + return false; +} + +static const int iMoreThanMaxDist=10000000; //TODO should be max integer but this will do for now in 2018 :) +truth wizautoplay::AutoPlayAITestValidPathTo(v2 v2To) +{ + return AutoPlayAIFindWalkDist(v2To) < iMoreThanMaxDist; +} + +int wizautoplay::AutoPlayAIFindWalkDist(v2 v2To) +{ + static bool bUseSimpleDirectDist=false; //very bad navigation this is + if(bUseSimpleDirectDist)return (v2To - P->GetPos()).GetLengthSquare(); + + static v2 GoingToBkp;GoingToBkp = P->GoingTo; //IsGoingSomeWhere() ? GoingTo : v2(0,0); + + P->SetGoingTo(v2To); + P->CreateRoute(); + static int iDist;iDist=P->Route.size(); + P->TerminateGoingTo(); + + if(GoingToBkp!=ERROR_V2){ DBG2("Warning:WrongUsage:ShouldBeGoingNoWhere",DBGAV2(GoingToBkp)); + P->SetGoingTo(GoingToBkp); + P->CreateRoute(); + } + + return iDist>0?iDist:iMoreThanMaxDist; +} + +const int iDesperateResetCountDownDefault=5; +const int iDesperateEarthQuakeCountDownDefault=iDesperateResetCountDownDefault*2; +const int iAutoPlayAIResetCountDownDefault = iDesperateEarthQuakeCountDownDefault*2; +int iAutoPlayAIResetCountDown = iAutoPlayAIResetCountDownDefault; +truth wizautoplay::AutoPlayAINavigateDungeon(bool bPlayerHasLantern) +{ + /** + * navigate the unknown dungeon + */ + festring fsDL;fsDL<GetDungeon()->GetIndex()<GetLevel()->GetIndex(); + festring fsStayOnDL;{const char* pc = std::getenv("IVAN_DebugStayOnDungeonLevel");if(pc!=NULL)fsStayOnDL< v2Exits; + std::vector v2Altars; + if(v2KeepGoingTo.Is0()){ DBG1("TryNewMoveTarget"); + // target undiscovered squares to explore + v2 v2PreferedTarget(0,0); + + int iNearestLanterOnFloorDist = iMoreThanMaxDist; + v2 v2PreferedLanternOnFloorTarget(0,0); + + v2 v2NearestUndiscovered(0,0); + int iNearestUndiscoveredDist=iMoreThanMaxDist; + std::vector vv2UndiscoveredValidPathSquares; + + lsquare* lsqrNearestSquareWithWallLantern=NULL; + lsquare* lsqrNearestDropWallLanternAt=NULL; + stack* stkNearestDropWallLanternAt = NULL; + int iNearestSquareWithWallLanternDist=iMoreThanMaxDist; + item* itNearestWallLantern=NULL; + + /*************************************************************** + * scan whole dungeon squares + */ + for(int iY=0;iYGetYSize();iY++){ for(int iX=0;iXGetXSize();iX++){ + lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(iX,iY); + + olterrain* olt = lsqr->GetOLTerrain(); + if(olt && (olt->GetConfig() == STAIRS_UP || olt->GetConfig() == STAIRS_DOWN)){ + if(fsDL!=fsStayOnDL){ + v2Exits.push_back(v2(lsqr->GetPos())); + DBGSV2(v2Exits[v2Exits.size()-1]); + } + } + + altar* Altar = dynamic_cast(olt); + if(olt && Altar){ + if(!Altar->GetMasterGod()->IsKnown()) + v2Altars.push_back(v2(lsqr->GetPos())); + } + + stack* stkSqr = lsqr->GetStack(); + static itemvector vit;vit.clear();stkSqr->FillItemVector(vit); + bool bAddValidTargetSquare=true; + + // find nearest wall lantern + if(!bPlayerHasLantern && olt && olt->IsWall()){ + for(int n=0;nIsLanternOnWall() && !vit[n]->IsBroken()){ + static stack* stkDropWallLanternAt;stkDropWallLanternAt = lsqr->GetStackOfAdjacentSquare(vit[n]->GetSquarePosition()); + static lsquare* lsqrDropWallLanternAt;lsqrDropWallLanternAt = + stkDropWallLanternAt?stkDropWallLanternAt->GetLSquareUnder():NULL; + + if(stkDropWallLanternAt && lsqrDropWallLanternAt && P->CanTheoreticallyMoveOn(lsqrDropWallLanternAt)){ + int iDist = AutoPlayAIFindWalkDist(lsqrDropWallLanternAt->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); + if(lsqrNearestSquareWithWallLantern==NULL || iDistGetPos()),DBGAV2(P->GetPos())); + lsqrNearestDropWallLanternAt=lsqrDropWallLanternAt; + stkNearestDropWallLanternAt=stkDropWallLanternAt; + } + } + + break; + } + } + } + + if(bAddValidTargetSquare && !P->CanTheoreticallyMoveOn(lsqr)) + bAddValidTargetSquare=false; + + bool bIsFailToTravelSquare=false; + if(bAddValidTargetSquare){ + for(int j=0;jGetPos()){ + bAddValidTargetSquare=false; + bIsFailToTravelSquare=true; + break; + } + } + + if(!bIsFailToTravelSquare){ + +// if(bAddValidTargetSquare && v2PreferedTarget.Is0() && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){ + if(bAddValidTargetSquare && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){ + bool bVisitAgain=false; + if(iVisitAgainCount>0 || !bPlayerHasLantern){ + if(stkSqr!=NULL && stkSqr->GetItems()>0){ + for(int n=0;nGetID());DBG1(vit[n]->GetType());DBG3("VisitAgain:ChkItem",vit[n]->GetNameSingular().CStr(),vit.size()); + if(vit[n]->IsBroken())continue; DBGLN; + + static bool bIsLanternOnFloor;bIsLanternOnFloor = dynamic_cast(vit[n])!=NULL;// || vit[n]->IsOnFire(P); DBGLN; + + if( // if is useful to the AutoPlay AI endless tests + vit[n]->IsShield (P) || + vit[n]->IsWeapon (P) || + vit[n]->IsArmor (P) || + vit[n]->IsAmulet (P) || + vit[n]->IsZappable(P) || //wands + vit[n]->IsAppliable(P) || //mines, beartraps etc + vit[n]->IsRing (P) || + vit[n]->IsReadable(P) || //books and scrolls + vit[n]->IsDrinkable(P)|| //potions and vials + bIsLanternOnFloor + ) + if(IsAutoplayAICanPickup(vit[n],bPlayerHasLantern)) + { + bVisitAgain=true; + + if(bIsLanternOnFloor && !bPlayerHasLantern){ + static int iDist;iDist = AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); + if(iDistGetPos(); DBG2("PreferLanternAt",DBGAV2(lsqr->GetPos())) + } + }else{ + iVisitAgainCount--; + } + + DBG4(bVisitAgain,DBGAV2(lsqr->GetPos()),iVisitAgainCount,bIsLanternOnFloor); + break; + } + } + } + } + + if(!bVisitAgain)bAddValidTargetSquare=false; + } + + } + + if(bAddValidTargetSquare) + if(!P->CanTheoreticallyMoveOn(lsqr)) //if(olt && !CanMoveOn(olt)) + bAddValidTargetSquare=false; + + if(bAddValidTargetSquare){ DBG2("addValidSqr",DBGAV2(lsqr->GetPos())); + static int iDist;iDist=AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare(); + + if(iDistGetPos()); + + if(iDistGetPos(); DBG3(iNearestUndiscoveredDist,DBGAV2(lsqr->GetPos()),DBGAV2(P->GetPos())); + } + } + }} DBG2(DBGAV2(v2PreferedTarget),vv2UndiscoveredValidPathSquares.size()); + + /*************************************************************** + * define prefered navigation target + */ + if(!bPlayerHasLantern && v2PreferedTarget.Is0()){ + bool bUseWallLantern=false; + if(!v2PreferedLanternOnFloorTarget.Is0() && lsqrNearestSquareWithWallLantern!=NULL){ + if(iNearestLanterOnFloorDist <= iNearestSquareWithWallLanternDist){ + v2PreferedTarget=v2PreferedLanternOnFloorTarget; + }else{ + bUseWallLantern=true; + } + }else if(!v2PreferedLanternOnFloorTarget.Is0()){ + v2PreferedTarget=v2PreferedLanternOnFloorTarget; + }else if(lsqrNearestSquareWithWallLantern!=NULL){ + bUseWallLantern=true; + } + + if(bUseWallLantern){ + /** + * target to nearest wall lantern + * check for lanterns on walls of adjacent squares if none found on floors + */ + itNearestWallLantern->MoveTo(stkNearestDropWallLanternAt); // the AI is prepared to get things from the floor only so "magically" drop it :) + v2PreferedTarget = lsqrNearestDropWallLanternAt->GetPos(); DBG2("PreferWallLanternAt",DBGAV2(lsqrNearestDropWallLanternAt->GetPos())) + } + + } + + /*************************************************************** + * validate and set new navigation target + */ +// DBG9("AllNavigatePossibilities",DBGAV2(v2PreferedTarget),DBGAV2(v2PreferedLanternOnFloorTarget),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2()); + v2 v2NewKGTo=v2(0,0); + + if(v2NewKGTo.Is0()){ + //TODO if(!v2PreferedTarget.Is0){ // how can this not be compiled? error: cannot convert ‘v2::Is0’ from type ‘truth (v2::)() const {aka bool (v2::)() const}’ to type ‘bool’ + if(v2PreferedTarget.GetLengthSquare()>0) + if(AutoPlayAITestValidPathTo(v2PreferedTarget)) + v2NewKGTo=v2PreferedTarget; DBGSV2(v2PreferedTarget); + } + + if(v2NewKGTo.Is0()){ + if(bAutoPlayUseRandomNavTargetOnce){ //these targets were already path validated and are safe to use! + v2NewKGTo=vv2UndiscoveredValidPathSquares[clock()%vv2UndiscoveredValidPathSquares.size()]; DBG2("RandomTarget",DBGAV2(v2NewKGTo)); + bAutoPlayUseRandomNavTargetOnce=false; + }else{ //find nearest + if(!v2NearestUndiscovered.Is0()){ + v2NewKGTo=v2NearestUndiscovered; DBGSV2(v2NearestUndiscovered); + } + } + } + + if(v2NewKGTo.Is0()){ //no new destination: fully explored + if(v2Altars.size()>0){ + for(int i=0;i0){ + if(game::GetCurrentDungeonTurnsCount()==0){ DBG1("Dungeon:FullyExplored:FirstTurn"); + iWanderTurns=100+clock()%300; DBG2("WanderALotOnFullyExploredLevel",iWanderTurns); //just move around a lot, some NPC may spawn + }else{ + // travel between dungeons if current fully explored + v2 v2Try; + for(int i=0;i<10;i++){ + v2Try = v2Exits[clock()%v2Exits.size()]; + if(v2Try!=P->GetPos())break; + } + if(AutoPlayAITestValidPathTo(v2Try) || iAutoPlayAIResetCountDown==0) + v2NewKGTo = v2TravelingToAnotherDungeon = v2Try; DBGSV2(v2TravelingToAnotherDungeon); + } + }else{ + DBG1("AutoPlayNeedsImprovement:Navigation") + ADD_MESSAGE("%s says \"I need more intelligence to move around...\"", P->GetName(DEFINITE).CStr()); // improve the dropping AI + //TODO stop autoplay mode? + } + } + + if(v2NewKGTo.Is0()){ DBG1("Desperately:TryAnyRandomTargetNavWithValidPath"); + std::vector vlsqrChk(vv2AllDungeonSquares); + + while(vlsqrChk.size()>0){ + static int i;i=clock()%vlsqrChk.size(); + static v2 v2Chk; v2Chk = vlsqrChk[i]->GetPos(); + + if(!AutoPlayAITestValidPathTo(v2Chk)){ + vlsqrChk.erase(vlsqrChk.begin()+i); + }else{ + v2NewKGTo=v2Chk; + break; + } + } + } + + if(!v2NewKGTo.Is0()){ + AutoPlayAISetAndValidateKeepGoingTo(v2NewKGTo); + }else{ + DBG1("TODO:too complex paths are failing... improve CreateRoute()?"); + } + } + + if(!v2KeepGoingTo.Is0()){ + if(v2KeepGoingTo==P->GetPos()){ DBG3("ReachedDestination",DBGAV2(v2KeepGoingTo),DBGAV2(P->GoingTo)); + //wander a bit before following new target destination + iWanderTurns=(clock()%iMaxWanderTurns)+iMinWanderTurns; DBG2("WanderAroundAtReachedDestination",iWanderTurns); + +// v2KeepGoingTo=v2(0,0); +// TerminateGoingTo(); + AutoPlayAIReset(false); + return true; + } + +// CheckForUsefulItemsOnGround(false); DBGSV2(GoingTo); +// CheckForEnemies(false, true, false, false); DBGSV2(GoingTo); + +// if(!IsGoingSomeWhere() || v2KeepGoingTo!=GoingTo){ DBG3("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo)); +// SetGoingTo(v2KeepGoingTo); +// } + static int iForceGoingToCountDown=10; + static v2 v2GoingToBkp;v2GoingToBkp=P->GoingTo; + if(!v2KeepGoingTo.IsAdjacent(P->GoingTo)){ + if(iForceGoingToCountDown==0){ + DBG4("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(P->GoingTo),DBGAV2(P->GetPos())); + + if(!AutoPlayAISetAndValidateKeepGoingTo(v2KeepGoingTo)){ + static int iSetFailTeleportCountDown=10; + iSetFailTeleportCountDown--; + vv2WrongGoingTo.push_back(v2GoingToBkp); + if(iSetFailTeleportCountDown==0){ + AutoPlayAITeleport(false); + AutoPlayAIReset(true); //refresh to test/try it all again + iSetFailTeleportCountDown=10; + } + } + DBGSV2(P->GoingTo); + return true; + }else{ + iForceGoingToCountDown--; DBG1(iForceGoingToCountDown); + } + }else{ + iForceGoingToCountDown=10; + } + + /** + * Determinedly blindly moves towards target, the goal is to Navigate! + * + * this has several possible status if returning false... + * so better do not decide anything based on it? + */ + P->MoveTowardsTarget(false); + +// if(!MoveTowardsTarget(false)){ DBG3("OrFailedGoingTo,OrReachedDestination...",DBGAV2(GoingTo),DBGAV2(GetPos())); // MoveTowardsTarget may break the GoingTo EVEN if it succeeds????? +// TerminateGoingTo(); +// v2KeepGoingTo=v2(0,0); //reset only this one to try again +// GetAICommand(); //wander once for randomicity +// } + + return true; + } + + return false; +} + +bool wizautoplay::AutoPlayAIChkInconsistency() +{ + if(P->GetSquareUnder()==NULL){ + DBG9(P,P->GetNameSingular().CStr(),P->IsPolymorphed(),P->IsHuman(),P->IsHumanoid(),P->IsPolymorphable(),P->IsPlayerKind(),P->IsTemporary(),P->IsPet()); + DBG6("GetSquareUnderIsNULLhow?",P->IsHeadless(),P->IsPlayer(),wizautoplay::GetAutoPlayMode(),IsPlayerAutoPlay(),P->GetName(DEFINITE).CStr()); + return true; //to just ignore this turn expecting on next it will be ok. + } + return false; +} + +truth wizautoplay::AutoPlayAIPray() +{ + bool bSPO = bSafePrayOnce; + bSafePrayOnce=false; + + if(bSPO){} + else if(P->StateIsActivated(PANIC) && clock()%10==0){ + iWanderTurns=1; DBG1("Wandering:InPanic"); // to regain control as soon it is a ghost anymore as it can break navigation when inside walls + }else return false; + + // check for known gods + int aiKGods[GODS]; + int iKGTot=0; + int aiKGodsP[GODS]; + int iKGTotP=0; + static int iPleased=50; //see god::PrintRelation() + for(int c = 1; c <= GODS; ++c){ + if(!game::GetGod(c)->IsKnown())continue; + // even known, praying to these extreme ones will be messy if Relation<1000 + if(dynamic_cast(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue; + if(dynamic_cast(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue; + + aiKGods[iKGTot++]=c; + + if(game::GetGod(c)->GetRelation() > iPleased){ +// //TODO could this help? +// switch(game::GetGod(c)->GetBasicAlignment()){ //game::GetGod(c)->GetAlignment(); +// case GOOD: +// if(game::GetPlayerAlignment()>=2){}else continue; +// break; +// case NEUTRAL: +// if(game::GetPlayerAlignment()<2 && game::GetPlayerAlignment()>-2){}else continue; +// break; +// case EVIL: +// if(game::GetPlayerAlignment()<=-2){}else continue; +// break; +// } + aiKGodsP[iKGTotP++] = c; + } + } + if(iKGTot==0){ + if(clock()%10==0) + for(int c = 1; c <= GODS; ++c) + game::LearnAbout(game::GetGod(c)); + return false; + } +// if(bSPO && iKGTotP==0)return false; + + // chose and pray to one god + god* g = NULL; + if(iKGTotP>0 && (bSPO || clock()%10!=0)) + g = game::GetGod(aiKGodsP[clock()%iKGTotP]); //only good effect + else + g = game::GetGod(aiKGods[clock()%iKGTot]); //can have bad effect too + + if(bSPO || clock()%10!=0){ //it may not recover some times to let pray unsafely + int iRecover=0; + if(iKGTotP==0){ + if(iRecover==0 && g->GetRelation()==-1000)iRecover=1000; //to test all relation range + if(iRecover==0 && g->GetRelation() <= iPleased)iRecover=iPleased; //to alternate tests on many with low good relation + } + if(iRecover>0) + g->SetRelation(iRecover); + + g->AdjustTimer(-1000000000); //TODO filter gods using timer too instead of this reset? + } + + g->Pray(); DBG2("PrayingTo",g->GetName()); + + return true; +} + +truth wizautoplay::AutoPlayAICommand(int& rKey) +{ + DBGLN;if(AutoPlayAIChkInconsistency())return true; + DBGSV2(P->GetPos()); + + if(AutoPlayLastChar!=P){ + AutoPlayAIReset(true); + AutoPlayLastChar=P; + } + + DBGLN;if(AutoPlayAIChkInconsistency())return true; + if(AutoPlayAICheckAreaLevelChangedAndReset()) + AutoPlayAIReset(true); + + static bool bDummy_initDbg = [](){game::AddDebugDrawOverlayFunction(&AutoPlayAIDebugDrawOverlay);return true;}(); + + truth bPlayerHasLantern=false; + static itemvector vit;vit.clear();P->GetStack()->FillItemVector(vit); + for(uint i=0;i(vit[i])!=NULL || vit[i]->IsOnFire(P) || vit[i]->GetEmitation()>0){ + bPlayerHasLantern=true; //will keep only the 1st lantern + break; + } + } + + DBGLN;if(AutoPlayAIChkInconsistency())return true; + AutoPlayAIPray(); + + //TODO this doesnt work??? -> if(IsPolymorphed()){ //to avoid some issues TODO but could just check if is a ghost +// if(dynamic_cast(P) == NULL){ //this avoid some issues TODO but could just check if is a ghost +// if(StateIsActivated(ETHEREAL_MOVING)){ //this avoid many issues + static bool bPreviousTurnWasGhost=false; + if(dynamic_cast(P) != NULL){ DBG1("Wandering:Ghost"); //this avoid many issues mainly related to navigation + iWanderTurns=1; // to regain control as soon it is a ghost anymore as it can break navigation when inside walls + bPreviousTurnWasGhost=true; + }else{ + if(bPreviousTurnWasGhost){ + AutoPlayAIReset(true); //this may help on navigation + bPreviousTurnWasGhost=false; + return true; + } + } + + DBGLN;if(AutoPlayAIChkInconsistency())return true; + if(AutoPlayAIDropThings()) + return true; + + DBGLN;if(AutoPlayAIChkInconsistency())return true; + if(AutoPlayAIEquipAndPickup(bPlayerHasLantern)) + return true; + + if(iWanderTurns>0){ + if(!P->IsPlayer() || wizautoplay::GetAutoPlayMode()==AUTOPLAYMODE_DISABLED || !IsPlayerAutoPlay()){ //redundancy: yep + DBG9(P,P->GetNameSingular().CStr(),P->IsPolymorphed(),P->IsHuman(),P->IsHumanoid(),P->IsPolymorphable(),P->IsPlayerKind(),P->IsTemporary(),P->IsPet()); + DBG5(P->IsHeadless(),P->IsPlayer(),wizautoplay::GetAutoPlayMode(),IsPlayerAutoPlay(),P->GetName(DEFINITE).CStr()); + ABORT("autoplay is inconsistent %d %d %d %d %d %s %d %s %d %d %d %d %d", + P->IsPolymorphed(),P->IsHuman(),P->IsHumanoid(),P->IsPolymorphable(),P->IsPlayerKind(), + P->GetNameSingular().CStr(),wizautoplay::GetAutoPlayMode(),P->GetName(DEFINITE).CStr(), + P->IsTemporary(),P->IsPet(),P->IsHeadless(),P->IsPlayer(),IsPlayerAutoPlay()); + } + P->GetAICommand(); DBG2("Wandering",iWanderTurns); //fallback to default TODO never reached? + iWanderTurns--; + return true; + } + + /*************************************************************************************************** + * WANDER above here + * NAVIGATE below here + ***************************************************************************************************/ + + /** + * travel between dungeons + */ + bool bTBD = false; + if(!v2TravelingToAnotherDungeon.Is0()){ + if(P->GetPos() == v2TravelingToAnotherDungeon) + bTBD=true; + else + if(iAutoPlayAIResetCountDown==0){ + P->Move(v2TravelingToAnotherDungeon,true); + iAutoPlayAIResetCountDown=iAutoPlayAIResetCountDownDefault; + bTBD=true; + } + } + if(bTBD){ + bool bTravel=false; + lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2TravelingToAnotherDungeon); +// square* sqr = Area->GetSquare(v2TravelingToAnotherDungeon); + olterrain* ot = lsqr->GetOLTerrain(); +// oterrain* ot = sqr->GetOTerrain(); + if(ot){ + if(ot->GetConfig() == STAIRS_UP){ + rKey='<'; + bTravel=true; + } + + if(ot->GetConfig() == STAIRS_DOWN){ + rKey='>'; + bTravel=true; + } + } + + if(bTravel){ DBG3("travel",DBGAV2(v2TravelingToAnotherDungeon),rKey); + AutoPlayAIReset(true); + return false; //so the new/changed key will be used as command, otherwise it would be ignored + } + } + + static int iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; + if(AutoPlayAINavigateDungeon(bPlayerHasLantern)){ + iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; + return true; + }else{ + if(iDesperateEarthQuakeCountDown==0){ + iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault; + /** + * this changes the dungeon level paths, + * so applying pickaxe or using fireballs etc are not required! + */ + scrollofearthquake::Spawn()->FinishReading(P); + DBG1("UsingTerribleEarthquakeSolution"); // xD + }else{ + iDesperateEarthQuakeCountDown--; + DBG1(iDesperateEarthQuakeCountDown); + } + } + + /**************************************** + * Twighlight zone + */ + + ADD_MESSAGE("%s says \"I need more intelligence to do things by myself...\"", P->GetName(DEFINITE).CStr()); DBG1("TODO: AI needs improvement"); + + static int iDesperateResetCountDown=iDesperateResetCountDownDefault; + if(iDesperateResetCountDown==0){ + iDesperateResetCountDown=iDesperateResetCountDownDefault; + + AutoPlayAIReset(true); + iAutoPlayAIResetCountDown--; + + // AFTER THE RESET!!! + iWanderTurns=iMaxWanderTurns; DBG2("DesperateResetToSeeIfAIWorksAgain",iWanderTurns); + }else{ + P->GetAICommand(); DBG2("WanderingDesperatelyNotKnowingWhatToDo",iDesperateResetCountDown); // :) + iDesperateResetCountDown--; + } + + return true; +} + +truth wizautoplay::IsPlayerAutoPlay(character* C) +{ + return C->IsPlayer() && wizautoplay::GetAutoPlayMode()>0; +} + +truth wizautoplay::AutoPlayAIequipConsumeZapReadApply() +{ + humanoid* H = dynamic_cast(P); + if(!H) + return false; + + bool bDidSomething=false; + + ///////////////////////////////// WIELD + + item* iL = H->GetEquipment(LEFT_WIELDED_INDEX); + item* iR = H->GetEquipment(RIGHT_WIELDED_INDEX); + + //every X turns remove all equipments + bool bTryWieldNow=false; + static int iLastReEquipAllTurn=-1; + if(game::GetTurn()>(iLastReEquipAllTurn+100)){ DBG2(game::GetTurn(),iLastReEquipAllTurn); + iLastReEquipAllTurn=game::GetTurn(); + DBG1("UnequipAll"); + for(int i=0;iGetEquipment(i); + if(eq){eq->MoveTo(H->GetStack());H->SetEquipment(i,NULL);} //eq is moved to end of stack! + if(iL==eq)iL=NULL; + if(iR==eq)iR=NULL; + } +// if(iL!=NULL){iL->MoveTo(GetStack());iL=NULL;SetEquipment(LEFT_WIELDED_INDEX ,NULL);} +// if(iR!=NULL){iR->MoveTo(GetStack());iR=NULL;SetEquipment(RIGHT_WIELDED_INDEX,NULL);} + bTryWieldNow=true; + } + + //wield some weapon from the inventory as the NPC AI is not working for the player TODO why? + //every X turns try to wield + static int iLastTryToWieldTurn=-1; + if(bTryWieldNow || game::GetTurn()>(iLastTryToWieldTurn+10)){ DBG2(game::GetTurn(),iLastTryToWieldTurn); + iLastTryToWieldTurn=game::GetTurn(); + bool bDoneLR=false; + bool bL2H = iL && iL->IsTwoHanded(); + bool bR2H = iR && iR->IsTwoHanded(); + + //2handed + static int iTryWieldWhat=0; iTryWieldWhat++; DBG1(iTryWieldWhat); + if(iTryWieldWhat%2==0){ //will try 2handed first, alternating. If player has only 2handeds, the 1handeds will not be wielded and it will use punches, what is good too for tests. + if( !bDoneLR && + iL==NULL && H->GetBodyPartOfEquipment(LEFT_WIELDED_INDEX )!=NULL && + iR==NULL && H->GetBodyPartOfEquipment(RIGHT_WIELDED_INDEX)!=NULL + ){ + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(vitEqW[c]->IsWeapon(P) && vitEqW[c]->IsTwoHanded()){ DBG1(vitEqW[c]->GetNameSingular().CStr()); + vitEqW[c]->RemoveFromSlot(); + DBG2("Wield2hd",vitEqW[c]->GetNameSingular().CStr()); + H->SetEquipment(clock()%2==0 ? LEFT_WIELDED_INDEX : RIGHT_WIELDED_INDEX, vitEqW[c]); //DBG3("Wield",iEqIndex,vitEqW[c]->GetName(DEFINITE).CStr()); + bDoneLR=true; + break; + } + } + } + } + + //dual 1handed (if not 2hd already) + if(!bDoneLR){ + for(int i=0;i<2;i++){ + int iChk=-1; + if(i==0)iChk=LEFT_WIELDED_INDEX; + if(i==1)iChk=RIGHT_WIELDED_INDEX; + + if( + !bDoneLR && + ( + (iChk==LEFT_WIELDED_INDEX && iL==NULL && H->GetBodyPartOfEquipment(LEFT_WIELDED_INDEX ) && !bR2H) + || + (iChk==RIGHT_WIELDED_INDEX && iR==NULL && H->GetBodyPartOfEquipment(RIGHT_WIELDED_INDEX) && !bL2H) + ) + ){ + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if( + (vitEqW[c]->IsWeapon(P) && !vitEqW[c]->IsTwoHanded()) + || + vitEqW[c]->IsShield(P) + ){ + DBG2("WieldDual",vitEqW[c]->GetNameSingular().CStr()); + vitEqW[c]->RemoveFromSlot(); + H->SetEquipment(iChk, vitEqW[c]); + bDoneLR=true; + break; + } + } + } + } + } + + } + + //every X turns try to use stuff from inv + static int iLastTryToUseInvTurn=-1; + if(game::GetTurn()>(iLastTryToUseInvTurn+5)){ + DBG2(game::GetTurn(),iLastTryToUseInvTurn); + iLastTryToUseInvTurn=game::GetTurn(); + + //////////////////////////////// consume food/drink + { //TODO let this happen for non-human too? + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(clock()%3!=0 && H->GetHungerState() >= BLOATED)break; //randomly let it vomit and activate all related flows *eew* xD + + //if(TryToConsume(vitEqW[c])) + material* ConsumeMaterial = vitEqW[c]->GetConsumeMaterial(P); + if( + ConsumeMaterial!=NULL && + vitEqW[c]->IsConsumable() && + !H->HasHadBodyPart(vitEqW[c]) && //this avoids a slow interactive question + H->ConsumeItem(vitEqW[c], vitEqW[c]->GetConsumeMaterial(P)->GetConsumeVerb()) + ){ + DBG2("AutoPlayConsumed",vitEqW[c]->GetNameSingular().CStr()); + bDidSomething=true; + break; + } + } + } + + //////////////////////////////// equip armor ring amulet etc + { + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(H->TryToEquip(vitEqW[c],true)){ + DBG2("EquipItem",vitEqW[c]->GetNameSingular().CStr()); + bDidSomething=true; + break; + }else{ + vitEqW[c]->MoveTo(H->GetStack()); //was dropped, get back, will be in the end of the stack! :) + } + } + } + + //////////////////////////////// zap + static int iLastZapTurn=-1; + if(game::GetTurn()>(iLastZapTurn+30)){ + DBG2(game::GetTurn(),iLastZapTurn); //every X turns try to use stuff from inv + iLastZapTurn=game::GetTurn(); + + int iDir=clock()%(8+1); // index 8 is the macro YOURSELF already... if(iDir==8)iDir=YOURSELF; + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(!vitEqW[c]->IsZappable(P))continue; + + if(vitEqW[c]->IsZapWorthy(P)){ + if(vitEqW[c]->Zap(P, H->GetPos(), iDir)){ + DBG2(iLastZapTurn,vitEqW[c]->GetNameSingular().CStr()); //TODO try to aim at NPCs + bDidSomething=true; + break; + } + }else{ + if(vitEqW[c]->Apply(P)){ + DBG2(iLastZapTurn,vitEqW[c]->GetNameSingular().CStr()); + bDidSomething=true; + break; + } + } + } + } + + //////////////////////////////// read books and scrolls + static int iLastReadTurn=-1; + if(game::GetTurn()>(iLastReadTurn+15)){ DBG2(game::GetTurn(),iLastReadTurn); //every X turns try to use stuff from inv + iLastReadTurn=game::GetTurn(); + + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(!vitEqW[c]->IsReadable(P))continue; + + static holybook* hb;hb = dynamic_cast(vitEqW[c]); + if(hb){ + if(vitEqW[c]->Read(P)){ DBG1(vitEqW[c]->GetNameSingular().CStr()); //TODO try to aim at NPCs + DBG2(iLastReadTurn,vitEqW[c]->GetNameSingular().CStr()); + bDidSomething=true; + break; + } + } + + static scroll* Scroll; Scroll = dynamic_cast(vitEqW[c]); + if( //some are simple (just read to work imediately) + dynamic_cast(Scroll) || + dynamic_cast(Scroll) || + dynamic_cast(Scroll) || + false //dummy + ){ + DBG2(iLastReadTurn,vitEqW[c]->GetNameSingular().CStr()); + Scroll->Read(P); + bDidSomething=true; + break; + } + } + } + + //////////////////////////////// apply things + static int iLastApplyTurn=-1; + if(game::GetTurn()>(iLastApplyTurn+40)){ + DBG2(game::GetTurn(),iLastApplyTurn); //every X turns try to use stuff from inv + iLastApplyTurn=game::GetTurn(); + + static itemvector vitEqW;vitEqW.clear();H->GetStack()->FillItemVector(vitEqW); + static itemvector vitA;vitA.clear(); + if(H->GetLeftWielded())vitEqW.push_back(H->GetLeftWielded()); + if(H->GetRightWielded())vitEqW.push_back(H->GetRightWielded()); + for(uint c = 0; c < vitEqW.size(); ++c){ + if(AutoPlayAIcanApply(vitEqW[c])) + vitA.push_back(vitEqW[c]); + } + + if(vitA.size()){ + item* itA = vitA[clock()%vitA.size()]; + DBG2(iLastApplyTurn,itA->GetNameSingular().CStr()); + itA->Apply(P); + bDidSomething=true; + } + } + + } + + return bDidSomething; +} + +void wizautoplay::IncAutoPlayMode() { +// if(!globalwindowhandler::IsKeyTimeoutEnabled()){ +// if(AutoPlayMode>=2){ +// AutoPlayMode=0; // TIMEOUT was disabled there at window handler! so reset here. +// AutoPlayModeApply(); +// } +// } + + ++AutoPlayMode; + if(AutoPlayMode>AUTOPLAYMODE_FRENZY)AutoPlayMode=AUTOPLAYMODE_DISABLED; + + AutoPlayModeApply(); +} + +void wizautoplay::AutoPlayModeApply(){ + int iTimeout=0; + bool bPlayInBackground=false; + + const char* msg; + switch(wizautoplay::AutoPlayMode){ + case AUTOPLAYMODE_DISABLED: + // disabled + msg="%s says \"I can rest now.\""; + break; + case AUTOPLAYMODE_NOTIMEOUT: + // no timeout, user needs to hit '.' to it autoplay once, the behavior is controled by AutoPlayMode AND the timeout delay that if 0 will have no timeout but will still autoplay. + msg="%s says \"I won't rest!\""; + break; + case AUTOPLAYMODE_SLOW: // TIMEOUTs key press from here to below + msg="%s says \"I can't wait anymore!\""; + iTimeout=(1000); + bPlayInBackground=true; + break; + case AUTOPLAYMODE_FAST: + msg="%s says \"I am in a hurry!\""; + iTimeout=(1000/2); + bPlayInBackground=true; + break; + case AUTOPLAYMODE_FRENZY: + msg="%s says \"I... *frenzy* yeah! Try to follow me now! Hahaha!\""; + iTimeout=10;//min possible to be fastest //(1000/10); // like 10 FPS, so user has 100ms chance to disable it + bPlayInBackground=true; + break; + } + ADD_MESSAGE(msg, P->GetName(DEFINITE).CStr()); + + globalwindowhandler::SetPlayInBackground(bPlayInBackground); + + if(!ivanconfig::IsXBRZScale()){ + /** + * TODO + * This is an horrible gum solution... + * I still have no idea why this happens. + * Autoplay will timeout 2 times slower if xBRZ is disabled! why!??!?!? + * But the debug log shows the correct timeouts :(, clueless for now... + */ + iTimeout/=2; + } + + globalwindowhandler::SetKeyTimeout(iTimeout,'.');//,'~'); +} + +void wizautoplay::AutoPlayCommandKey(character* C,int& Key,truth& HasActed,truth& ValidKeyPressed) +{ + P = C; + + if(wizautoplay::IsPlayerAutoPlay()){ + bool bForceStop = false; + if(wizautoplay::GetAutoPlayMode()>=AUTOPLAYMODE_SLOW) + bForceStop = globalwindowhandler::IsKeyPressed(SDL_SCANCODE_ESCAPE); + + if(!bForceStop && Key=='.'){ // pressed or simulated + if(game::IsInWilderness()){ + Key='>'; //blindly tries to go back to the dungeon safety :) TODO target and move to other dungeons/towns in the wilderness + }else{ + HasActed = wizautoplay::AutoPlayAICommand(Key); DBG2("Simulated",Key); + if(HasActed)ValidKeyPressed = true; //valid simulated action + } + }else{ + /** + * if the user hits any key during the autoplay mode that runs by itself, it will be disabled. + * at non auto mode, can be moved around but cannot rest or will move by itself + */ + if(wizautoplay::GetAutoPlayMode()>=AUTOPLAYMODE_SLOW && (Key!='~' || bForceStop)){ + wizautoplay::DisableAutoPlayMode(); + AutoPlayAIReset(true); // this will help on re-randomizing things, mainly paths + } + } + } +} + +#endif diff --git a/Script/item.dat b/Script/item.dat index 4ca9fb658..38bfc6e76 100644 --- a/Script/item.dat +++ b/Script/item.dat @@ -7970,3 +7970,30 @@ pica EnchantmentPlusChance = 15; } } + +/* nail was initially based on stone and ingot data */ +/* +nail +{ + DefaultSize = 5; + Possibility = 100; + Category = VALUABLE; + BitmapPos = 128, 32; + NameSingular = "nail"; + IsValuable = true; + MainMaterialConfig = { 35, + VALPURIUM, BRASS, HEPATIZON, OCTIRON, ORICHALCUM, SILVER, + MOON_SILVER, FAIRY_STEEL, MITHRIL, GALVORN, STAR_METAL, + PLATINUM, DARK_GOLD, ELECTRUM, IRIDIUM, PALLADIUM, CHROME, + TITANITE, STAINLESS_STEEL, PLASTIC_STEEL, DURALLOY, + VITRELLOY, URANIUM, SOLARIUM, NEUTRONIUM, ARCANITE, + OCCULTUM, ILLITHIUM, METEORIC_STEEL, UKKU_STEEL, ADAMANT, + SOUL_STEEL, BLOOD_STEEL, WHITE_STEEL, UR_STEEL; } + MaterialConfigChances = { 35, + 1, 200, 75, 25, 5, 300, 250, 100, 75, 25, 5, 50, 50, 50, + 30, 20, 10, 5, 20, 15, 10, 5, 15, 10, 5, 100, 15, 150, + 150, 75, 5, 200, 200, 200, 75; } + Alias == "metal"; + DescriptiveInfo = "Useful in wood crafting."; +} +*/ diff --git a/Script/olterra.dat b/Script/olterra.dat index 76cf72607..a6aa7bdf9 100644 --- a/Script/olterra.dat +++ b/Script/olterra.dat @@ -754,6 +754,8 @@ stairs /* olterrain-> */ DigMessage = "The stairs are too hard to dig."; MainMaterialConfig == GRANITE; MaterialColorB = rgb16(160, 64, 0); + MaterialColorC = rgb16(220, 220, 0); + MaterialColorD = rgb16(175, 130, 0); NameSingular = "stairway"; Config STAIRS_UP; diff --git a/Sound/SoundEffects.cfg b/Sound/SoundEffects.cfg index 281d23496..d2224c8a3 100644 --- a/Sound/SoundEffects.cfg +++ b/Sound/SoundEffects.cfg @@ -112,7 +112,8 @@ mine; mine.wav;.*You.*hear.*a.*faint.*thump.* #TODO: gas trap slip; slip.wav;.*on banana peel.* -web; teardown.wav;.*tear the web down.* +web; teardown1.wav;.*You fail to tear down the web.* +web; teardown2.wav, teardown3.wav;.*tear the web down.* ################### ### Player Hurt ### @@ -120,7 +121,7 @@ web; teardown.wav;.*tear the web down.* bleedsbadly; heartbeat.wav;.*bleeds very badly.* severed; severed.wav;.*is severed.* -die; die.wav, die2.wav, die3.wav, die4.wav, die5.wav;.*You.* die.* +die; die.wav, die2.wav, die3.wav, die4.wav, die5.wav;.*(You die[.]| you died[.]|, you die).* ###################### ### Player Actions ### @@ -147,7 +148,7 @@ holytome; holy.wav;.*HOLY SAGA.* # the bonus in piety and Wis, so the music would be awkward. #MonasterySounds;;.*The book reveals many divine secrets.* -Dig; FallDown1.wav, FallDown2.wav;.*start digging.* +Dig; dig.wav;.*start digging.* Snore; snore1.wav, snore2.wav, snore3.wav;.*You lose consciousness.* YouFallDown; FallDown1.wav, FallDown2.wav;.*You[^.]* fall down.* @@ -202,6 +203,7 @@ frog; frog.wav;.*Ribbit! Ribbit!.* frog; frog.wav;.*croaks.* plant; sprout.wav;.*sprouts.* imp; laugh2.wav;.*chortles.* +imp; laugh6.wav;.*imp grins maniacally.* # Cat avoided certain death: ninelives; cat2.wav, cat3.wav;.*miraculously.* diff --git a/Sound/dig.wav b/Sound/dig.wav new file mode 100644 index 000000000..cbe15a183 Binary files /dev/null and b/Sound/dig.wav differ diff --git a/Sound/laugh6.wav b/Sound/laugh6.wav new file mode 100644 index 000000000..c464c4d70 Binary files /dev/null and b/Sound/laugh6.wav differ diff --git a/Sound/teardown1.wav b/Sound/teardown1.wav new file mode 100644 index 000000000..2eff28692 Binary files /dev/null and b/Sound/teardown1.wav differ diff --git a/Sound/teardown2.wav b/Sound/teardown2.wav new file mode 100644 index 000000000..a15dafa48 Binary files /dev/null and b/Sound/teardown2.wav differ diff --git a/Sound/teardown3.wav b/Sound/teardown3.wav new file mode 100644 index 000000000..47f9d4923 Binary files /dev/null and b/Sound/teardown3.wav differ diff --git a/audio/audio.cpp b/audio/audio.cpp index 4e278a012..efb3efd18 100644 --- a/audio/audio.cpp +++ b/audio/audio.cpp @@ -158,6 +158,7 @@ int audio::Loop(void *ptr) festring MusFile = MusDir + CurrentTrack; + MPB_ResetMIDI(); // fix to some music transitions that leave MIDI in wrong state (keeps playing some single instrument note until next music is played) PlayMIDIFile(MusFile, 1); } isTrackPlaying = false; diff --git a/igor/CMakeLists.txt b/igor/CMakeLists.txt index b1c0b73bb..810a277ab 100644 --- a/igor/CMakeLists.txt +++ b/igor/CMakeLists.txt @@ -1,5 +1,19 @@ set(IGOR_VERSION 1.203) +include(FindPkgConfig) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PCRE libpcre) + set(PCRE_LIBRARIES ${PCRE_LDFLAGS}) +endif() +if(NOT PCRE_FOUND) + if(MSVC) + find_path(PCRE_INCLUDE_DIR NAMES pcre.h) + find_library(PCRE_LIBRARY pcre) + endif() + find_package(PCRE REQUIRED) + add_definitions(${PCRE_DEFINITIONS}) +endif() + add_executable(igor EXCLUDE_FROM_ALL Source/igor.cpp) -target_include_directories(igor PUBLIC ../Felib/Include) -target_link_libraries(igor FeLib xbrzscale) +target_include_directories(igor PUBLIC ../Felib/Include ${PCRE_INCLUDE_DIRS}) +target_link_libraries(igor FeLib xbrzscale ${PCRE_LIBRARIES}) diff --git a/mihail/CMakeLists.txt b/mihail/CMakeLists.txt index e7c952b1e..c037ff7c6 100644 --- a/mihail/CMakeLists.txt +++ b/mihail/CMakeLists.txt @@ -1,5 +1,19 @@ set(MIHAIL_VERSION 0.91) +include(FindPkgConfig) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PCRE libpcre) + set(PCRE_LIBRARIES ${PCRE_LDFLAGS}) +endif() +if(NOT PCRE_FOUND) + if(MSVC) + find_path(PCRE_INCLUDE_DIR NAMES pcre.h) + find_library(PCRE_LIBRARY pcre) + endif() + find_package(PCRE REQUIRED) + add_definitions(${PCRE_DEFINITIONS}) +endif() + add_executable(mihail EXCLUDE_FROM_ALL Source/mihail.cpp) -target_include_directories(mihail PUBLIC ../Felib/Include) -target_link_libraries(mihail FeLib xbrzscale) +target_include_directories(mihail PUBLIC ../Felib/Include ${PCRE_INCLUDE_DIRS}) +target_link_libraries(mihail FeLib xbrzscale ${PCRE_LIBRARIES})