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/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..732a5fad1 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(); @@ -1184,7 +1188,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*); @@ -1222,23 +1225,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; 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..41e4dcc4c 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -401,10 +401,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 +409,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 +582,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/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..7125d1675 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]")); } } diff --git a/Main/Source/char.cpp b/Main/Source/char.cpp index a263c8238..8ade9c20c 100644 --- a/Main/Source/char.cpp +++ b/Main/Source/char.cpp @@ -1213,7 +1213,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 +1505,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()) @@ -1702,34 +1702,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 +1710,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,7 +2544,14 @@ truth character::TestForPickup(item* ToBeTested) const void character::AddScoreEntry(cfestring& Description, double Multiplier, truth AddEndLevel) const { - if(!game::WizardModeIsReallyActive()) + if(game::WizardModeIsReallyActive()) + return; + if(curseddeveloper::IsCursedDeveloper()) + return; + + highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME); + + if(!HScore.CheckVersion()) { highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME); @@ -2734,923 +2721,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 +2741,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 +2755,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 +2773,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){ @@ -4607,10 +3663,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 +4027,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 +4047,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 +4062,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 +4330,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]")); } @@ -6202,7 +5256,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 +5922,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)) @@ -6924,7 +6017,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 +8262,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 +8412,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); @@ -13053,6 +12145,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)) 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/command.cpp b/Main/Source/command.cpp index 40e2fea57..90c8f74b7 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -37,6 +37,7 @@ #include "stack.h" #include "team.h" #include "whandler.h" +#include "wizautoplay.h" #include "worldmap.h" #include "wsquare.h" #include "wterras.h" @@ -872,8 +873,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); @@ -1999,7 +2000,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..c24a1a36c --- /dev/null +++ b/Main/Source/curseddeveloper.cpp @@ -0,0 +1,611 @@ +/* + * + * 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; + + 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..e6194d0f7 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()<2, false, &game::BusyAnimation); game::SetEnterImage(0); delete EnterImage; 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..df2db9240 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; @@ -1528,7 +1529,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; @@ -3806,10 +3807,20 @@ void game::RemoveSaves(truth RealSavesAlso,truth onlyBackups) void game::SetPlayer(character* NP) { +#ifdef CURSEDDEVELOPER + if(NP && Player && Player->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() @@ -4214,7 +4225,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 @@ -5532,69 +5543,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 +5552,6 @@ void game::SeeWholeMap() GetCurrentArea()->SendNewDrawRequest(); } - #endif void game::CreateBone() @@ -6469,7 +6416,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(;;) { diff --git a/Main/Source/main.cpp b/Main/Source/main.cpp index 146f3cc40..bdd26255a 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" @@ -149,6 +150,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/wizautoplay.cpp b/Main/Source/wizautoplay.cpp new file mode 100644 index 000000000..188136b2e --- /dev/null +++ b/Main/Source/wizautoplay.cpp @@ -0,0 +1,1369 @@ +/* + * + * 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; + + 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