diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b9b95c137 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Do not track these files. + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] +__pycache__ + +# Log files +log/ +*.log + +# Package files +*.jar +/*egg-info/ + +# Maven +target/ + +# JetBrains IDE +.idea/ +venv/ + +# Visual Studio +.vs* + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + +# Temporary files +~$*.doc* +~$*.xls* +~$*.ppt* diff --git a/CMakeLists.txt b/CMakeLists.txt index e2358bad4..e2aba0b97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,9 @@ project(ivan CXX C) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") set(VERSION_MAJOR 0) -set(VERSION_MINOR 59) +set(VERSION_MINOR 60) set(PROJECT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}") -#set(VERSION_PATCH 0) +#set(VERSION_PATCH dev) #set(PROJECT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") diff --git a/CMakeSettings.json b/CMakeSettings.json index 05fd6da9f..c0fecbfe1 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "x86-Release", - "generator": "Visual Studio 15 2017", + "generator": "Visual Studio 17 2022", "configurationType": "Release", "inheritEnvironments": [ "msvc_x86" diff --git a/Doc/HowTo/ConfigSyntax.txt b/Doc/HowTo/ConfigSyntax.txt index 635581efc..505014336 100644 --- a/Doc/HowTo/ConfigSyntax.txt +++ b/Doc/HowTo/ConfigSyntax.txt @@ -8,7 +8,7 @@ { let S = "string", N = "number", T = "truth", C = "cycle"; - let OPTIONS = new Map([["DefaultName",S],["FantasyNamePattern",S],["DefaultPetName",S],["AutoSaveInterval",N],["AltAdentureInfo",T],["BeNice",T],["HoldPosMaxDist",N],["MemorizeEquipmentMode",C],["WarnAboutVeryDangerousMonsters",T],["AutoDropLeftOvers",T],["SmartOpenCloseApply",T],["CenterOnPlayerAfterLook",T],["ShowGodInfo",T],["ShowMapAtDetectMaterial",T],["GoOnStopMode",C],["WaitNeutralsMoveAway",T],["Contrast",N],["WindowWidth",N],["WindowHeight",N],["GraphicsScale",C],["FullScreenMode",T],["ScalingQuality",C],["LookZoom",T],["XBRZScale",C],["XBRZSquaresAroundPlayer",N],["SilhouetteScale",C],["AltSilhouette",C],["AltSilhouettePreventColorGlitch",C],["AltListItemPos",C],["AltListItemWidth",N],["StackListPageLength",N],["DungeonGfxScale",C],["OutlinedGfx",T],["FrameSkip",N],["ShowItemsAtPlayerSquare",C],["RotateTimesPerSquare",C],["HitIndicator",C],["ShowMap",C],["PlaySounds",T],["Volume",N],["MIDIOutputDevice",C],["DirectionKeyMap",C],["SaveGameSortMode",C],["ShowTurn",T],["ShowFullDungeonName",T],["SelectedBkgColor",S],["AllowImportOldSavegame",T],["SavegameSafely",T],["HideWeirdHitAnimationsThatLookLikeMiss",T],["GenerateDefinesValidator",T]]); + let OPTIONS = new Map([["DefaultName",S],["FantasyNamePattern",S],["DefaultPetName",S],["AutoSaveInterval",N],["AltAdventureInfo",T],["BeNice",T],["HoldPosMaxDist",N],["MemorizeEquipmentMode",C],["WarnAboutVeryDangerousMonsters",T],["AutoDropLeftOvers",T],["SmartOpenCloseApply",T],["CenterOnPlayerAfterLook",T],["ShowGodInfo",T],["ShowMapAtDetectMaterial",T],["GoOnStopMode",C],["WaitNeutralsMoveAway",T],["Contrast",N],["WindowWidth",N],["WindowHeight",N],["GraphicsScale",C],["FullScreenMode",T],["ScalingQuality",C],["LookZoom",T],["XBRZScale",C],["XBRZSquaresAroundPlayer",N],["SilhouetteScale",C],["AltSilhouette",C],["AltSilhouettePreventColorGlitch",C],["AltListItemPos",C],["AltListItemWidth",N],["StackListPageLength",N],["DungeonGfxScale",C],["OutlinedGfx",T],["FrameSkip",N],["ShowItemsAtPlayerSquare",C],["RotateTimesPerSquare",C],["HitIndicator",C],["ShowMap",C],["PlaySounds",T],["Volume",N],["MIDIOutputDevice",C],["DirectionKeyMap",C],["SaveGameSortMode",C],["ShowTurn",T],["ShowFullDungeonName",T],["SelectedBkgColor",S],["AllowImportOldSavegame",T],["SavegameSafely",T],["HideWeirdHitAnimationsThatLookLikeMiss",T],["GenerateDefinesValidator",T]]); let VARIABLES = new Map([["XSize",30],["YSize",30]]); let isdigit = function (char) { return /[0-9]/.test(char); }; diff --git a/Doc/Lore/Fiction/Lantern.txt b/Doc/Lore/Fiction/Lantern.txt new file mode 100644 index 000000000..886ac4c67 --- /dev/null +++ b/Doc/Lore/Fiction/Lantern.txt @@ -0,0 +1,17 @@ +Many are the names in the common tongues for the great blessing which holy Legifer hath bestowed on us: "Lumes", "Glims", "Lanthorns", "Little-stars", "Twinkleboxes" and countless others in the tongue of Man alone. Many more exist in the languages of Dwarven-kind or the Elvish kindred and even the barbaric blabber of the Orcs, far more than could be mentioned. + +Yet how poor and pitiful is the gratitude returned to the lord of Order for these daily wonders despite the great benefit that they provide! Little do we seem to think of what should happen if His blessing were to be withdrawn across the world, leaving us all groping in the dark. So for the further enlightenment of the reader (in every meaning of the term) here is the known history of His generosity: + +In ages long past when cities had not begun and villages were but rudely built of straw-daubed walls with hide rooves, all except the highest of nobles lit torches for their homes within and without. Often naught else could be had: rushes burned but would only grow on the banks of warm rivers while coal mining scarce existed past tiny pits. Immeasurable was the amount of labor to keep the fickle fires of countless torches fed well enough to burn brightly but also guard them from catching aflame to everything else! + +Of all the torments of the forces of Chaos in those hard days, few were of greater delight in their loathesome eyes than to set a feeble spirit to flitter about a torch or luchina so that it leaned closely to a curtain to set it ablaze, or topple a poorly fixed oil lamp to the floor to spread flaming liquid all about. Such wicked conjurations gained the name chandl-wyghts (or "candle devils" in more modern speech). in time the more devious servants of darkness learnt to use the mere threat of the spirits for extortion that proved of greater effect than the wanton destructiveness of the spirits themselves. Sigils of protection began to be sold by such merchants who would trade in them that offered a tenday or a month or a season's worth of respite despite furious efforts to quell such round-about funding of evil. Even these sigils in turn became targets for a few bold charlatans, selling counterfeit sigils that held no magic protection at all. However, these forgeries were especially dangerous; risking fates far worse than death for the makers unfortunate enough to be caught by the 'true' salers. + +At length this ville traffic reached the household of a leader of an early guild of chandlers whose name has been lost to the ages. He apparently found one morning a threat scribbled upon a scrap of parchment to pay his tithe to the temple instead for 'insuring' the safety of his family and home. As his very life's work was the proper making of lights and fire-guarding, his rage was so kindled that he at once ran to the temple to shout with such a voice that half the village heard his oaths, demanding some means of the Lawful gods to stop his tormentors. Such had been his devotion to that no less than Iustitia herself appeared on the threshold of the temple. With a voice of thunder she proclaimed a call to holy war upon evil to last until the sun had set that day. + +Of what events transpired that day we know little, save that every man and strong boy for miles around grabbed such arms and armor that were at hand to answer Iustitia's summon to go forth putting a great many dens of evil to the sword. As the last flickers of daylight vanished over the horizon, the mighty archangel vanished to leave behind something most wondrous. A massive tablet stood where she had been, made of a material like glass yet far stronger that no man had ever seen the like of before. It would at some length be given the name "illithium" by learned men of the time. The lengthy canticle of holy words the tablet bore wove a spell that lit a brilliant white glow from within a vessel held by the one with the patience to complete the wearisome chant. As if this gift were not great enough, so amazing was Legifer's grace that even a simple-minded apprentice was able to complete it so long as his attention stayed unbroken. + +As quickly as they could be made, skilled smiths began fashioning more vessels for these blessed lights out of tin or brass with many piercings to let out the light from within. But the tradesman who had first begged Legifer's aid crafted the first true lantern encompassed with glass, knowing its affinity to the god. By the time the pious man went to his eternal reward he claimed to have fashioned at least a hundred-score lanterns with his own hands - and likely so! + +With the dispersal of these blessed lights all across the lands, not only were the ruinous powers sorely hurt but also many who would have perished in some firey tragedy hath lived full lives instead. Countless hearths and halls came to be lit without smoke or a pence's worth of tinder. To say nothing of the relief to forest-wardens when the common man needed far less wood throughout the year. + +Now let thy mind be enlightened by the full truth of Legifer's beneficence, that you may reflect upon his gift to the full measure. diff --git a/FeLib/Include/festring.h b/FeLib/Include/festring.h index 3c1af2a54..2e4f7255a 100644 --- a/FeLib/Include/festring.h +++ b/FeLib/Include/festring.h @@ -119,6 +119,8 @@ class festring void ExtractWord(festring&); long GetCheckSum() const; void EnsureOwnsData(bool = false); + inline bool IsUnique () const { return (OwnsData && Data && (REFS(Data) == 0)); } + void PutWeight (int w); // Nicely formatted weight private: static void InstallIntegerMap(); static void DeInstallIntegerMap(); @@ -126,6 +128,8 @@ class festring void CreateNewData(sizetype); void CreateOwnData(cchar*, sizetype); festring& AppendInt(long); + festring& AppendIntr (cchar* CStr, sizetype clen); + festring& AppendStr (cchar* s); festring& Append(char); festring& Append(cchar*, sizetype); festring& Append(cfestring&); diff --git a/FeLib/Source/festring.cpp b/FeLib/Source/festring.cpp index 6d26138ce..7535dcf44 100644 --- a/FeLib/Source/festring.cpp +++ b/FeLib/Source/festring.cpp @@ -907,3 +907,66 @@ void festring::EnsureOwnsData(bool Unique) CreateOwnData(Data, Size); } } + +festring& festring::AppendIntr(cchar* CStr, sizetype N) +{ + if(N == 0) + return *this; + + sizetype OldSize = Size; + sizetype NewSize = OldSize+N; + char* OldPtr = Data; + + if(OwnsData && IsUnique() && NewSize <= Reserved) { + memmove(OldPtr + OldSize, CStr, N); + Size = NewSize; + } else { + SlowAppend(CStr, N); + } + return *this; +} + +festring& festring::AppendStr(cchar* s) +{ + if(s && s[0]) { + return AppendIntr(s, (sizetype)strlen(s)); + } else { + return *this; + } +} + +void festring::PutWeight(int w) { + if(w < 1000) { + *this << w << "g"; + } else { + int kg = w / 1000; + int g = w % 1000; + + if(g != 0) { + if(g % 100 != 0) { + AppendStr("~"); + + if(g % 100 > 50) + g = g / 100 + 1; + else + g = g / 100; + + if(g == 10) { + kg += 1; + g = 0; + } + } else { + g /= 100; + } + } + + *this << kg; + + if (g != 0) { + AppendStr("."); + (*this) << g; + } + + AppendStr("kg"); + } +} diff --git a/Graphics/Char.png b/Graphics/Char.png index 01530a88f..b3e558cdc 100644 Binary files a/Graphics/Char.png and b/Graphics/Char.png differ diff --git a/Graphics/nuke.png b/Graphics/nuke.png new file mode 100644 index 000000000..4cbe9aa3b Binary files /dev/null and b/Graphics/nuke.png differ diff --git a/INSTALL b/INSTALL index 2be834c2c..7efa2c813 100644 --- a/INSTALL +++ b/INSTALL @@ -19,7 +19,8 @@ cd build cmake .. # ".." refers to the parent directory of "build" make make install -Note: This will make changes to /usr/bin, and you have to either run "sudo make install" instead, or log in as root. + +Note: This will make changes to /usr/bin, and you have to either run "sudo make install" instead, or log in as root. Note: Wizard Mode is disabled by default. To enable it run: mkdir build @@ -40,19 +41,19 @@ like this: CMAKE_CXX_FLAGS="-DFELIST_WAITKEYUP" Under DOS: -If you have DJGPP 2.03+ and gcc 2.952+ -installed, type: +If you have DJGPP 2.03+ and gcc 2.952+ installed, type: make -f ivandj.mak -------------------------------------- -Under Windows - Microsoft Visual Studio 2017 (or better): +Under Windows - Microsoft Visual Studio 2022: -You will need to install the C++ tools with CMake support, along with Git integrations from the Visual Studio Installer. -We will not be using Visual Studio in the traditional way with everything squished into a solution file. -Instead we will use Visual Studio to open a repo folder with a top-level CMakeLists.txt, in conjunction with a CMakeSettings.json file to create the cmake build files. -Then we will use msvc to compile ivan.exe, with the help of libraries we install from vcpkg using Powershell (PS). +You will need to install the C++ tools with CMake support, along with Git integrations from the Visual Studio Installer. +We will not be using Visual Studio in the traditional way with everything squished into a solution file. +Instead we will use Visual Studio to open a repo folder with a top-level CMakeLists.txt, in conjunction with +a CMakeSettings.json file to create the cmake build files. +Then we will use msvc to compile ivan.exe, with the help of libraries we install from vcpkg using Powershell (PS). Install vcpkg and install the needed libraries using Powershell: Here is a condensed summary of applicable instructions from https://github.com/microsoft/vcpkg @@ -76,7 +77,7 @@ zlib:x86-windows 1.2.11-5 A compressio Clone attnam/ivan into a directory (for example C:\Users\yourname\source\repos\ivan) -Open Microsoft Visual Studio (2017) +Open Microsoft Visual Studio 2022 Go File->Open Folder @@ -84,7 +85,8 @@ Select C:\Users\yourname\source\repos\ivan Some stuff will happen automatically. The CMakeSettings.json file will be found, and the CMake cache will be generated. -Next to the green "play" button is some text (Select Startup Item...) in a gray dropdown menu. Select "ivan.exe (Install) (ivan\ivan.exe)". +Next to the green "play" button is some text (Select Startup Item...) in a gray dropdown menu. +Select "ivan.exe (Install) (ivan\ivan.exe)". "Build" will appear in the menu next to "CMake". Click Build->ivan.exe (Install) (ivan\ivan.exe) F7 @@ -93,6 +95,14 @@ This will build ivan.exe and install all the components, automatically copying t The build folder is located in C:\Users\yourname\CMakeBuilds\some-long-code-here-abcdef123456\install\x86-Release\ivan Go there and hit ivan.exe, and you're playing IVAN built using Microsoft Visual Studio!!! +-------------------------------------- + +Under Windows - Microsoft Visual Studio OTHER THAN 2022: + +Instructions are the same as for Microsoft Visual Studio 2022, except that you must open CMakeSettings.json +in the project folder and change the generator to your version of VS. For example: + + "generator": "Visual Studio 15 2017" -------------------------------------- @@ -136,11 +146,13 @@ Follow instructions on the page to install correctly. Run MSYS2.exe from either the "-msys64" or "-msys32" folder, depending on your system, and type these commands $ pacman -Syu -Proceed with installation (option Y), then exit by closing the terminal window. The terminal window will crash and burn eventually but may take some time to do so. +Proceed with installation (option Y), then exit by closing the terminal window. +The terminal window will crash and burn eventually but may take some time to do so. Run MSYS2.exe again: $ pacman -Su -proceed with installation (option Y). This will take a long time to download all the packages, depending on your download speed +Proceed with installation (option Y). +This will take a long time to download all the packages, depending on your download speed. $ pacman -S git Proceed with installation (option Y) etcetera @@ -159,10 +171,11 @@ $ pacman -S mingw-w64-i686-SDL2_mixer $ pacman -S mingw-w64-i686-pkg-config -Now the MSYS2 build system is installed, you only need to follow the instructions below to keep IVAN up to date +Now the MSYS2 build system is installed, you only need to follow the instructions below to keep IVAN up to date. Create an empty folder in \ivan\ called msys2 -run MinGW32.exe (from your "-msys64" or "-msys32" folder, whichever is applicable) and change directory to your newly created \ivan\msys2\ folder +Run MinGW32.exe (from your "-msys64" or "-msys32" folder, whichever is applicable) and change directory to your +newly created \ivan\msys2\ folder. In MinGW32.exe type the following commands, respecting the direction of the slashes: $ cmake .. -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=C:/ivan/inst_msys2 @@ -170,7 +183,8 @@ $ make -j4 install Navigate to \ivan\inst_msys2\ivan and you will find the ivan.exe executable! -You will probably need to copy a bunch of DLLs into the directory where ivan.exe is located. These may include the following, likely not an exhaustive list, but they can be found under for example C:\-msys64\mingw32\bin: +You will probably need to copy a bunch of DLLs into the directory where ivan.exe is located. These may include +the following, likely not an exhaustive list, but they can be found under for example C:\-msys64\mingw32\bin: libpng16-16.dll libstdc++-6.dll libwinpthread-1.dll @@ -180,7 +194,9 @@ libSDL2_mixer-2-0-0.dll libmad-0.dll -Use dependencywalker if you need to check for missing libraries, and if you get the message: "error cannot find entrypoint inflateValidate (in dll libpng16-16.dll)" then you need to include zlib1.dll +Use dependencywalker if you need to check for missing libraries, and if you get the message: + "error cannot find entrypoint inflateValidate (in dll libpng16-16.dll)" +then you need to include zlib1.dll -------------------------------------- diff --git a/Main/Include/game.h b/Main/Include/game.h index b497cd938..771af7889 100644 --- a/Main/Include/game.h +++ b/Main/Include/game.h @@ -433,6 +433,7 @@ class game static long GetScore(); static truth TweraifIsFree(); static truth IsXMas(); + static truth IsSamhain(); static int AddToItemDrawVector(const itemvector&); static void ClearItemDrawVector(); static void ItemEntryDrawer(bitmap*, v2, uint); @@ -501,10 +502,10 @@ class game static void SetEnterImage(cbitmap* What) { EnterImage = What; } static void SetEnterTextDisplacement(v2 What){ EnterTextDisplacement = What; } static int getDefaultItemsListWidth(){ return iListWidth; } - static void AddDebugDrawOverlayFunction(dbgdrawoverlay ddo){vDbgDrawOverlayFunctions.push_back(ddo);} + static void AddDebugDrawOverlayFunction(dbgdrawoverlay ddo){ vDbgDrawOverlayFunctions.push_back(ddo); } static int GetCurrentDungeonTurnsCount(){return iCurrentDungeonTurn;} static int GetSaveFileVersionHardcoded(); - static void ValidateCommandKeys(char Key1,char Key2,char Key3); + static void ValidateCommandKeys(char Key1, char Key2, char Key3); static truth ConfigureCustomKeys(); static festring ToCharIfPossible(int i); static truth ValidateCustomCmdKey(int iNewKey, int iIgnoreIndex, bool bMoveKeys); @@ -512,6 +513,7 @@ class game static void LoadCustomCommandKeys(); static int GetWorldShape() { return WorldShape; } static void SetWorldShape(int What) { WorldShape = What; } + static bool OpposedCheck(int first, int second, int cap = 5); private: static void UpdateCameraCoordinate(int&, int, int, int); static cchar* const Alignment[]; diff --git a/Main/Include/iconf.h b/Main/Include/iconf.h index 47e387514..7665f6500 100644 --- a/Main/Include/iconf.h +++ b/Main/Include/iconf.h @@ -55,9 +55,9 @@ class ivanconfig static truth GetLookZoom() { return LookZoom.Value; } static truth IsXBRZScale() { return XBRZScale.Value; } static truth IsAutoPickupThrownItems() { return AutoPickupThrownItems.Value; } - static truth IsAltAdentureInfo() { return AltAdentureInfo.Value; } + static truth IsAltAdventureInfo() { return AltAdventureInfo.Value; } static truth UseDescriptiveHP() { return DescriptiveHP.Value; } - static truth GetNoPet() { return StartWithNoPet.Value; } + static truth GetPet() { return StartWithPet.Value; } static int GetXBRZSquaresAroundPlayer() { return XBRZSquaresAroundPlayer.Value; } static int GetStartingDungeonGfxScale() { return iStartingDungeonGfxScale; } static int GetStartingFontGfx() { return iStartingFontGfx; } @@ -83,6 +83,7 @@ class ivanconfig static int GetWorldShapeConfig() { return WorldShapeConfig.Value; } static int GetWorldSeedConfig() { return WorldSeedConfig.Value; } static int GetWorldSizeNumber() { return WorldSizeConfig.Value; } + static truth IsCraftingEnabled() { return CraftingEnabled.Value; } #ifndef __DJGPP__ static int GetGraphicsScale() { return GraphicsScale.Value; } @@ -196,7 +197,7 @@ class ivanconfig static stringoption SelectedBkgColor; static stringoption AutoPickUpMatching; static numberoption AutoSaveInterval; - static truthoption AltAdentureInfo; + static truthoption AltAdventureInfo; static truthoption CenterOnPlayerAfterLook; static scrollbaroption Contrast; static truthoption ShowGodInfo; @@ -231,7 +232,7 @@ class ivanconfig static truthoption XBRZScale; static truthoption AutoPickupThrownItems; static truthoption DescriptiveHP; - static truthoption StartWithNoPet; + static truthoption StartWithPet; static cycleoption SaveGameSortMode; static cycleoption DistLimitMagicMushrooms; @@ -262,12 +263,14 @@ class ivanconfig static scrollbaroption Volume; static scrollbaroption SfxVolume; static cycleoption MIDIOutputDevice; - + static cycleoption WorldSizeConfig; static cycleoption LandTypeConfig; static cycleoption WorldShapeConfig; static numberoption WorldSeedConfig; + static truthoption CraftingEnabled; + #ifndef __DJGPP__ static cycleoption GraphicsScale; static truthoption FullScreenMode; diff --git a/Main/Source/bodypart.cpp b/Main/Source/bodypart.cpp index 7ddf08815..8bf51829a 100644 --- a/Main/Source/bodypart.cpp +++ b/Main/Source/bodypart.cpp @@ -3564,17 +3564,21 @@ void head::SignalPossibleUsabilityChange() if(RAND_N(5)) { ADD_MESSAGE("Your memory becomes blurred."); - GetLevel()->Amnesia(25 + RAND_N(50)); Master->EditExperience(INTELLIGENCE, -80, 1 << 13); - game::SendLOSUpdateRequest(); + if(!game::IsInWilderness()) { + GetLevel()->Amnesia(25 + RAND_N(50)); + game::SendLOSUpdateRequest(); + } } else { ADD_MESSAGE("A terrible concussion garbles your consciousness."); Master->BeginTemporaryState(CONFUSED, 5000 + RAND_N(5000)); Master->EditExperience(INTELLIGENCE, -100, 1 << 14); - GetLevel()->BlurMemory(); - game::SendLOSUpdateRequest(); + if(!game::IsInWilderness()) { + GetLevel()->BlurMemory(); + game::SendLOSUpdateRequest(); + } } } else diff --git a/Main/Source/char.cpp b/Main/Source/char.cpp index 60c12b099..72659b6d6 100644 --- a/Main/Source/char.cpp +++ b/Main/Source/char.cpp @@ -1615,8 +1615,8 @@ truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWai } else MoveTo = MoveTo; // Flat (default) - - + + if(CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) @@ -5042,7 +5042,7 @@ void character::DoDetecting() if(Squares > GetAttribute(INTELLIGENCE) * (25 + RAND() % 51)) { ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur."); - Level->BlurMemory(); + Level->BlurMemory(); // !game::IsInWilderness() is already checked by DetectHandler() BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000); EditExperience(INTELLIGENCE, -100, 1 << 12); } @@ -8527,8 +8527,11 @@ void character::ReceiveAntidote(long Amount) } else { - if(IsPlayer()) + if(IsPlayer()) { ADD_MESSAGE("Aaaah... You feel much better."); + } else if(CanBeSeenByPlayer()) { + ADD_MESSAGE("%s looks much better.", CHAR_NAME(DEFINITE)); + } Amount -= GetTemporaryStateCounter(POISONED); DeActivateTemporaryState(POISONED); @@ -8897,33 +8900,27 @@ truth character::MoveTowardsHomePos() truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Chosen) { - if(!GetBodyPartOfEquipment(Chosen)) - { + if(!GetBodyPartOfEquipment(Chosen)) { ADD_MESSAGE("Bodypart missing!"); return false; } item* OldEquipment = GetEquipment(Chosen); + if(OldEquipment) { + if(!IsPlayer() && BoundToUse(OldEquipment, Chosen)) { + ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE)); + return false; + } - if(!IsPlayer() && BoundToUse(OldEquipment, Chosen)) - { - ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE)); - return false; - } - - if(OldEquipment) - { - if(!OldEquipment->CanBeUnEquipped(Chosen)) - { + if(!OldEquipment->CanBeUnEquipped(Chosen)) { if(IsPlayer()) ADD_MESSAGE("You fail to unequip %s.", OldEquipment->CHAR_NAME(DEFINITE)); else ADD_MESSAGE("%s fails to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE)); - return false; } - else - OldEquipment->MoveTo(MainStack); + + OldEquipment->MoveTo(MainStack); } sorter Sorter = EquipmentSorter(Chosen); @@ -8948,7 +8945,7 @@ truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Cho SecStack ? festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()) : CONST_S(""), GetVerbalBurdenStateColor(), - NONE_AS_CHOICE|NO_MULTI_SELECT, + NONE_AS_CHOICE|NO_MULTI_SELECT|SELECT_PAIR, Sorter); if(Return == ESCAPED) @@ -8963,6 +8960,7 @@ truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Cho } item* Item = ItemVector.empty() ? 0 : ItemVector[0]; + int otherChosen = -1; if(Item) { @@ -8982,11 +8980,83 @@ truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Cho return false; } + // Handle paired items: + if(Item->HandleInPairs() && ItemVector.size() > 1) { + switch (Chosen) { + case RIGHT_GAUNTLET_INDEX: otherChosen = LEFT_GAUNTLET_INDEX; break; + case LEFT_GAUNTLET_INDEX: otherChosen = RIGHT_GAUNTLET_INDEX; break; + case RIGHT_BOOT_INDEX: otherChosen = LEFT_BOOT_INDEX; break; + case LEFT_BOOT_INDEX: otherChosen = RIGHT_BOOT_INDEX; break; + default: break; + } + + if(otherChosen != -1) { + if(!GetBodyPartOfEquipment(otherChosen) || !game::TruthQuestion("Do you want to wear both items? [y/N]", YES)) + otherChosen = -1; + } + } + Item->RemoveFromSlot(); SetEquipment(Chosen, Item); - if(CheckIfEquipmentIsNotUsable(Chosen)) - Item->MoveTo(MainStack); // small bug? + // Wear first/only item: + if(CheckIfEquipmentIsNotUsable(Chosen)) { + // TODO small bug? + Item->MoveTo(MainStack); + Item = 0; + otherChosen = -1; + } + + // Wear possible second item: + if(otherChosen != -1) + { + item* otherOldEquipment = GetEquipment(otherChosen); + + if(otherOldEquipment) { + if(!IsPlayer() && BoundToUse(otherOldEquipment, otherChosen)) { + ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), otherOldEquipment->CHAR_NAME(DEFINITE)); + otherChosen = -1; + } + + if(otherChosen != -1 && !otherOldEquipment->CanBeUnEquipped(otherChosen)) { + if(IsPlayer()) + ADD_MESSAGE("You fail to unequip %s.", otherOldEquipment->CHAR_NAME(DEFINITE)); + else + ADD_MESSAGE("%s fails to unequip %s.", CHAR_DESCRIPTION(DEFINITE), otherOldEquipment->CHAR_NAME(DEFINITE)); + otherChosen = -1; + } + + if(otherChosen != -1) + otherOldEquipment->MoveTo(MainStack); + } + + item* otherItem = ItemVector[1]; + if(otherChosen != -1 && otherItem) + { + if(!IsPlayer() && !AllowEquipment(otherItem, otherChosen)) { + ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), otherItem->CHAR_NAME(DEFINITE)); + otherChosen = -1; + } + + if(otherChosen != -1 && !otherItem->CanBeEquipped(otherChosen)) { + if(IsPlayer()) + ADD_MESSAGE("You fail to equip %s.", otherItem->CHAR_NAME(DEFINITE)); + else + ADD_MESSAGE("%s fails to equip %s.", CHAR_DESCRIPTION(DEFINITE), otherItem->CHAR_NAME(DEFINITE)); + otherChosen = -1; + } + + if(otherChosen != -1) { + otherItem->RemoveFromSlot(); + SetEquipment(otherChosen, otherItem); + + if(CheckIfEquipmentIsNotUsable(otherChosen)) { + otherItem->MoveTo(MainStack); + otherChosen = -1; + } + } + } + } } return Item != OldEquipment; @@ -9374,7 +9444,7 @@ void character::ShowAdventureInfo() const { graphics::SetAllowStretchedBlit(); - if(ivanconfig::IsAltAdentureInfo()) + if(ivanconfig::IsAltAdventureInfo()) { ShowAdventureInfoAlt(); } @@ -11472,9 +11542,7 @@ truth character::EquipmentScreen(stack* MainStack, stack* SecStack) if(IsPlayer()) { festring Total("Total weight: "); - Total << TotalEquippedWeight; - Total << "g"; - + Total.PutWeight(TotalEquippedWeight); List.AddDescription(CONST_S("")); List.AddDescription(Total); } @@ -12511,17 +12579,19 @@ void character::ForcePutNear(v2 Pos) PutTo(NewPos); } -void character::ReceiveMustardGas(int BodyPart, long Volume) +void character::ReceiveMustardGas(int BodyPartIndex, long Volume) { - if(Volume) - GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true); + bodypart* BodyPart = GetBodyPart(BodyPartIndex); + + if(BodyPart && Volume) + BodyPart->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true); } void character::ReceiveMustardGasLiquid(int BodyPartIndex, long Modifier) { bodypart* BodyPart = GetBodyPart(BodyPartIndex); - if(BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) + if(BodyPart && BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) { long Tries = Modifier; Modifier -= Tries; //opt%? @@ -12687,7 +12757,14 @@ truth character::ReceiveSirenSong(character* Siren) if(Siren->GetRelation(this) != HOSTILE) return false; - if(RAND_N(GetAttribute(WILL_POWER)) > RAND_N(Siren->GetAttribute(CHARISMA))) + // Since siren song is a sound-based attack, modify base resistance by any sound resistance the character may have. + int songResist = GetAttribute(WILL_POWER); + if(!IsHumanoid()) + songResist *= 1 + GetResistance(SOUND); + else if(HasHead()) + songResist *= 1 + dynamic_cast(this)->GetHead()->GetTotalResistance(SOUND); + + if(game::OpposedCheck(songResist, Siren->GetAttribute(CHARISMA))) { if(IsPlayer()) ADD_MESSAGE("The beautiful song of %s makes you feel a little sad.", Siren->CHAR_NAME(DEFINITE)); diff --git a/Main/Source/cmdcraft.cpp b/Main/Source/cmdcraft.cpp index 5a5b0d5a0..4da190f9c 100644 --- a/Main/Source/cmdcraft.cpp +++ b/Main/Source/cmdcraft.cpp @@ -399,11 +399,6 @@ void recipedata::Load(inputfile& SaveFile) ; - if(game::GetCurrentSavefileVersion() >= 135){ - SaveFile >> bTailoringMode; - SaveFile >> v2TailoringWorkbenchLocation; - } - // if(otSpawnType!=CTT_NONE) // SaveFile >> otSpawn; rc.integrityCheck(); @@ -3093,8 +3088,13 @@ void addMissingMsg(festring& where, cfestring& what){ */ truth commandsystem::Craft(character* Char) //TODO currently this is an over simplified crafting system... should be easy to add recipes and show their formulas... { - return craftcore::Craft(Char); + if(ivanconfig::IsCraftingEnabled()) + return craftcore::Craft(Char); + + ADD_MESSAGE("Crafting is an experimental system and may cause issues. Enable it in Options, if you wish."); + return false; } + truth craftcore::CheckArms(humanoid* h) { bool bLOk,bROk; //dummy diff --git a/Main/Source/command.cpp b/Main/Source/command.cpp index 520505301..ebf9577ca 100644 --- a/Main/Source/command.cpp +++ b/Main/Source/command.cpp @@ -71,7 +71,7 @@ int command::GetKey() const return Key1; case DIR_ALT: // Alternative return Key2; - case DIR_HACK: // Nethack + case DIR_HACK: // NetHack return Key3; default: ABORT("This is not Vim!"); @@ -83,7 +83,7 @@ command* commandsystem::Command[] = { 0, - /* Sort according to relaiton and assumed frequency of use */ + /* Sort according to relation and assumed frequency of use */ new command(&NOP, "wait a turn", '.', '.', '.', true), new command(&Go, "go / fastwalk", 'g', 'g', 'g', false), @@ -99,20 +99,20 @@ command* commandsystem::Command[] = new command(&Zap, "zap a wand", 'z', 'z', 'z', false), new command(&Read, "read", 'r', 'r', 'r', false), new command(&Eat, "eat", 'e', 'e', 'e', true), - new command(&Drink, "drink liquid", 'D', 'D', 'D', true), + new command(&Drink, "drink liquid", 'D', 'D', 'q', true), new command(&Taste, "taste a bit of liquid", 'T', 'T', 'T', true), new command(&Dip, "dip into liquid", '!', '!', '!', false), - new command(&Open, "open", 'o', 'O', 'o', false), + new command(&Open, "open", 'o', 'O', 'o', true), new command(&Close, "close", 'c', 'c', 'c', false), new command(&Search, "search", 's', 's', 's', false), new command(&Look, "look around", 'l', 'L', 'L', true), new command(&ShowMap, "show map", 'm', 'm', 'm', false), new command(&WhatToEngrave, "engrave / inscribe", 'G', 'G', 'G', false), new command(&Talk, "chat", 'C', 'C', 'C', false), - new command(&Craft, "craft", 'f', 'F', 'f', false), + new command(&Craft, "craft", 'f', 'f', 'f', false), new command(&AssignName, "name team members", 'n', 'n', 'N', false), new command(&IssueCommand, "issue commands to team members", 'I', 'I', 'I', false), - new command(&Offer, "offer to gods", 'O', 'f', 'O', false), + new command(&Offer, "offer to gods", 'O', 'F', 'O', false), new command(&Pray, "pray to gods", 'p', 'p', 'p', false), new command(&Sit, "sit down", '_', '_', '_', false), new command(&Rest, "rest and heal", 'h', 'h', 'H', true), @@ -330,6 +330,12 @@ truth commandsystem::Open(character* Char) { if(Char->CanOpen()) { + if(game::IsInWilderness()) + { + item* Item = Char->GetStack()->DrawContents(Char, CONST_S("What do you want to open?"), 0, &item::IsOpenable); + return Item && Item->Open(Char); + } + int Key; if(ivanconfig::GetSmartOpenCloseApply()) @@ -367,9 +373,7 @@ truth commandsystem::Open(character* Char) if(Key == 'i') { - item* Item = Char->GetStack()->DrawContents(Char, - CONST_S("What do you want to open?"), - 0, &item::IsOpenable); + item* Item = Char->GetStack()->DrawContents(Char, CONST_S("What do you want to open?"), 0, &item::IsOpenable); return Item && Item->Open(Char); } } @@ -391,7 +395,7 @@ truth commandsystem::Open(character* Char) Key = game::AskForKeyPress(CONST_S("What do you wish to open? " "[press a direction key or space]")); } - else + else // Old "stupid" open { truth OpenableItems = Char->GetStack()->SortedItems(Char, &item::IsOpenable); @@ -404,16 +408,14 @@ truth commandsystem::Open(character* Char) if(Key == 'i' && OpenableItems) { - item* Item = Char->GetStack()->DrawContents(Char, - CONST_S("What do you want to open?"), - 0, &item::IsOpenable); + item* Item = Char->GetStack()->DrawContents(Char, CONST_S("What do you want to open?"), 0, &item::IsOpenable); return Item && Item->Open(Char); } } v2 DirVect = game::GetDirectionVectorForKey(Key); - if(DirVect != ERROR_V2 && Char->GetArea()->IsValidPos(Char->GetPos() + DirVect)){ + if(DirVect != ERROR_V2 && Char->GetArea()->IsValidPos(Char->GetPos() + DirVect)) { return Char->GetNearLSquare(Char->GetPos() + DirVect)->Open(Char); } } @@ -632,8 +634,8 @@ truth commandsystem::ShowInventory(character* Char) { itemvector WhichItem; festring Title("Your inventory (total weight: "); - Title << Char->GetStack()->GetWeight(); - Title << "g)"; + Title.PutWeight(Char->GetStack()->GetWeight()); + Title << ")"; Char->GetStack()->DrawContents(WhichItem, Char, Title, REMEMBER_SELECTED); return false; diff --git a/Main/Source/dungeon.cpp b/Main/Source/dungeon.cpp index 95bf89cc5..3fc9f6c41 100644 --- a/Main/Source/dungeon.cpp +++ b/Main/Source/dungeon.cpp @@ -94,18 +94,18 @@ truth dungeon::PrepareLevel(int Index, truth Visual) { festring fsGenLoopDL;{const char* pc = std::getenv("IVAN_DebugGenDungeonLevelLoopID");if(pc!=NULL)fsGenLoopDL<SetDungeon(this); NewLevel->SetIndex(Index); @@ -123,11 +123,11 @@ truth dungeon::PrepareLevel(int Index, truth Visual) game::SetEnterTextDisplacement(Displacement); game::TextScreen(CONST_S("Entering ") + GetLevelDescription(Index) + CONST_S("...\n\nThis may take some time, please wait."), - Displacement, WHITE, false, + Displacement, WHITE, false, 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, game::GetAutoPlayMode()<2, false, &game::BusyAnimation); game::SetEnterImage(0); delete EnterImage; @@ -158,7 +158,7 @@ truth dungeon::PrepareLevel(int Index, truth Visual) //TODO it is not working well, memory usage keeps increasing... if(NewLevel ){delete NewLevel ;NewLevel=NULL;} if(EnterImage){delete EnterImage;EnterImage=NULL;} - + //retry } } //for() @@ -210,10 +210,6 @@ void dungeon::PrepareMusic(int Index) } audio::SetPlaybackStatus(audio::PLAYING); } - - - - } void dungeon::SaveLevel(cfestring& SaveName, int Number, truth DeleteAfterwards) @@ -312,7 +308,7 @@ inputfile& operator>>(inputfile& SaveFile, dungeon*& Dungeon) /** * The wrong luminance saved to a lsquare problem - * may happen after craft/split eg.: a blue crystal stone, + * may happen after craft/split e.g.: a blue crystal stone, * then you save the game and re-load it and the luminance would be still there. * TODO this workaround will not be necessary when the problem is fixed on it's origin */ @@ -333,7 +329,7 @@ level* dungeon::LoadLevel(inputfile& SaveFile, int Number) Level[Number]->SetLevelScript(GetLevelScript(Number)); PrepareMusic(Number); WorkaroundFixLuminance(Level[Number]); - + return Level[Number]; } diff --git a/Main/Source/game.cpp b/Main/Source/game.cpp index 321449fd4..8b3e4c688 100644 --- a/Main/Source/game.cpp +++ b/Main/Source/game.cpp @@ -874,9 +874,9 @@ truth game::Init(cfestring& loadBaseName) DefaultDetectMaterial.Empty(); Player->GetStack()->AddItem(encryptedscroll::Spawn()); - if(!ivanconfig::GetNoPet()) + if(ivanconfig::GetPet()) { - character* Doggie = dog::Spawn(); + character* Doggie = dog::Spawn(IsSamhain() ? SKELETON_DOG : 0); Doggie->SetTeam(GetTeam(0)); GetWorldMap()->GetPlayerGroup().push_back(Doggie); Doggie->SetAssignedName(ivanconfig::GetDefaultPetName()); @@ -908,6 +908,8 @@ truth game::Init(cfestring& loadBaseName) Player->GetStack()->AddItem(Present); ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE)); } + if(IsSamhain()) + ADD_MESSAGE("%s looks a little under the weather.", ivanconfig::GetDefaultPetName().CStr()); /* Set off the worldmap music */ audio::SetPlaybackStatus(0); @@ -5770,7 +5772,7 @@ void game::LoadCustomCommandKeys() static festring fsFile = GetUserDataDir() + CUSTOM_KEYS_FILENAME; FILE *fl = fopen(fsFile.CStr(), "rt"); if(!fl)return; - + festring Line; festring fsMatch; festring fsPostFix="\"=0x"; @@ -5787,7 +5789,7 @@ void game::LoadCustomCommandKeys() break; } } - + int iVal; for(int c=0;c<8;c++){ fsMatch.Empty(); @@ -5796,9 +5798,9 @@ void game::LoadCustomCommandKeys() game::MoveCustomCommandKey[c]=HexToInt(Line); break; } - } + } } - + fclose(fl); } @@ -5824,7 +5826,7 @@ truth game::ValidateCustomCmdKey(int iNewKey, int iIgnoreIndex, bool bMoveKeys) //TODO these SYSTEM messages messes the gameplay message log... but is better than a popup? bool bValid=true; festring fsDesc; - + // conflicts check if(bValid){ command *cmd; @@ -5845,13 +5847,13 @@ truth game::ValidateCustomCmdKey(int iNewKey, int iIgnoreIndex, bool bMoveKeys) bValid=false; break; } - } + } } if(!bValid){ ADD_MESSAGE("SYSTEM: conflicting key '%s'(code is %d or 0x%04X) with command \"%s\", retry...", ToCharIfPossible(iNewKey).CStr(),iNewKey,iNewKey,fsDesc.CStr()); } - + // general invalid key codes if(bValid){ if(iNewKey<0x20){ @@ -5859,7 +5861,7 @@ truth game::ValidateCustomCmdKey(int iNewKey, int iIgnoreIndex, bool bMoveKeys) bValid=false; } } - + return bValid; } @@ -5873,33 +5875,33 @@ festring IntToHexStr(int i) festring game::ToCharIfPossible(int i) { - switch(i){ // these are above 0xFF + switch(i){ // these are above 0xFF //TODO complete this list, if has no #define, use the hexa directly. - case KEY_UP: + case KEY_UP: return "Up"; - case KEY_DOWN: + case KEY_DOWN: return "Down"; - case KEY_RIGHT: + case KEY_RIGHT: return "Right"; - case KEY_LEFT: + case KEY_LEFT: return "Left"; - case KEY_HOME: + case KEY_HOME: return "Home"; - case KEY_END: + case KEY_END: return "End"; - case KEY_PAGE_DOWN: + case KEY_PAGE_DOWN: return "PgDn"; - case KEY_PAGE_UP: + case KEY_PAGE_UP: return "PgUp"; case KEY_DELETE: return "Del"; case KEY_INSERT: return "Ins"; } - + if(i>=0 && i<=0xFF) //these are mapped at fonts gfx files return festring()+(char)i; - + return IntToHexStr(i); } @@ -5911,12 +5913,12 @@ void WriteCustomKeyBindingsCfgFile(FILE *fl,festring fsDesc,int iKey){ /** * Command's (and movement keys) descriptions are used as identifiers at the config file. * These descriptions shall not clash and preferably should not be changed. - * @return + * @return */ truth game::ConfigureCustomKeys() { game::LoadCustomCommandKeys(); //in case there is anything already set - + felist fel(CONST_S("Configure custom keys:")); bool bRet=true; command* cmd; @@ -5926,16 +5928,16 @@ truth game::ConfigureCustomKeys() bool bWizIni=false; festring fsEntry; for(int c = 1; (cmd=commandsystem::GetCommand(c)); ++c){ - fsEntry=cmd->GetDescription(); + fsEntry=cmd->GetDescription(); fsEntry.Resize(60); fsEntry<<"'"<GetKey())<<"' "; fsEntry<GetKey()); - + if(!bWizIni && cmd->IsWizardModeFunction()){ fel.AddEntry("Wizard mode keys:", DARK_GRAY, 20, NO_IMAGE, false); bWizIni=true; } - + fel.AddEntry(fsEntry, LIGHT_GRAY, 0, NO_IMAGE, true); iMoveKeyStart++; } @@ -5955,7 +5957,7 @@ truth game::ConfigureCustomKeys() bRet=false; break; } - + bool bIsMoveKeys = Select >= iMoveKeyStart; int iMvKeyIndex = bIsMoveKeys ? Select-iMoveKeyStart : -1; int iCmdKeyIndex = bIsMoveKeys ? -1 : Select+1; @@ -5965,7 +5967,7 @@ truth game::ConfigureCustomKeys() while(true){ cmd=NULL; festring fsAsk = "Press a key to assign to the command \""; - + if(bIsMoveKeys){ fsAsk<GetKey()); fsAsk<<")"; - + iNewKey=game::AskForKeyPress(fsAsk); if(iNewKey==KEY_ESC){bIgnore=true;break;} @@ -5990,23 +5992,23 @@ truth game::ConfigureCustomKeys() } } if(bIgnore)continue; - + if(!bRet)break; - + if(bIsMoveKeys) game::MoveCustomCommandKey[iMvKeyIndex]=iNewKey; else commandsystem::GetCommand(iCmdKeyIndex)->SetCustomKey(iNewKey); } - + festring fsFl = GetUserDataDir() + CUSTOM_KEYS_FILENAME; - + // backup existing festring fsFlBkp=fsFl+".bkp"; std::ifstream src(fsFl.CStr() , std::ios::binary); std::ofstream dst(fsFlBkp.CStr(), std::ios::binary); dst << src.rdbuf(); - + // write a new in full FILE *fl = fopen(fsFl.CStr(), "wt"); //"a"); festring fsWriteLine; @@ -6016,7 +6018,7 @@ truth game::ConfigureCustomKeys() for(int c=0;c<8;c++) WriteCustomKeyBindingsCfgFile(fl,GetMoveKeyDesc(c),game::MoveCustomCommandKey[c]); fclose(fl); - + if(bRet)game::LoadCustomCommandKeys(); //this here is more to validate if all went ok return bRet; } @@ -6040,7 +6042,7 @@ void game::ValidateCommandKeys(char Key1,char Key2,char Key3) pa=game::MoveAbnormalCommandKey; Key=Key2; break; case DIR_HACK: pa=game::MoveNetHackCommandKey; Key=Key3; break; -/*TODO case DIR_CUSTOM: +/*TODO case DIR_CUSTOM: pa=game::MoveCustomCommandKey; Key=???; break; */ } @@ -6057,7 +6059,7 @@ int game::GetMoveCommandKey(int I) { if(ivanconfig::IsSetupCustomKeys()) return MoveCustomCommandKey[I]; - + switch(ivanconfig::GetDirectionKeyMap()) { case DIR_NORM: @@ -6106,13 +6108,29 @@ truth game::TweraifIsFree() return true; } -truth game::IsXMas() // returns true if date is christmaseve or day +// FIXME: +// The time related functions such as time fill data into a tm struct or char array in shared memory and then +// returns a pointer to that memory. If the function is called from multiple places in the same program, and +// especially if it is called from multiple threads in the same program, then the calls will overwrite each other's +// data. Possibly replace calls to localtime with localtime_r. With _r, the application code manages allocation +// of the tm struct. That way, separate calls to the function can use their own storage. +// Unfortunately, localtime_r was only added in C23. + +truth game::IsXMas() // returns true if date is Christmas Eve or Day. { time_t Time = time(0); struct tm* TM = localtime(&Time); return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25)); } +truth game::IsSamhain() +{ + time_t Time = time(0); + struct tm* TM = localtime(&Time); + // Celebrations of Samhain begin on the evening of 31st October and go through 1st November. + return (TM->tm_mon == 9 && TM->tm_mday == 31) || (TM->tm_mon == 10 && TM->tm_mday == 1); +} + int game::AddToItemDrawVector(const itemvector& What) { ItemDrawVector.push_back(What); @@ -7090,3 +7108,23 @@ void game::ShowDeathSmiley(bitmap* Buffer, truth) if(Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen(); } + +bool game::OpposedCheck(int first, int second, int cap) +{ + /* Opposed random check + * + * Takes two numbers and rolls a percentage die against a weighted chance + * based on these two numbers, returning whether the first number was + * successful in the roll. Same numbers will always have 50/50 chance, but + * different numbers will always be fit on a 0% to 100% chance of success for + * the first. + * + * Eg. first = 25, second = 50. There is a 33% chance that first will be + * successful and this method will return true. + * + * Can also be capped, which sets both min and max percentage. The default + * cap of 5 will result in chances between 5% and 95%, even if the numbers + * would otherwise have worse/better chances. + */ + return RAND_N(100) < std::max(cap, std::min(100 - cap, (first * 100) / (first + second))); +} diff --git a/Main/Source/gear.cpp b/Main/Source/gear.cpp index 48642ae6a..cc3563159 100644 --- a/Main/Source/gear.cpp +++ b/Main/Source/gear.cpp @@ -427,7 +427,8 @@ void meleeweapon::AddInventoryEntry(ccharacter* Viewer, festring& Entry, if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", DAM " << GetBaseMinDamage() << '-' << GetBaseMaxDamage(); @@ -646,7 +647,8 @@ void armor::AddInventoryEntry(ccharacter*, festring& Entry, int Amount, truth Sh } if(ShowSpecialInfo) - Entry << " [" << GetWeight() * Amount << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight() * Amount); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", AV " << GetStrengthValue() << ']'; @@ -659,7 +661,8 @@ void shield::AddInventoryEntry(ccharacter* Viewer, festring& Entry, if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", AV " << GetStrengthValue(); @@ -1408,7 +1411,8 @@ void taiaha::AddInventoryEntry(ccharacter* Viewer, festring& Entry, int, truth S if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", DAM " << GetBaseMinDamage() << '-' << GetBaseMaxDamage(); @@ -1913,7 +1917,8 @@ void chastitybelt::AddInventoryEntry(ccharacter*, festring& Entry, int Amount, t } if(ShowSpecialInfo) - Entry << " [" << GetWeight() * Amount << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight() * Amount); if(ivanconfig::IsShowVolume()) Entry << ", " << GetVolume() << "cm3"; Entry << ", AV " << GetStrengthValue(); diff --git a/Main/Source/human.cpp b/Main/Source/human.cpp index de8f4088a..d6ee3e5e7 100644 --- a/Main/Source/human.cpp +++ b/Main/Source/human.cpp @@ -1889,7 +1889,7 @@ truth humanoid::SwitchToCraft(recipedata rpd) {DBGLN; craft* Craft = craft::Spawn(this);DBGLN; DBG4(rpd.GetTool(),rpd.GetTool2(),GetRightArm()?GetRightArm()->IsUsable():0,GetLeftArm()?GetLeftArm()->IsUsable():0); - + bool b1OK=false; bool b2OK=false; item* it; @@ -1901,7 +1901,7 @@ truth humanoid::SwitchToCraft(recipedata rpd) b1OK=true; Craft->SetMoveCraftTool(false); } - + if(!b1OK && GetRightArm() && GetRightArm()->IsUsable()){ if((it = GetRightWielded())){ Craft->SetRightBackupID(it->GetID()); @@ -1914,7 +1914,7 @@ truth humanoid::SwitchToCraft(recipedata rpd) b1OK=true; Craft->SetMoveCraftTool(true); } - + if(!b1OK && GetLeftArm() && GetLeftArm()->IsUsable()){ if((it = GetLeftWielded())){ Craft->SetLeftBackupID(it->GetID()); @@ -1927,11 +1927,11 @@ truth humanoid::SwitchToCraft(recipedata rpd) b1OK=true; Craft->SetMoveCraftTool(true); } - + }else{ b1OK=true; //can craft somethings w/o tools } - + //TODO let the GetTool2() be equipped too? if(b1OK){ @@ -1939,7 +1939,7 @@ truth humanoid::SwitchToCraft(recipedata rpd) SetAction(Craft);DBGLN; return true; } - + ADD_MESSAGE("You have no usable arm."); rpd.SetAlreadyExplained(); return false; @@ -5390,7 +5390,7 @@ void FixSumoWrestlerHouse(festring fsCmdParams) break; } } - + if(SM){ for(int d = 0; d < SM->GetNeighbourSquares(); ++d) { @@ -5412,7 +5412,7 @@ void sumowrestler::GetAICommand() devcons::AddDevCmd("FixSumoHouse",FixSumoWrestlerHouse, "BugFix sumo wrestler house in case banana growers over crowd it."); return true;}(); - + EditNP(-25); SeekLeader(GetLeader()); @@ -7031,7 +7031,7 @@ truth mirrorimp::DrinkMagic(const beamdata& Beam) if(!Beam.Wand->IsExplosive()) return false; - if(Beam.Owner && RAND_N(GetAttribute(MANA)) <= RAND_N(Beam.Owner->GetAttribute(WILL_POWER))) + if(Beam.Owner && game::OpposedCheck(Beam.Owner->GetAttribute(WILL_POWER), GetAttribute(MANA))) { Beam.Owner->EditExperience(WILL_POWER, 100, 1 << 12); return false; @@ -7053,9 +7053,12 @@ truth mirrorimp::DrinkMagic(const beamdata& Beam) void mirrorimp::CreateCorpse(lsquare* Square) { - decoration* Shard = decoration::Spawn(SHARD); - Shard->InitMaterials(MAKE_MATERIAL(GLASS)); - Square->ChangeOLTerrainAndUpdateLights(Shard); + // Do not replace any interesting terrain! + if (!Square->GetOLTerrain()) { + decoration* Shard = decoration::Spawn(SHARD); + Shard->InitMaterials(MAKE_MATERIAL(GLASS)); + Square->ChangeOLTerrainAndUpdateLights(Shard); + } SendToHell(); } @@ -7227,7 +7230,7 @@ void aslonawizard::GetAICommand() } if(NearestEnemy && NearestEnemy->GetPos().IsAdjacent(Pos) && - (!(RAND() & 4) || StateIsActivated(PANIC))) + (!(RAND() % 4) || StateIsActivated(PANIC))) { if(CanBeSeenByPlayer()) ADD_MESSAGE("%s invokes a spell and disappears.", CHAR_NAME(DEFINITE)); @@ -7285,8 +7288,8 @@ void aslonawizard::GetAICommand() EditAP(-GetSpellAPCost()); - int GasMaterial[] = { MUSTARD_GAS, MAGIC_VAPOUR, SLEEPING_GAS, TELEPORT_GAS, - EVIL_WONDER_STAFF_VAPOUR, EVIL_WONDER_STAFF_VAPOUR }; + const int GasMaterial[] = { MUSTARD_GAS, MAGIC_VAPOUR, SLEEPING_GAS, TELEPORT_GAS, + EVIL_WONDER_STAFF_VAPOUR, EVIL_WONDER_STAFF_VAPOUR }; ToBeCalled = golem::Spawn(GasMaterial[RAND() % 6]); v2 Where = GetLevel()->GetNearestFreeSquare(ToBeCalled, Square->GetPos()); diff --git a/Main/Source/iconf.cpp b/Main/Source/iconf.cpp index 3d98f062d..4150eae95 100644 --- a/Main/Source/iconf.cpp +++ b/Main/Source/iconf.cpp @@ -85,7 +85,7 @@ cycleoption ivanconfig::HoldPosMaxDist( "HoldPosMaxDist", cycleoption ivanconfig::ShowItemsAtPlayerSquare("ShowItemsAtPlayerSquare", "Show items at player's square", "", - 0, 12, + 5, 12, &ShowItemsAtPlayerSquareDisplayer, &configsystem::NormalCycleChangeInterface, &ShowItemsAtPlayerSquareChanger); @@ -152,13 +152,13 @@ truthoption ivanconfig::TransparentMapLM( "TransparentMapLM", "Show transparent map of the whole level when in look mode.", true); truthoption ivanconfig::AllowImportOldSavegame("AllowImportOldSavegame", - "Import old savegames (v131 up, experimental)", - "", + "Import old or corrupted save files", + "Attempt to import save files that may be incompatible with the current version of the game, or corrupted. May still result in unstable gameplay.", false); truthoption ivanconfig::WaitNeutralsMoveAway("WaitNeutralsMoveAway", "Wait until neutral NPCs move from your path", "When you try to move in a direction that is blocked by a neutral NPC, skip turns until the path is clear. Will not skip turns if the NPC doesn't move from their square, or if there are hostiles nearby.", - false); + true); truthoption ivanconfig::AllWeightIsRelevant("AllWeightIsRelevant", "Only pile items with equal weight on lists", //clutter are useful now for crafting so their weight matters... "", @@ -179,42 +179,46 @@ truthoption ivanconfig::UseLightEmiterBasedOnVolume("UseLightEmiterBasedOnVolume "Small light sources emit less light", "This experimental feature still has bugs that happen when splitting rocks etc. Most are fixed after restarting the game.", false); +truthoption ivanconfig::CraftingEnabled( "CraftingEnabled", + "Enable crafting", + "Crafting is an experimental system and may cause issues.", + false); truthoption ivanconfig::ShowFullDungeonName("ShowFullDungeonName", "Show full name of current dungeon", "", false); truthoption ivanconfig::ShowGodInfo( "ShowGodInfo", - "Show extra info about gods when praying", + "Extra info about gods", "Remember the last response to a prayer for each god.", - false); + true); truthoption ivanconfig::CenterOnPlayerAfterLook("CenterOnPlayerAfterLook", "Center camera on player after exiting look mode", "Always center the displayed region of the dungeon back on player after exiting look mode.", - false); + true); truthoption ivanconfig::WarnAboutDanger( "WarnAboutVeryDangerousMonsters", "Warn about very dangerous monsters", "Display a warning prompt when you encounter an unusually dangerous monster.", true); truthoption ivanconfig::AutoDropLeftOvers("AutoDropLeftOvers", "Drop food leftovers automatically", - "", + "If there are any remains of your edibles (empty bottle, fish skeleton), drop them automatically.", true); truthoption ivanconfig::LookZoom( "LookZoom", "Zoom in look mode", - "", - false); -truthoption ivanconfig::AltAdentureInfo( "AltAdentureInfo", - "Enhanced message review mode after death", - "", - false); + "Show a zoomed-in view of selected square in look mode.", + true); +truthoption ivanconfig::AltAdventureInfo( "AltAdventureInfo", + "Extra info after death", + "Show more information about your adventure, once it comes to its likely quite bloody end.", + true); truthoption ivanconfig::DescriptiveHP( "DescriptiveHP", "Use health level descriptions", "Display description of your relative health rather than numeric value of your hit points.", false); -truthoption ivanconfig::StartWithNoPet( "StartWithNoPet", - "Start with no pet", - "Do not start the game with a puppy.", - false); +truthoption ivanconfig::StartWithPet( "StartWithPet", + "Start with a pet", + "Start the game with a happy little puppy who likes you very much.", + true); cycleoption ivanconfig::MemorizeEquipmentMode("MemorizeEquipmentMode", "NPCs restore equipped items after polymorph", "", @@ -254,8 +258,8 @@ cycleoption ivanconfig::DistLimitMagicMushrooms("DistLimitMagicMushrooms", 0, 5, &DistLimitMagicMushroomsDisplayer); cycleoption ivanconfig::SaveGameSortMode( "SaveGameSortMode", - "Sort savegame files by dungeon IDs", - "Savegame selection menu will be sorted according to the chosen criterion.", + "Sort save files by", + "Saved game selection menu will be sorted according to the chosen criterion.", 0, 4, &SaveGameSortModeDisplayer, &configsystem::NormalCycleChangeInterface, @@ -279,12 +283,12 @@ cycleoption ivanconfig::AltSilhouettePreventColorGlitch("AltSilhouettePreventCol &AltSilhouettePreventColorGlitchDisplayer); cycleoption ivanconfig::DirectionKeyMap( "DirectionKeyMap", "Movement control scheme", - "Select a pre-defined keybinding scheme for the movement of your character. Normal scheme uses NumPad, or arrow keys along with Home, End, PgUp and PgDn for diagonal directions. Alternative scheme is better suited for laptops and uses number and letter keys on the main keyboard. NetHack scheme uses vi keys. After you select a movement control scheme, you may also check the in game keybindings help to see the currently active movement keys. Any other command keys may be auto changed also to not conflict with this movement keys choice.", + "Select a pre-defined keybinding scheme for the movement of your character. Normal scheme uses NumPad, or arrow keys along with Home, End, PgUp and PgDn for diagonal directions. Alternative scheme is better suited for laptops and uses number and letter keys on the main keyboard. NetHack scheme uses vi keys. After you select a movement control scheme, you may also check the in-game keybindings help (press '?') to see the currently active movement keys. Other command keys may be changed to avoid conflict with the movement keys.", DIR_NORM, 3, // {default value, number of options to cycle through} &DirectionKeyMapDisplayer); truthoption ivanconfig::SetupCustomKeys( "SetupCustomKeys", - "Custom command and movement", //TODO all keys one day, and let it work on main menu - "Lets you assign any command to any key binding of your preference. The default keys here will be from the control scheme option above. Only changed keybindings will be saved at the new config file. This global configuration won't work at main menu, load/start some game.", + "Custom control scheme", //TODO all keys one day, and let it work on main menu + "Lets you assign any command to any key binding of your preference. The default keys are defined by the movement control scheme chosen in the option above.", // This configuration won't work at main menu, load/start some game. false, &configsystem::NormalTruthDisplayer, &configsystem::NormalTruthChangeInterface, @@ -325,30 +329,29 @@ scrollbaroption ivanconfig::SfxVolume( "SfxVolume", &SfxVolumeChangeInterface, &SfxVolumeChanger, &SfxVolumeHandler); - cycleoption ivanconfig::MIDIOutputDevice( "MIDIOutputDevice", "Use MIDI soundtrack", "Select an output device for the game music, or disable soundtrack.", 0, 0, // {default value, number of options to cycle through} &MIDIOutputDeviceDisplayer); -cycleoption ivanconfig::LandTypeConfig("LandTypeConfig", - "What land shapes to generate", +cycleoption ivanconfig::LandTypeConfig( "LandTypeConfig", + "Shape of the continents", "Choose whether to generate continents or pangea. If Pangea is selected, the generator will make all locations reachable from the same landmass.", 0, 2, &LandTypeConfigDisplayer); -cycleoption ivanconfig::WorldSizeConfig("WorldSizeConfig", - "Size of the world map", +cycleoption ivanconfig::WorldSizeConfig( "WorldSizeConfig", + "Size of the world", "Select a world size.", 2, 7, &WorldSizeConfigDisplayer); -cycleoption ivanconfig::WorldShapeConfig("WorldShapeConfig", +cycleoption ivanconfig::WorldShapeConfig( "WorldShapeConfig", "Shape of the world", "This affects the player's movement around the world. Pancake worlds are flat, and the player cannot cross the edges of the world map. Brandy snap worlds are like a cylinder, the world map wraps around the horizontal axis. Doughnut worlds are shaped like a torus, the player can wrap around the horizontal and vertical axes.", 0, 3, &WorldShapeConfigDisplayer); -numberoption ivanconfig::WorldSeedConfig("WorldSeedConfig", +numberoption ivanconfig::WorldSeedConfig( "WorldSeedConfig", "Select a world seed", - "0 gives a random world seed, else select a new one at your own risk. If a world cannot be generated with the given seed after a finite number of attempts, you will get a message saying the world generator encountered a bad seed, what that seed was, and a new world will be generated from a random seed instead of the one specified here.", + "Selecting 0 gives you a random world seed, or select a new one at your own risk. If a world cannot be generated with the given seed after a finite number of attempts, you will get a message saying the world generator encountered a bad seed, what that seed was, and a new world will be generated from a random seed instead of the one specified here.", 0, &WorldSeedConfigDisplayer, &WorldSeedConfigChangeInterface, @@ -365,7 +368,7 @@ cycleoption ivanconfig::GraphicsScale( "GraphicsScale", truthoption ivanconfig::FullScreenMode( "FullScreenMode", "Full screen mode", "Display the game in full screen mode.", - false, + true, &configsystem::NormalTruthDisplayer, &configsystem::NormalTruthChangeInterface, &FullScreenModeChanger); @@ -375,7 +378,7 @@ cycleoption ivanconfig::ScalingQuality( "ScalingQuality", 0, 2, &ScalingQualityDisplayer); truthoption ivanconfig::UseExtraMenuGraphics("UseExtraMenuGraphics", - "Use extra main menu graphics", + "Use alternate main menu graphics", "Add changing graphics and sounds to the main menu.", false, &configsystem::NormalTruthDisplayer, @@ -388,7 +391,7 @@ truthoption ivanconfig::PlaySounds( "PlaySounds", "Use sound effects for combat, explosions and more.", true); truthoption ivanconfig::ShowTurn( "ShowTurn", - "Show game turn on message log", + "Show game turn in message log", "Add a game turn number to each action described in the message log.", false); truthoption ivanconfig::OutlinedGfx( "OutlinedGfx", @@ -506,19 +509,19 @@ void ivanconfig::ShowMapDisplayer(const cycleoption* O, festring& Entry) void ivanconfig::ShowItemsAtPlayerSquareDisplayer(const cycleoption* O, festring& Entry) { - if(O->Value>=10){ - Entry << "Use corners if NPC"; - if(O->Value==11)Entry << "+Items"; + if(O->Value >= 10) { + Entry << "corners if NPC"; + if(O->Value == 11) + Entry << " or items"; Entry << " above"; return; } int iCode = game::ItemUnderCode(O->Value); - - if(iCode==0){ + if(iCode == 0){ Entry << "disabled"; }else - if(iCode==1){ + if(iCode == 1){ Entry << "above head"; }else{ switch(game::ItemUnderCorner(iCode)){ @@ -992,7 +995,7 @@ void ivanconfig::WorldShapeConfigDisplayer(const cycleoption* O, festring& Entry v2 ivanconfig::GetWorldSizeConfig() { v2 WorldSize = v2(49, 49); - + if(WorldSizeConfig.Value == HUGE_WORLD) WorldSize = v2(128, 128); else if(WorldSizeConfig.Value == LARGE_WORLD) @@ -1007,7 +1010,7 @@ v2 ivanconfig::GetWorldSizeConfig() WorldSize = v2(84, 52); else WorldSize = v2(49, 49); //SMALL_WORLD - + return WorldSize; } @@ -1217,6 +1220,7 @@ int ivanconfig::iStartingWindowHeight=-1; int ivanconfig::iStartingDungeonGfxScale=-1; int ivanconfig::iStartingFontGfx=-1; bool ivanconfig::bStartingOutlinedGfx=false; + void ivanconfig::Initialize() { festring fsCategory; @@ -1224,21 +1228,21 @@ void ivanconfig::Initialize() fsCategory="General Setup"; configsystem::AddOption(fsCategory,&DefaultName); configsystem::AddOption(fsCategory,&FantasyNamePattern); + configsystem::AddOption(fsCategory,&StartWithPet); configsystem::AddOption(fsCategory,&DefaultPetName); - configsystem::AddOption(fsCategory,&StartWithNoPet); configsystem::AddOption(fsCategory,&AutoSaveInterval); - configsystem::AddOption(fsCategory,&AltAdentureInfo); - fsCategory="Gameplay Options"; + fsCategory="Gameplay"; configsystem::AddOption(fsCategory,&BeNice); configsystem::AddOption(fsCategory,&HoldPosMaxDist); - //configsystem::AddOption(fsCategory,&MemorizeEquipmentMode); // Let everyone retore equipped items on unpolymorph. + //configsystem::AddOption(fsCategory,&MemorizeEquipmentMode); // Let everyone restore equipped items on unpolymorph. configsystem::AddOption(fsCategory,&WarnAboutDanger); configsystem::AddOption(fsCategory,&AutoDropLeftOvers); configsystem::AddOption(fsCategory,&SmartOpenCloseApply); configsystem::AddOption(fsCategory,&CenterOnPlayerAfterLook); configsystem::AddOption(fsCategory,&DescriptiveHP); - configsystem::AddOption(fsCategory,&ShowGodInfo); //gameplay change in a sense that, to remember what each god is about may be a challenge on itself :) + configsystem::AddOption(fsCategory,&AltAdventureInfo); + configsystem::AddOption(fsCategory,&ShowGodInfo); configsystem::AddOption(fsCategory,&ShowMapAtDetectMaterial); configsystem::AddOption(fsCategory,&GoOnStopMode); configsystem::AddOption(fsCategory,&WaitNeutralsMoveAway); @@ -1247,19 +1251,32 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&EnhancedLights); configsystem::AddOption(fsCategory,&DistLimitMagicMushrooms); configsystem::AddOption(fsCategory,&AutoPickupThrownItems); - configsystem::AddOption(fsCategory,&AutoPickUpMatching); - fsCategory="Game Window"; + fsCategory="Sounds"; + configsystem::AddOption(fsCategory,&PlaySounds); + + std::vector DeviceNames; + int NumDevices = audio::GetMIDIOutputDevices(DeviceNames); + MIDIOutputDevice.Value = 0; + if( NumDevices ) + { + MIDIOutputDevice.Value = 1; + } + MIDIOutputDevice.CycleCount = NumDevices+1; + + configsystem::AddOption(fsCategory,&MIDIOutputDevice); + configsystem::AddOption(fsCategory,&Volume); + configsystem::AddOption(fsCategory,&SfxVolume); + + fsCategory="Graphics"; +#ifndef __DJGPP__ + configsystem::AddOption(fsCategory,&FullScreenMode); +#endif configsystem::AddOption(fsCategory,&Contrast); configsystem::AddOption(fsCategory,&WindowWidth); configsystem::AddOption(fsCategory,&WindowHeight); #ifndef __DJGPP__ configsystem::AddOption(fsCategory,&GraphicsScale); - configsystem::AddOption(fsCategory,&FullScreenMode); -#endif - - fsCategory="Graphics"; -#ifndef __DJGPP__ configsystem::AddOption(fsCategory,&ScalingQuality); #endif configsystem::AddOption(fsCategory,&LookZoom); @@ -1278,26 +1295,11 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&ShowItemsAtPlayerSquare); configsystem::AddOption(fsCategory,&RotateTimesPerSquare); configsystem::AddOption(fsCategory,&HitIndicator); + configsystem::AddOption(fsCategory,&HideWeirdHitAnimationsThatLookLikeMiss); configsystem::AddOption(fsCategory,&ShowMap); configsystem::AddOption(fsCategory,&TransparentMapLM); configsystem::AddOption(fsCategory,&UseExtraMenuGraphics); - fsCategory="Sounds"; - configsystem::AddOption(fsCategory,&PlaySounds); - - std::vector DeviceNames; - int NumDevices = audio::GetMIDIOutputDevices(DeviceNames); - MIDIOutputDevice.Value = 0; - if( NumDevices ) - { - MIDIOutputDevice.Value = 1; - } - MIDIOutputDevice.CycleCount = NumDevices+1; - - configsystem::AddOption(fsCategory,&MIDIOutputDevice); - configsystem::AddOption(fsCategory,&Volume); - configsystem::AddOption(fsCategory,&SfxVolume); - fsCategory="Input and Interface"; configsystem::AddOption(fsCategory,&DirectionKeyMap); configsystem::AddOption(fsCategory,&SetupCustomKeys); @@ -1307,20 +1309,21 @@ void ivanconfig::Initialize() configsystem::AddOption(fsCategory,&SelectedBkgColor); configsystem::AddOption(fsCategory,&AllowMouseOnFelist); - fsCategory="Advanced Options"; - configsystem::AddOption(fsCategory,&AllowImportOldSavegame); - configsystem::AddOption(fsCategory,&HideWeirdHitAnimationsThatLookLikeMiss); - configsystem::AddOption(fsCategory,&UseLightEmiterBasedOnVolume); - fsCategory="World Generation"; configsystem::AddOption(fsCategory, &WorldSizeConfig); - configsystem::AddOption(fsCategory, &LandTypeConfig); configsystem::AddOption(fsCategory, &WorldShapeConfig); + configsystem::AddOption(fsCategory, &LandTypeConfig); configsystem::AddOption(fsCategory, &WorldSeedConfig); //World shape: Flat, [Horizontal Wrap (cylinder)] // Alt names for world shape: pancake (flat), doughnut (torus), brandy snap (cylinder). + fsCategory="Advanced and Experimental"; + configsystem::AddOption(fsCategory,&AllowImportOldSavegame); + configsystem::AddOption(fsCategory,&UseLightEmiterBasedOnVolume); + configsystem::AddOption(fsCategory,&CraftingEnabled); + configsystem::AddOption(fsCategory,&AutoPickUpMatching); + /******************************** * LOAD AND APPLY some SETTINGS * ********************************/ @@ -1341,7 +1344,7 @@ void ivanconfig::Initialize() CalculateContrastLuminance(); audio::ChangeMIDIOutputDevice(MIDIOutputDevice.Value); audio::SetVolumeLevel(Volume.Value); - + if(ivanconfig::IsSetupCustomKeys()) game::LoadCustomCommandKeys(); diff --git a/Main/Source/item.cpp b/Main/Source/item.cpp index 5f44f2d1a..830074cdf 100644 --- a/Main/Source/item.cpp +++ b/Main/Source/item.cpp @@ -864,8 +864,11 @@ void item::AddInventoryEntry(ccharacter*, festring& Entry, int Amount, truth Sho } if(ShowSpecialInfo){ - Entry << " [" << GetWeight() * Amount << "g"; //TODO if the 1st and 2nd of 3 items have 100g and the last has 2000g, the weight shown would be 300g ... now that lumps, stones and sticks are useful, this may not be that good... - if(ivanconfig::IsShowVolume()){ + //TODO if the 1st and 2nd of 3 items have 100g and the last has 2000g, the weight shown would be 300g ... now that lumps, stones and sticks are useful, this may not be that good... + Entry << " ["; + Entry.PutWeight(GetWeight() * Amount); + + if(ivanconfig::IsShowVolume()) { Entry << " " << GetVolume() * Amount << "cm3"; //the item can be seen therefore it's volume guessed already if(GetSecondaryMaterial()==NULL){ //simple items like ingots sticks etc static char density[20]; diff --git a/Main/Source/lsquare.cpp b/Main/Source/lsquare.cpp index d07aa1dbe..4de4d125a 100644 --- a/Main/Source/lsquare.cpp +++ b/Main/Source/lsquare.cpp @@ -1895,7 +1895,7 @@ truth lsquare::LowerEnchantment(const beamdata& Beam) if(Char) { - if(Beam.Owner && RAND_N(Char->GetAttribute(WILL_POWER)) > RAND_N(Beam.Owner->GetAttribute(MANA))) + if(Beam.Owner && game::OpposedCheck(Char->GetAttribute(WILL_POWER), Beam.Owner->GetAttribute(MANA))) { if(Char->IsPlayer()) ADD_MESSAGE("%s glows dull brown for a second, but then it passes.", RandomItem->CHAR_NAME(DEFINITE)); diff --git a/Main/Source/miscitem.cpp b/Main/Source/miscitem.cpp index 72e88b3b8..6cfc24a5d 100644 --- a/Main/Source/miscitem.cpp +++ b/Main/Source/miscitem.cpp @@ -1810,7 +1810,8 @@ void wand::AddInventoryEntry(ccharacter*, festring& Entry, int, truth ShowSpecia if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; @@ -2735,7 +2736,8 @@ void holybanana::AddInventoryEntry(ccharacter* Viewer, festring& Entry, int, tru if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", DAM " << GetBaseMinDamage() << '-' << GetBaseMaxDamage(); @@ -3087,7 +3089,7 @@ void scrollofdetectmaterial::FinishReading(character* Reader) { ADD_MESSAGE("An enormous burst of geographical information overwhelms your " "consciousness. Your mind cannot cope with it and your memories blur."); - Level->BlurMemory(); + Level->BlurMemory(); // !game::IsInWilderness() is already handled by reading command Reader->BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000); Reader->EditExperience(INTELLIGENCE, -100, 1 << 12); } @@ -3436,12 +3438,13 @@ void holyhandgrenade::AddInventoryEntry(ccharacter* Viewer, festring& Entry, int if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(!!WillExplodeSoon()) - Entry << ", " << "(armed)"; + Entry << ", armed"; - Entry << ']'; + Entry << "]"; } } @@ -3829,7 +3832,8 @@ void ullrbone::AddInventoryEntry(const character* Viewer, festring& Entry, int, if(ShowSpecialInfo) { - Entry << " [" << GetWeight() << "g"; + Entry << " ["; + Entry.PutWeight(GetWeight()); if(ivanconfig::IsShowVolume()) Entry << " " << GetVolume() << "cm3"; Entry << ", DAM " << GetBaseMinDamage() << '-' << GetBaseMaxDamage(); diff --git a/Main/Source/nonhuman.cpp b/Main/Source/nonhuman.cpp index 326d97d51..23a9eb639 100644 --- a/Main/Source/nonhuman.cpp +++ b/Main/Source/nonhuman.cpp @@ -164,9 +164,10 @@ truth feline::Catches(item* Thingy) if(CanBeSeenByPlayer()) ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE)); - if(PLAYER->GetRelativeDanger(this, true) > 0.1) + if(PLAYER->GetRelativeDanger(this, true) > 0.1) { ChangeTeam(PLAYER->GetTeam()); ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE)); + } } } else if(IsPlayer()) @@ -1051,10 +1052,10 @@ int nerfbat::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 if(Return != HAS_DIED) { // Compare Mana against enemy Willpower to see if they resist polymorph. - if(RAND_N(GetAttribute(MANA)) > RAND_N(Enemy->GetAttribute(WILL_POWER))) + if(game::OpposedCheck(GetAttribute(MANA), Enemy->GetAttribute(WILL_POWER))) { if(IsPlayer()) - ADD_MESSAGE("You are engulfed in a malignant aura!."); + ADD_MESSAGE("You are engulfed in a malignant aura!"); else if(CanBeSeenByPlayer()) ADD_MESSAGE("%s is engulfed in a malignant aura!", CHAR_DESCRIPTION(DEFINITE)); @@ -2085,7 +2086,7 @@ void mommo::GetAICommand() void dog::GetAICommand() { if(!game::IsInWilderness() && !(RAND() & 7)) - GetLSquareUnder()->SpillFluid(this, liquid::Spawn(DOG_DROOL, 25 + RAND() % 50), false, false); + GetLSquareUnder()->SpillFluid(this, liquid::Spawn(GetConfig() == SKELETON_DOG ? BLOOD : DOG_DROOL, 25 + RAND() % 50), false, false); character::GetAICommand(); } @@ -2558,7 +2559,7 @@ void lobhse::FinalProcessForBone() void lobhse::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit) { - if(!RAND_N(7)) + if(!RAND_N(4)) { if(IsPlayer()) ADD_MESSAGE("You vomit at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE)); diff --git a/Script/char.dat b/Script/char.dat index 7ce6c94fb..761568c7e 100644 --- a/Script/char.dat +++ b/Script/char.dat @@ -3125,7 +3125,6 @@ golem DefaultCharisma = 5; DefaultMana = 5; TotalVolume = 100000; - TorsoBitmapPos = 256, 0; TotalSize = 250; NameSingular = "golem"; CanBeGenerated = true; @@ -3168,12 +3167,11 @@ golem AttachedGod = NONE; ClassStates = GAS_IMMUNITY; Frequency = 100; - DangerModifier = 2000; + DangerModifier = 1000; DayRequirementForGeneration = 3; CanTalk = false; CanRead = true; BodyPartsDisappearWhenSevered = true; - DangerModifier = 75; AlwaysUseMaterialAttributes = true; IsEnormous = true; CanChoke = false; @@ -5917,7 +5915,6 @@ gibberling LegBitmapPos = 16, 128; TotalVolume = 30000; TotalSize = 90; - SkinColor = rgb16(100, 100, 200); NameSingular = "gibberling"; CanBeGenerated = true; Sex = UNDEFINED; @@ -6122,7 +6119,6 @@ angel Sex = FEMALE; ClassStates = ESP|GAS_IMMUNITY|TELEPORT_CONTROL; TotalVolume = 60000; - TorsoBitmapPos = 432, 0; TotalSize = 200; CanRead = true; NameSingular = "angel"; @@ -6788,7 +6784,6 @@ orc Belt = RUBY belt { Enchantment = 2; } RightGauntlet = DRAGON_HIDE gauntlet(GAUNTLET_OF_STRENGTH) { Enchantment = 2; } RightBoot = SPIDER_SILK boot { Enchantment = 2; } - LeftWielded = 0; KnownCWeaponSkills == POLE_ARMS; CWeaponSkillHits == 2000; RightSWeaponSkillHits = 1000; @@ -7662,7 +7657,6 @@ eddy HasEyes = false; HasHead = false; HasALeg = false; - CanBeGenerated = true; IgnoreDanger = true; HPRequirementForGeneration = 50; DayRequirementForGeneration = 5; @@ -7836,7 +7830,6 @@ darkmage DayRequirementForGeneration = 15; Frequency = 1500; PanicLevel = 75; - Inventory == lantern; CWeaponSkillHits == 50; RightSWeaponSkillHits = 20; LeftSWeaponSkillHits = 20; @@ -8610,7 +8603,6 @@ necromancer IsUnique = true; CanRead = true; IsPolymorphable = false; - PanicLevel = 0; CanBeCloned = false; BodyPartsDisappearWhenSevered = true; CanBeConfused = false; @@ -9274,7 +9266,6 @@ siren DefaultIntelligence = 10; DefaultWillPower = 10; DefaultWisdom = 15; - DefaultCharisma = 50; DefaultMana = 5; Sex = FEMALE; StandVerb = "flaunting"; @@ -9319,6 +9310,7 @@ siren Config LIGHT_ASIAN_SIREN; { + DefaultCharisma = 45; UsesLongAdjectiveArticle = true; NameSingular = "erd-siren"; SkinColor = rgb16(254, 247, 208); @@ -9328,6 +9320,7 @@ siren Config DARK_ASIAN_SIREN; { + DefaultCharisma = 42; UsesLongAdjectiveArticle = true; NameSingular = "ygg-siren"; SkinColor = rgb16(254, 247, 183); @@ -9337,6 +9330,7 @@ siren Config CAUCASIAN_SIREN; { + DefaultCharisma = 39; UsesLongAdjectiveArticle = true; NameSingular = "ary-siren"; SkinColor = rgb16(255, 212, 192); @@ -9346,6 +9340,7 @@ siren Config DARK_SIREN; { + DefaultCharisma = 36; UsesLongAdjectiveArticle = true; NameSingular = "unn-siren"; SkinColor = rgb16(128, 80, 48); @@ -9355,6 +9350,7 @@ siren Config GREEN_SIREN; { + DefaultCharisma = 33; UsesLongAdjectiveArticle = true; NameSingular = "eli-siren"; SkinColor = rgb16(180, 255, 150); @@ -9364,6 +9360,7 @@ siren Config BLUE_SIREN; { + DefaultCharisma = 30; NameSingular = "zar-siren"; SkinColor = rgb16(153, 204, 255); HairColor = rgb16(51, 51, 255); @@ -9373,6 +9370,7 @@ siren Config RED_SIREN; { + DefaultCharisma = 27; NameSingular = "pra-siren"; SkinColor = rgb16(255, 51, 51); HairColor = rgb16(255, 255, 255); @@ -9382,6 +9380,7 @@ siren Config PINK_SIREN; { + DefaultCharisma = 24; NameSingular = "jin-siren"; SkinColor = rgb16(240, 200, 201); HairColor = rgb16(255, 51, 153); @@ -9391,6 +9390,7 @@ siren Config HISPANIC_SIREN; { + DefaultCharisma = 21; NameSingular = "kos-siren"; SkinColor = rgb16(160, 100, 64); HairColor = rgb16(80, 48, 32); @@ -9402,7 +9402,7 @@ siren DefaultIntelligence = 30; DefaultWillPower = 30; DefaultWisdom = 35; - DefaultCharisma = 75; + DefaultCharisma = 50; HeadBitmapPos = 112, 400; SkinColor = rgb16(180, 255, 150); HairColor = rgb16(200, 48, 32); diff --git a/Script/dungeons/GoblinFort.dat b/Script/dungeons/GoblinFort.dat index fa40ab526..efd6e5867 100644 --- a/Script/dungeons/GoblinFort.dat +++ b/Script/dungeons/GoblinFort.dat @@ -1017,6 +1017,7 @@ Dungeon GOBLIN_FORT; { AllowBoobyTrappedDoors = false; Flags = NO_MONSTER_GENERATION; + Size = 4:7,4:7; Square, Random HAS_NO_OTERRAIN; { @@ -1041,6 +1042,7 @@ Dungeon GOBLIN_FORT; { AllowBoobyTrappedDoors = false; Flags = NO_MONSTER_GENERATION; + Size = 4:7,4:7; Square, Random HAS_NO_OTERRAIN; { @@ -1065,6 +1067,7 @@ Dungeon GOBLIN_FORT; { AllowBoobyTrappedDoors = false; Flags = NO_MONSTER_GENERATION; + Size = 5:7,5:7; Square, Random HAS_NO_OTERRAIN; { diff --git a/Script/item.dat b/Script/item.dat index a4f6f84c3..7ff5066df 100644 --- a/Script/item.dat +++ b/Script/item.dat @@ -1821,7 +1821,6 @@ holybanana /* materialcontainer->banana-> */ CanBeMirrored = true; /* will create a peel */ AllowEquip = true; IsValuable = true; - CanBePiled = false; IsMaterialChangeable = false; DescriptiveInfo = "Oily Orpiv was an explorer turned merchant. His commercial empire began its rise when on one expedition, he discovered a small village that never had to deal with the hardships of agriculture because of a seemingly miraculous crop. Oily Orpiv had discovered the banana. Being a follower of Mellis, he saw the monetary potential in this new fruit, subjugated the people and turned them into a highly efficient banana production facility. He made a quick profit that he soon used as investment for further business ventures. It is said that Oily Orpiv saved the third banana he ever saw (he ate the first and sold the second, claiming it was the first one) as a keepsake. When later in his life he became the champion of Mellis, this banana was imbued with powers both beneficial and deadly by Oily Orpiv's appreciative patron."; } @@ -5112,7 +5111,6 @@ itemcontainer OAK_WOOD, TEAK_WOOD, EBONY_WOOD, KAURI_WOOD, RATA_WOOD; } MaterialConfigChances = { 20, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 500, 5, 5, 5, 5, 5, 5; } - Roundness = 60; CanBePiled = false; IsAbstract = true; IsPolymorphSpawnable = false; @@ -7545,6 +7543,7 @@ fish BitmapPos = 80, 336; Adjective = "dead"; NameSingular = "fish"; + NamePlural = "fish"; Alias == "sardine"; MainMaterialConfig == SARDINE; AttachedGod = SEGES; diff --git a/Script/material.dat b/Script/material.dat index 90afbd268..2082661b1 100644 --- a/Script/material.dat +++ b/Script/material.dat @@ -4575,7 +4575,6 @@ organic /* Substances that spoil but are not flesh. */ Color = rgb16(244, 241, 235); NutritionValue = 1000; NameStem = "ommel bone"; - ConsumeEndMessage = CEM_BONE; SpoilModifier = 48000; AttachedGod = MORTIFER; SoftenedMaterial = DRAGON_BONE; @@ -4651,7 +4650,6 @@ organic /* Substances that spoil but are not flesh. */ Color = rgb16(200, 200, 255); NutritionValue = 1000; NameStem = "ommel tooth"; - ConsumeEndMessage = CEM_BONE; SpoilModifier = 48000; AttachedGod = MORTIFER; SoftenedMaterial = MAMMOTH_TUSK; @@ -5625,7 +5623,7 @@ liquid Alpha = 175; Flexibility = 40; AttachedGod = CRUENTUS; - Acidicity = 500: + Acidicity = 500; RustModifier = 500; ConsumeWisdomLimit = 4; CategoryFlags = IS_BLOOD; @@ -5955,7 +5953,6 @@ flesh SpoilModifier = 20000; PriceModifier = 9000; AttachedGod = SEGES; - BodyFlags = Base&~IS_WARM_BLOODED; BodyFlags = Base&~CAN_HAVE_PARASITE&~IS_WARM_BLOODED; CategoryFlags = Base|IS_GOLEM_MATERIAL; } @@ -5968,7 +5965,6 @@ flesh SpoilModifier = 20000; PriceModifier = 9000; AttachedGod = SEGES; - BodyFlags = Base&~IS_WARM_BLOODED; BodyFlags = Base&~CAN_HAVE_PARASITE&~IS_WARM_BLOODED; CategoryFlags = Base|IS_GOLEM_MATERIAL; } diff --git a/Script/olterra.dat b/Script/olterra.dat index 76cf72607..329f14eaf 100644 --- a/Script/olterra.dat +++ b/Script/olterra.dat @@ -227,7 +227,6 @@ decoration CanBeDestroyed = true; DigMessage = "You ruthlessly chop the beautiful tree down."; MainMaterialConfig == GOLD; - ShowMaterial = false; MaterialColorB = rgb16(224, 224, 0); Adjective = "holy"; NameSingular = "tree"; @@ -466,7 +465,6 @@ decoration MaterialColorB = rgb16(0, 150, 0); NameSingular = "oak tree"; BitmapPos = 32, 336; - ShowMaterial = true; HPModifier = 25; IsAlwaysTransparent = false; } @@ -962,7 +960,6 @@ sign { DigMessage = "You smash the sign to tiny bits."; MainMaterialConfig == BALSA_WOOD; - MaterialColorB = rgb16(200,200,200); /* Uses own postfix */ NameSingular = "sign"; BitmapPos = 16, 336; @@ -1035,7 +1032,6 @@ barwall NameSingular = "bars"; BitmapPos = 16, 112; HPModifier = 25; - ShowThingsUnder = true; AttachedGod = LEGIFER; Config BROKEN_BARWALL;