diff --git a/Source/control.h b/Source/control.h index 9dc1ed365..005966ca6 100644 --- a/Source/control.h +++ b/Source/control.h @@ -68,8 +68,8 @@ extern int initialDropGoldValue; extern bool panbtndown; extern bool spselflag; -/** - * @brief Check if the UI can cover the game area entierly +/** + * @brief Check if the UI can cover the game area entierly */ inline bool CanPanelsCoverView() { diff --git a/Source/engine/animationinfo.cpp b/Source/engine/animationinfo.cpp index a960b328c..b6b797418 100644 --- a/Source/engine/animationinfo.cpp +++ b/Source/engine/animationinfo.cpp @@ -1,154 +1,154 @@ -/** - * @file animationinfo.cpp - * - * Contains the core animation information and related logic - */ - -#include "animationinfo.h" -#include "appfat.h" -#include "nthread.h" -#include "utils/log.hpp" - -namespace devilution { - -int AnimationInfo::GetFrameToUseForRendering() const -{ - // Normal logic is used, - // - if no frame-skipping is required and so we have exactly one Animationframe per game tick - // or - // - if we load from a savegame where the new variables are not stored (we don't want to break savegame compatiblity because of smoother rendering of one animation) - if (RelevantFramesForDistributing <= 0) - return CurrentFrame; - - if (CurrentFrame > RelevantFramesForDistributing) - return CurrentFrame; - - assert(TicksSinceSequenceStarted >= 0); - - // we don't use the processed game ticks alone but also the fragtion of the next game tick (if a rendering happens between game ticks). This helps to smooth the animations. - float totalTicksForCurrentAnimationSequence = gfProgressToNextGameTick + (float)TicksSinceSequenceStarted; - - // 1 added for rounding reasons. float to int cast always truncate. - int absoluteAnimationFrame = 1 + (int)(totalTicksForCurrentAnimationSequence * TickModifier); - if (SkippedFramesFromPreviousAnimation > 0) { - // absoluteAnimationFrames contains also the Frames from the previous Animation, so if we want to get the current Frame we have to remove them - absoluteAnimationFrame -= SkippedFramesFromPreviousAnimation; - if (absoluteAnimationFrame <= 0) { - // We still display the remains of the previous Animation - absoluteAnimationFrame = NumberOfFrames + absoluteAnimationFrame; - } - } else if (absoluteAnimationFrame > RelevantFramesForDistributing) { - // this can happen if we are at the last frame and the next game tick is due (gfProgressToNextGameTick >= 1.0f) - if (absoluteAnimationFrame > (RelevantFramesForDistributing + 1)) { - // we should never have +2 frames even if next game tick is due - Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {} MaxFrame {})", absoluteAnimationFrame, RelevantFramesForDistributing); - } - return RelevantFramesForDistributing; - } - if (absoluteAnimationFrame <= 0) { - Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {})", absoluteAnimationFrame); - return 1; - } - return absoluteAnimationFrame; -} - -void AnimationInfo::SetNewAnimation(byte *pData, int numberOfFrames, int delayLen, AnimationDistributionFlags flags /*= AnimationDistributionFlags::None*/, int numSkippedFrames /*= 0*/, int distributeFramesBeforeFrame /*= 0*/) -{ - if ((flags & AnimationDistributionFlags::RepeatedAction) == AnimationDistributionFlags::RepeatedAction && distributeFramesBeforeFrame != 0 && NumberOfFrames == numberOfFrames && CurrentFrame >= distributeFramesBeforeFrame && CurrentFrame != NumberOfFrames) { - // We showed the same Animation (for example a melee attack) before but truncated the Animation. - // So now we should add them back to the new Animation. This increases the speed of the current Animation but the game logic/ticks isn't affected. - SkippedFramesFromPreviousAnimation = NumberOfFrames - CurrentFrame; - } else { - SkippedFramesFromPreviousAnimation = 0; - } - - this->pData = pData; - NumberOfFrames = numberOfFrames; - CurrentFrame = 1; - DelayCounter = 0; - DelayLen = delayLen; - TicksSinceSequenceStarted = 0; - RelevantFramesForDistributing = 0; - TickModifier = 0.0f; - - if (numSkippedFrames != 0 || flags != AnimationDistributionFlags::None) { - // Animation Frames that will be adjusted for the skipped Frames/game ticks - int relevantAnimationFramesForDistributing = numberOfFrames; - if (distributeFramesBeforeFrame != 0) { - // After an attack hits (_pAFNum or _pSFNum) it can be canceled or another attack can be queued and this means the animation is canceled. - // In normal attacks frame skipping always happens before the attack actual hit. - // This has the advantage that the sword or bow always points to the enemy when the hit happens (_pAFNum or _pSFNum). - // Our distribution logic must also regard this behaviour, so we are not allowed to distribute the skipped animations after the actual hit (_pAnimStopDistributingAfterFrame). - relevantAnimationFramesForDistributing = distributeFramesBeforeFrame - 1; - } - - // How many game ticks are needed to advance one Animation Frame - int ticksPerFrame = (delayLen + 1); - - // Game ticks that will be adjusted for the skipped Frames/game ticks - int relevantAnimationTicksForDistribution = relevantAnimationFramesForDistributing * ticksPerFrame; - - // How many game ticks will the Animation be really shown (skipped Frames and game ticks removed) - int relevantAnimationTicksWithSkipping = relevantAnimationTicksForDistribution - (numSkippedFrames * ticksPerFrame); - - if ((flags & AnimationDistributionFlags::ProcessAnimationPending) == AnimationDistributionFlags::ProcessAnimationPending) { - // If ProcessAnimation will be called after SetNewAnimation (in same game tick as SetNewAnimation), we increment the Animation-Counter. - // If no delay is specified, this will result in complete skipped frame (see ProcessAnimation). - // But if we have a delay specified, this would only result in a reduced time the first frame is shown (one skipped delay). - // Because of that, we only the remove one game tick from the time the Animation is shown - relevantAnimationTicksWithSkipping -= 1; - // The Animation Distribution Logic needs to account how many game ticks passed since the Animation started. - // Because ProcessAnimation will increase this later (in same game tick as SetNewAnimation), we correct this upfront. - // This also means Rendering should never hapen with TicksSinceSequenceStarted < 0. - TicksSinceSequenceStarted = -1; - } - - if ((flags & AnimationDistributionFlags::SkipsDelayOfLastFrame) == AnimationDistributionFlags::SkipsDelayOfLastFrame) { - // The logic for player/monster/... (not ProcessAnimation) only checks the frame not the delay. - // That means if a delay is specified, the last-frame is shown less then the other frames - // Example: - // If we have a animation with 3 frames and with a delay of 1 (ticksPerFrame = 2). - // The logic checks "if (frame == 3) { start_new_animation(); }" - // This will result that frame 4 is the last shown Animation Frame. - // GameTick Frame Cnt - // 1 1 0 - // 2 1 1 - // 3 2 0 - // 3 2 1 - // 4 3 0 - // 5 - - - // in game tick 5 ProcessPlayer sees Frame = 3 and stops the animation. - // But Frame 3 is only shown 1 game tick and all other Frames are shown 2 game ticks. - // Thats why we need to remove the Delay of the last Frame from the time (game ticks) the Animation is shown - relevantAnimationTicksWithSkipping -= delayLen; - } - - // The truncated Frames from previous Animation will also be shown, so we also have to distribute them for the given time (game ticks) - relevantAnimationTicksForDistribution += (SkippedFramesFromPreviousAnimation * ticksPerFrame); - - // if we skipped Frames we need to expand the game ticks to make one game tick for this Animation "faster" - float tickModifier = (float)relevantAnimationTicksForDistribution / (float)relevantAnimationTicksWithSkipping; - - // tickModifier specifies the Animation fraction per game tick, so we have to remove the delay from the variable - tickModifier /= ticksPerFrame; - - RelevantFramesForDistributing = relevantAnimationFramesForDistributing; - TickModifier = tickModifier; - } -} - -void AnimationInfo::ProcessAnimation() -{ - DelayCounter++; - TicksSinceSequenceStarted++; - if (DelayCounter > DelayLen) { - DelayCounter = 0; - CurrentFrame++; - if (CurrentFrame > NumberOfFrames) { - CurrentFrame = 1; - TicksSinceSequenceStarted = 0; - } - } -} - -} // namespace devilution +/** + * @file animationinfo.cpp + * + * Contains the core animation information and related logic + */ + +#include "animationinfo.h" +#include "appfat.h" +#include "nthread.h" +#include "utils/log.hpp" + +namespace devilution { + +int AnimationInfo::GetFrameToUseForRendering() const +{ + // Normal logic is used, + // - if no frame-skipping is required and so we have exactly one Animationframe per game tick + // or + // - if we load from a savegame where the new variables are not stored (we don't want to break savegame compatiblity because of smoother rendering of one animation) + if (RelevantFramesForDistributing <= 0) + return CurrentFrame; + + if (CurrentFrame > RelevantFramesForDistributing) + return CurrentFrame; + + assert(TicksSinceSequenceStarted >= 0); + + // we don't use the processed game ticks alone but also the fragtion of the next game tick (if a rendering happens between game ticks). This helps to smooth the animations. + float totalTicksForCurrentAnimationSequence = gfProgressToNextGameTick + (float)TicksSinceSequenceStarted; + + // 1 added for rounding reasons. float to int cast always truncate. + int absoluteAnimationFrame = 1 + (int)(totalTicksForCurrentAnimationSequence * TickModifier); + if (SkippedFramesFromPreviousAnimation > 0) { + // absoluteAnimationFrames contains also the Frames from the previous Animation, so if we want to get the current Frame we have to remove them + absoluteAnimationFrame -= SkippedFramesFromPreviousAnimation; + if (absoluteAnimationFrame <= 0) { + // We still display the remains of the previous Animation + absoluteAnimationFrame = NumberOfFrames + absoluteAnimationFrame; + } + } else if (absoluteAnimationFrame > RelevantFramesForDistributing) { + // this can happen if we are at the last frame and the next game tick is due (gfProgressToNextGameTick >= 1.0f) + if (absoluteAnimationFrame > (RelevantFramesForDistributing + 1)) { + // we should never have +2 frames even if next game tick is due + Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {} MaxFrame {})", absoluteAnimationFrame, RelevantFramesForDistributing); + } + return RelevantFramesForDistributing; + } + if (absoluteAnimationFrame <= 0) { + Log("GetFrameToUseForRendering: Calculated an invalid Animation Frame (Calculated {})", absoluteAnimationFrame); + return 1; + } + return absoluteAnimationFrame; +} + +void AnimationInfo::SetNewAnimation(byte *pData, int numberOfFrames, int delayLen, AnimationDistributionFlags flags /*= AnimationDistributionFlags::None*/, int numSkippedFrames /*= 0*/, int distributeFramesBeforeFrame /*= 0*/) +{ + if ((flags & AnimationDistributionFlags::RepeatedAction) == AnimationDistributionFlags::RepeatedAction && distributeFramesBeforeFrame != 0 && NumberOfFrames == numberOfFrames && CurrentFrame >= distributeFramesBeforeFrame && CurrentFrame != NumberOfFrames) { + // We showed the same Animation (for example a melee attack) before but truncated the Animation. + // So now we should add them back to the new Animation. This increases the speed of the current Animation but the game logic/ticks isn't affected. + SkippedFramesFromPreviousAnimation = NumberOfFrames - CurrentFrame; + } else { + SkippedFramesFromPreviousAnimation = 0; + } + + this->pData = pData; + NumberOfFrames = numberOfFrames; + CurrentFrame = 1; + DelayCounter = 0; + DelayLen = delayLen; + TicksSinceSequenceStarted = 0; + RelevantFramesForDistributing = 0; + TickModifier = 0.0f; + + if (numSkippedFrames != 0 || flags != AnimationDistributionFlags::None) { + // Animation Frames that will be adjusted for the skipped Frames/game ticks + int relevantAnimationFramesForDistributing = numberOfFrames; + if (distributeFramesBeforeFrame != 0) { + // After an attack hits (_pAFNum or _pSFNum) it can be canceled or another attack can be queued and this means the animation is canceled. + // In normal attacks frame skipping always happens before the attack actual hit. + // This has the advantage that the sword or bow always points to the enemy when the hit happens (_pAFNum or _pSFNum). + // Our distribution logic must also regard this behaviour, so we are not allowed to distribute the skipped animations after the actual hit (_pAnimStopDistributingAfterFrame). + relevantAnimationFramesForDistributing = distributeFramesBeforeFrame - 1; + } + + // How many game ticks are needed to advance one Animation Frame + int ticksPerFrame = (delayLen + 1); + + // Game ticks that will be adjusted for the skipped Frames/game ticks + int relevantAnimationTicksForDistribution = relevantAnimationFramesForDistributing * ticksPerFrame; + + // How many game ticks will the Animation be really shown (skipped Frames and game ticks removed) + int relevantAnimationTicksWithSkipping = relevantAnimationTicksForDistribution - (numSkippedFrames * ticksPerFrame); + + if ((flags & AnimationDistributionFlags::ProcessAnimationPending) == AnimationDistributionFlags::ProcessAnimationPending) { + // If ProcessAnimation will be called after SetNewAnimation (in same game tick as SetNewAnimation), we increment the Animation-Counter. + // If no delay is specified, this will result in complete skipped frame (see ProcessAnimation). + // But if we have a delay specified, this would only result in a reduced time the first frame is shown (one skipped delay). + // Because of that, we only the remove one game tick from the time the Animation is shown + relevantAnimationTicksWithSkipping -= 1; + // The Animation Distribution Logic needs to account how many game ticks passed since the Animation started. + // Because ProcessAnimation will increase this later (in same game tick as SetNewAnimation), we correct this upfront. + // This also means Rendering should never hapen with TicksSinceSequenceStarted < 0. + TicksSinceSequenceStarted = -1; + } + + if ((flags & AnimationDistributionFlags::SkipsDelayOfLastFrame) == AnimationDistributionFlags::SkipsDelayOfLastFrame) { + // The logic for player/monster/... (not ProcessAnimation) only checks the frame not the delay. + // That means if a delay is specified, the last-frame is shown less then the other frames + // Example: + // If we have a animation with 3 frames and with a delay of 1 (ticksPerFrame = 2). + // The logic checks "if (frame == 3) { start_new_animation(); }" + // This will result that frame 4 is the last shown Animation Frame. + // GameTick Frame Cnt + // 1 1 0 + // 2 1 1 + // 3 2 0 + // 3 2 1 + // 4 3 0 + // 5 - - + // in game tick 5 ProcessPlayer sees Frame = 3 and stops the animation. + // But Frame 3 is only shown 1 game tick and all other Frames are shown 2 game ticks. + // Thats why we need to remove the Delay of the last Frame from the time (game ticks) the Animation is shown + relevantAnimationTicksWithSkipping -= delayLen; + } + + // The truncated Frames from previous Animation will also be shown, so we also have to distribute them for the given time (game ticks) + relevantAnimationTicksForDistribution += (SkippedFramesFromPreviousAnimation * ticksPerFrame); + + // if we skipped Frames we need to expand the game ticks to make one game tick for this Animation "faster" + float tickModifier = (float)relevantAnimationTicksForDistribution / (float)relevantAnimationTicksWithSkipping; + + // tickModifier specifies the Animation fraction per game tick, so we have to remove the delay from the variable + tickModifier /= ticksPerFrame; + + RelevantFramesForDistributing = relevantAnimationFramesForDistributing; + TickModifier = tickModifier; + } +} + +void AnimationInfo::ProcessAnimation() +{ + DelayCounter++; + TicksSinceSequenceStarted++; + if (DelayCounter > DelayLen) { + DelayCounter = 0; + CurrentFrame++; + if (CurrentFrame > NumberOfFrames) { + CurrentFrame = 1; + TicksSinceSequenceStarted = 0; + } + } +} + +} // namespace devilution diff --git a/Source/engine/animationinfo.h b/Source/engine/animationinfo.h index 5cf677130..bbb3220bc 100644 --- a/Source/engine/animationinfo.h +++ b/Source/engine/animationinfo.h @@ -1,101 +1,101 @@ -/** - * @file animationinfo.h - * - * Contains the core animation information and related logic - */ -#pragma once - -#include -#include - -#include "engine.h" - -namespace devilution { - -/** - * @brief Specifies what special logics are applied for a Animation - */ -enum AnimationDistributionFlags : uint8_t { - None = 0, - /** - * @brief ProcessAnimation will be called after SetNewAnimation (in same game tick as NewPlrAnim) - */ - ProcessAnimationPending = 1 << 0, - /** - * @brief Delay of last Frame is ignored (for example, because only Frame and not delay is checked in game_logic) - */ - SkipsDelayOfLastFrame = 1 << 1, - /** - * @brief Repeated Animation (for example same player melee attack, that can be repeated directly after hit frame and doesn't need to show all animation frames) - */ - RepeatedAction = 1 << 2, -}; - -/* -* @brief Contains the core animation information and related logic -*/ -class AnimationInfo { -public: - /** - * @brief Pointer to Animation Data - */ - byte *pData; - /** - * @brief Additional delay of each animation in the current animation - */ - int DelayLen; - /** - * @brief Increases by one each game tick, counting how close we are to DelayLen - */ - int DelayCounter; - /** - * @brief Number of frames in current animation - */ - int NumberOfFrames; - /** - * @brief Current frame of animation - */ - int CurrentFrame; - - /** - * @brief Calculates the Frame to use for the Animation rendering - * @return The Frame to use for rendering - */ - int GetFrameToUseForRendering() const; - - /** - * @brief Sets the new Animation with all relevant information for rendering - * @param pData Pointer to Animation Data - * @param numberOfFrames Number of Frames in Animation - * @param delayLen Delay after each Animation sequence - * @param flags Specifies what special logics are applied to this Animation - * @param numSkippedFrames Number of Frames that will be skipped (for example with modifier "faster attack") - * @param distributeFramesBeforeFrame Distribute the numSkippedFrames only before this frame - */ - void SetNewAnimation(byte *pData, int numberOfFrames, int delayLen, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0); - - /* - * @brief Process the Animation for a game tick (for example advances the frame) - */ - void ProcessAnimation(); - -private: - /** - * @brief Specifies how many animations-fractions are displayed between two game ticks. this can be > 0, if animations are skipped or < 0 if the same animation is shown in multiple times (delay specified). - */ - float TickModifier; - /** - * @brief Number of game ticks after the current animation sequence started - */ - int TicksSinceSequenceStarted; - /** - * @brief Animation Frames that will be adjusted for the skipped Frames/game ticks - */ - int RelevantFramesForDistributing; - /** - * @brief Animation Frames that wasn't shown from previous Animation - */ - int SkippedFramesFromPreviousAnimation; -}; - -} // namespace devilution +/** + * @file animationinfo.h + * + * Contains the core animation information and related logic + */ +#pragma once + +#include +#include + +#include "engine.h" + +namespace devilution { + +/** + * @brief Specifies what special logics are applied for a Animation + */ +enum AnimationDistributionFlags : uint8_t { + None = 0, + /** + * @brief ProcessAnimation will be called after SetNewAnimation (in same game tick as NewPlrAnim) + */ + ProcessAnimationPending = 1 << 0, + /** + * @brief Delay of last Frame is ignored (for example, because only Frame and not delay is checked in game_logic) + */ + SkipsDelayOfLastFrame = 1 << 1, + /** + * @brief Repeated Animation (for example same player melee attack, that can be repeated directly after hit frame and doesn't need to show all animation frames) + */ + RepeatedAction = 1 << 2, +}; + +/* +* @brief Contains the core animation information and related logic +*/ +class AnimationInfo { +public: + /** + * @brief Pointer to Animation Data + */ + byte *pData; + /** + * @brief Additional delay of each animation in the current animation + */ + int DelayLen; + /** + * @brief Increases by one each game tick, counting how close we are to DelayLen + */ + int DelayCounter; + /** + * @brief Number of frames in current animation + */ + int NumberOfFrames; + /** + * @brief Current frame of animation + */ + int CurrentFrame; + + /** + * @brief Calculates the Frame to use for the Animation rendering + * @return The Frame to use for rendering + */ + int GetFrameToUseForRendering() const; + + /** + * @brief Sets the new Animation with all relevant information for rendering + * @param pData Pointer to Animation Data + * @param numberOfFrames Number of Frames in Animation + * @param delayLen Delay after each Animation sequence + * @param flags Specifies what special logics are applied to this Animation + * @param numSkippedFrames Number of Frames that will be skipped (for example with modifier "faster attack") + * @param distributeFramesBeforeFrame Distribute the numSkippedFrames only before this frame + */ + void SetNewAnimation(byte *pData, int numberOfFrames, int delayLen, AnimationDistributionFlags flags = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0); + + /* + * @brief Process the Animation for a game tick (for example advances the frame) + */ + void ProcessAnimation(); + +private: + /** + * @brief Specifies how many animations-fractions are displayed between two game ticks. this can be > 0, if animations are skipped or < 0 if the same animation is shown in multiple times (delay specified). + */ + float TickModifier; + /** + * @brief Number of game ticks after the current animation sequence started + */ + int TicksSinceSequenceStarted; + /** + * @brief Animation Frames that will be adjusted for the skipped Frames/game ticks + */ + int RelevantFramesForDistributing; + /** + * @brief Animation Frames that wasn't shown from previous Animation + */ + int SkippedFramesFromPreviousAnimation; +}; + +} // namespace devilution diff --git a/Source/storm/storm_dvlnet.h b/Source/storm/storm_dvlnet.h index f7534a194..286399101 100644 --- a/Source/storm/storm_dvlnet.h +++ b/Source/storm/storm_dvlnet.h @@ -1,5 +1,5 @@ -#pragma once - -void DvlNet_SendInfoRequest(); -std::vector DvlNet_GetGamelist(); -void DvlNet_SetPassword(std::string pw); +#pragma once + +void DvlNet_SendInfoRequest(); +std::vector DvlNet_GetGamelist(); +void DvlNet_SetPassword(std::string pw); diff --git a/Source/utils/math.h b/Source/utils/math.h index b4feac8e3..8f17186f6 100644 --- a/Source/utils/math.h +++ b/Source/utils/math.h @@ -1,73 +1,73 @@ -/** -* @file math.h -* -* Math utility functions -*/ -#pragma once - -namespace devilution { -namespace math { - -/** - * @brief Compute sign of t - * @tparam T Any arithmetic type - * @param t Value to compute sign of - * @return -1 if t < 0, 1 if t > 0, 0 if t == 0 -*/ -template -int Sign(T t) -{ - return (t > T(0)) - (t < T(0)); -} - -/** - * @brief Linearly interpolate from a towards b using mixing value t - * @tparam V Any arithmetic type, used for interpolants and return value - * @tparam T Any arithmetic type, used for interpolator - * @param a Low interpolation value (returned when t == 0) - * @param b High interpolation value (returned when t == 1) - * @param t Interpolator, commonly in range [0..1], values outside this range will extrapolate - * @return a + (b - a) * t -*/ -template -V Lerp(V a, V b, T t) -{ - return a + (b - a) * t; -} - -/** - * @brief Inverse lerp, given two key values a and b, and a free value v, determine mixing factor t so that v = Lerp(a, b, t) - * @tparam T Any arithmetic type - * @param a Low key value (function returns 0 if v == a) - * @param b High key value (function returns 1 if v == b) - * @param v Mixing factor, commonly in range [a..b] to get a return [0..1] - * @return Value t so that v = Lerp(a, b, t); or 0 if b == a -*/ -template -T InvLerp(T a, T b, T v) -{ - if (b == a) - return T(0); - - return (v - a) / (b - a); -} - -/** - * @brief Remaps value v from range [inMin, inMax] to [outMin, outMax] - * @tparam T Any arithmetic type - * @param inMin First bound of input range - * @param inMax Second bound of input range - * @param outMin First bound of output range - * @param outMax Second bound of output range - * @param v Value to remap - * @return Transformed value so that InvLerp(inMin, inMax, v) == InvLerp(outMin, outMax, return) -*/ -template -T Remap(T inMin, T inMax, T outMin, T outMax, T v) -{ - auto t = InvLerp(inMin, inMax, v); - return Lerp(outMin, outMax, t); -} - -} // namespace math -} // namespace devilution +/** +* @file math.h +* +* Math utility functions +*/ +#pragma once + +namespace devilution { +namespace math { + +/** + * @brief Compute sign of t + * @tparam T Any arithmetic type + * @param t Value to compute sign of + * @return -1 if t < 0, 1 if t > 0, 0 if t == 0 +*/ +template +int Sign(T t) +{ + return (t > T(0)) - (t < T(0)); +} + +/** + * @brief Linearly interpolate from a towards b using mixing value t + * @tparam V Any arithmetic type, used for interpolants and return value + * @tparam T Any arithmetic type, used for interpolator + * @param a Low interpolation value (returned when t == 0) + * @param b High interpolation value (returned when t == 1) + * @param t Interpolator, commonly in range [0..1], values outside this range will extrapolate + * @return a + (b - a) * t +*/ +template +V Lerp(V a, V b, T t) +{ + return a + (b - a) * t; +} + +/** + * @brief Inverse lerp, given two key values a and b, and a free value v, determine mixing factor t so that v = Lerp(a, b, t) + * @tparam T Any arithmetic type + * @param a Low key value (function returns 0 if v == a) + * @param b High key value (function returns 1 if v == b) + * @param v Mixing factor, commonly in range [a..b] to get a return [0..1] + * @return Value t so that v = Lerp(a, b, t); or 0 if b == a +*/ +template +T InvLerp(T a, T b, T v) +{ + if (b == a) + return T(0); + + return (v - a) / (b - a); +} + +/** + * @brief Remaps value v from range [inMin, inMax] to [outMin, outMax] + * @tparam T Any arithmetic type + * @param inMin First bound of input range + * @param inMax Second bound of input range + * @param outMin First bound of output range + * @param outMax Second bound of output range + * @param v Value to remap + * @return Transformed value so that InvLerp(inMin, inMax, v) == InvLerp(outMin, outMax, return) +*/ +template +T Remap(T inMin, T inMax, T outMin, T outMax, T v) +{ + auto t = InvLerp(inMin, inMax, v); + return Lerp(outMin, outMax, t); +} + +} // namespace math +} // namespace devilution diff --git a/Source/utils/stdcompat/string_view.hpp b/Source/utils/stdcompat/string_view.hpp index f42c3e405..4e7f60326 100644 --- a/Source/utils/stdcompat/string_view.hpp +++ b/Source/utils/stdcompat/string_view.hpp @@ -1,19 +1,19 @@ -#pragma once - -#ifdef __has_include -#if defined(__cplusplus) && __cplusplus >= 201703L && __has_include() // should be 201606L, but STL headers disagree -#include // IWYU pragma: export -namespace devilution { - using string_view = std::string_view; -} -#elif __has_include() -#include // IWYU pragma: export -namespace devilution { - using string_view = std::experimental::string_view; -} -#else -#error "Missing support for or " -#endif -#else -#error "__has_include unavailable" -#endif +#pragma once + +#ifdef __has_include +#if defined(__cplusplus) && __cplusplus >= 201703L && __has_include() // should be 201606L, but STL headers disagree +#include // IWYU pragma: export +namespace devilution { + using string_view = std::string_view; +} +#elif __has_include() +#include // IWYU pragma: export +namespace devilution { + using string_view = std::experimental::string_view; +} +#else +#error "Missing support for or " +#endif +#else +#error "__has_include unavailable" +#endif diff --git a/test/animationinfo_test.cpp b/test/animationinfo_test.cpp index 49f0a64ce..fc408a617 100644 --- a/test/animationinfo_test.cpp +++ b/test/animationinfo_test.cpp @@ -1,524 +1,524 @@ -#include - -#include "nthread.h" -#include "engine/animationinfo.h" - -using namespace devilution; - -/** - * @brief Represents a Action in the game_logic or rendering. - */ -struct TestData { - virtual ~TestData() = default; -}; - -/** - * @brief Represents a call to SetNewAnimation - */ -struct SetNewAnimationData : TestData { - SetNewAnimationData(int numberOfFrames, int delayLen, AnimationDistributionFlags params = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0) - { - _NumberOfFrames = numberOfFrames; - _DelayLen = delayLen; - _Params = params; - _NumSkippedFrames = numSkippedFrames; - _DistributeFramesBeforeFrame = distributeFramesBeforeFrame; - } - int _NumberOfFrames; - int _DelayLen; - AnimationDistributionFlags _Params; - int _NumSkippedFrames; - int _DistributeFramesBeforeFrame; -}; - -/** - * @brief Represents a GameTick, this includes skipping of Frames (for example because of Fastest Attack Modifier) and ProcessAnimation (which also updates Animation Frame/Count). - */ -struct GameTickData : TestData { - int _ExpectedAnimationFrame; - int _ExpectedAnimationCnt; - int _FramesToSkip; - GameTickData(int expectedAnimationFrame, int expectedAnimationCnt, int framesToSkip = 0) - { - _ExpectedAnimationFrame = expectedAnimationFrame; - _ExpectedAnimationCnt = expectedAnimationCnt; - _FramesToSkip = framesToSkip; - } -}; - -/** - * @brief Represents a rendering/drawing Action. - * This happens directly after the game_logic (_fProgressToNextGameTick = 0) or if no GameTick is due between two GameTicks (_fProgressToNextGameTick != 0). - */ -struct RenderingData : TestData { - /* - * @brief the progress as a fraction (0.0f to 1.0f) in time to the next game tick - */ - float _fProgressToNextGameTick; - int _ExpectedRenderingFrame; - RenderingData(float fProgressToNextGameTick, int expectedRenderingFrame) - { - this->_fProgressToNextGameTick = fProgressToNextGameTick; - this->_ExpectedRenderingFrame = expectedRenderingFrame; - } -}; - -/* -* @brief -* This UnitTest tries to simulate the GameLoop (GameTickData) and the Rendering that can happen (RenderingData). -* Rendering can happen more often then GameTicks and at any time between two GameTicks. -* The Animation Distribution Logic must adjust to the Rendering that happens at any give point in time. -*/ -void RunAnimationTest(const std::vector &vecTestData) -{ - const int pnum = 0; - AnimationInfo animInfo = {}; - - int currentGameTick = 0; - for (TestData *x : vecTestData) { - auto setNewAnimationData = dynamic_cast(x); - if (setNewAnimationData != nullptr) { - animInfo.SetNewAnimation(nullptr, setNewAnimationData->_NumberOfFrames, setNewAnimationData->_DelayLen, setNewAnimationData->_Params, setNewAnimationData->_NumSkippedFrames, setNewAnimationData->_DistributeFramesBeforeFrame); - } - - auto gameTickData = dynamic_cast(x); - if (gameTickData != nullptr) { - currentGameTick += 1; - if (gameTickData->_FramesToSkip != 0) - animInfo.CurrentFrame += gameTickData->_FramesToSkip; - animInfo.ProcessAnimation(); - EXPECT_EQ(animInfo.CurrentFrame, gameTickData->_ExpectedAnimationFrame); - EXPECT_EQ(animInfo.DelayCounter, gameTickData->_ExpectedAnimationCnt); - } - - auto renderingData = dynamic_cast(x); - if (renderingData != nullptr) { - gfProgressToNextGameTick = renderingData->_fProgressToNextGameTick; - EXPECT_EQ(animInfo.GetFrameToUseForRendering(), renderingData->_ExpectedRenderingFrame) - << std::fixed << std::setprecision(2) - << "ProgressToNextGameTick: " << renderingData->_fProgressToNextGameTick - << " CurrentFrame: " << animInfo.CurrentFrame - << " DelayCounter: " << animInfo.DelayCounter - << " GameTick: " << currentGameTick; - } - } - for (TestData *x : vecTestData) { - delete x; - } -} - -TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be considered by distribution logic -{ - RunAnimationTest( - { - new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 0, 9), - // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. - new GameTickData(2, 0), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 1), - new GameTickData(3, 0), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 2), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 3), - new GameTickData(4, 0), - new RenderingData(0.0f, 3), - new RenderingData(0.3f, 3), - new RenderingData(0.6f, 3), - new RenderingData(0.8f, 4), - new GameTickData(5, 0), - new RenderingData(0.0f, 4), - new RenderingData(0.3f, 4), - new RenderingData(0.6f, 5), - new RenderingData(0.8f, 5), - new GameTickData(6, 0), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 5), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 6), - new GameTickData(7, 0), - new RenderingData(0.0f, 6), - new RenderingData(0.3f, 7), - new RenderingData(0.6f, 7), - new RenderingData(0.8f, 7), - new GameTickData(8, 0), - new RenderingData(0.0f, 7), - new RenderingData(0.3f, 8), - new RenderingData(0.6f, 8), - new RenderingData(0.8f, 8), - - // After this GameTick, the Animation Distribution Logic is disabled - new GameTickData(9, 0), - new RenderingData(0.1f, 9), - new GameTickData(10, 0), - new RenderingData(0.4f, 10), - new GameTickData(11, 0), - new RenderingData(0.4f, 11), - new GameTickData(12, 0), - new RenderingData(0.3f, 12), - new GameTickData(13, 0), - new RenderingData(0.0f, 13), - new GameTickData(14, 0), - new RenderingData(0.6f, 14), - new GameTickData(15, 0), - new RenderingData(0.6f, 15), - new GameTickData(16, 0), - new RenderingData(0.6f, 16), - // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" - }); -} - -TEST(AnimationInfo, AttackSwordWarriorWithFastestAttack) // Skipped frames and ProcessAnimationPending should be considered by distribution logic -{ - RunAnimationTest( - { - new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 2, 9), - // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. - new GameTickData(2, 0), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 2), - new GameTickData(3, 0), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 3), - new RenderingData(0.6f, 3), - new RenderingData(0.8f, 3), - new GameTickData(4, 0), - new RenderingData(0.0f, 4), - new RenderingData(0.3f, 4), - new RenderingData(0.6f, 5), - new RenderingData(0.8f, 5), - new GameTickData(7, 0, 2), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 6), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 7), - new GameTickData(8, 0), - new RenderingData(0.0f, 7), - new RenderingData(0.3f, 7), - new RenderingData(0.6f, 8), - new RenderingData(0.8f, 8), - - // After this GameTick, the Animation Distribution Logic is disabled - new GameTickData(9, 0), - new RenderingData(0.1f, 9), - new GameTickData(10, 0), - new RenderingData(0.4f, 10), - new GameTickData(11, 0), - new RenderingData(0.4f, 11), - new GameTickData(12, 0), - new RenderingData(0.3f, 12), - new GameTickData(13, 0), - new RenderingData(0.0f, 13), - new GameTickData(14, 0), - new RenderingData(0.6f, 14), - new GameTickData(15, 0), - new RenderingData(0.6f, 15), - new GameTickData(16, 0), - new RenderingData(0.6f, 16), - // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" - }); -} - -/* -* @brief The Warrior make two attacks. The second queued attack cancels the first after the Hit Frame. -*/ -TEST(AnimationInfo, AttackSwordWarriorRepeated) -{ - RunAnimationTest( - { - new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 0, 9), - // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. - new GameTickData(2, 0), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 1), - new GameTickData(3, 0), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 2), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 3), - new GameTickData(4, 0), - new RenderingData(0.0f, 3), - new RenderingData(0.3f, 3), - new RenderingData(0.6f, 3), - new RenderingData(0.8f, 4), - new GameTickData(5, 0), - new RenderingData(0.0f, 4), - new RenderingData(0.3f, 4), - new RenderingData(0.6f, 5), - new RenderingData(0.8f, 5), - new GameTickData(6, 0), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 5), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 6), - new GameTickData(7, 0), - new RenderingData(0.0f, 6), - new RenderingData(0.3f, 7), - new RenderingData(0.6f, 7), - new RenderingData(0.8f, 7), - new GameTickData(8, 0), - new RenderingData(0.0f, 7), - new RenderingData(0.3f, 8), - new RenderingData(0.6f, 8), - new RenderingData(0.8f, 8), - - // After this GameTick, the Animation Distribution Logic is disabled - new GameTickData(9, 0), - new RenderingData(0.1f, 9), - new GameTickData(10, 0), - new RenderingData(0.3f, 10), - - // Start of repeated attack, cause plr[pnum].AnimInfo.CurrentFrame > plr[myplr]._pAFNum - new SetNewAnimationData(16, 0, static_cast(AnimationDistributionFlags::ProcessAnimationPending | AnimationDistributionFlags::RepeatedAction), 0, 9), - // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. - new GameTickData(2, 0), - new RenderingData(0.0f, 11), - new RenderingData(0.3f, 11), - new RenderingData(0.6f, 12), - new RenderingData(0.8f, 12), - new GameTickData(3, 0), - new RenderingData(0.0f, 13), - new RenderingData(0.3f, 13), - new RenderingData(0.6f, 14), - new RenderingData(0.8f, 14), - new GameTickData(4, 0), - new RenderingData(0.0f, 15), - new RenderingData(0.3f, 15), - new RenderingData(0.6f, 16), - new RenderingData(0.8f, 16), - new GameTickData(5, 0), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 2), - new GameTickData(6, 0), - new RenderingData(0.0f, 3), - new RenderingData(0.3f, 3), - new RenderingData(0.6f, 4), - new RenderingData(0.8f, 4), - new GameTickData(7, 0), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 5), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 6), - new GameTickData(8, 0), - new RenderingData(0.0f, 7), - new RenderingData(0.3f, 7), - new RenderingData(0.6f, 8), - new RenderingData(0.8f, 8), - - // After this GameTick, the Animation Distribution Logic is disabled - new GameTickData(9, 0), - new RenderingData(0.1f, 9), - new GameTickData(10, 0), - new RenderingData(0.4f, 10), - new GameTickData(11, 0), - new RenderingData(0.4f, 11), - new GameTickData(12, 0), - new RenderingData(0.3f, 12), - new GameTickData(13, 0), - new RenderingData(0.0f, 13), - new GameTickData(14, 0), - new RenderingData(0.6f, 14), - new GameTickData(15, 0), - new RenderingData(0.6f, 15), - new GameTickData(16, 0), - new RenderingData(0.6f, 16), - // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" - }); -} - -TEST(AnimationInfo, BlockingWarriorNormal) // Ignored delay for last Frame should be considered by distribution logic -{ - RunAnimationTest( - { - new SetNewAnimationData(2, 2, AnimationDistributionFlags::SkipsDelayOfLastFrame), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 1), - new GameTickData(1, 1), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 1), - new GameTickData(1, 2), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 2), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 2), - new GameTickData(2, 0), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 2), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 2), - // Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {" - }); -} - -TEST(AnimationInfo, BlockingSorcererWithFastBlock) // Skipped frames and ignored delay for last Frame should be considered by distribution logic -{ - RunAnimationTest( - { - new SetNewAnimationData(6, 2, AnimationDistributionFlags::SkipsDelayOfLastFrame, 4), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 1), - new RenderingData(0.8f, 2), - new GameTickData(1, 1), - new RenderingData(0.0f, 2), - new RenderingData(0.3f, 2), - new RenderingData(0.6f, 3), - new RenderingData(0.8f, 3), - new GameTickData(1, 2), - new RenderingData(0.0f, 4), - new RenderingData(0.3f, 4), - new RenderingData(0.6f, 4), - new RenderingData(0.8f, 5), - new GameTickData(2, 0), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 5), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 6), - // Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {" - }); -} - -TEST(AnimationInfo, HitRecoverySorcererZenMode) // Skipped frames and ignored delay for last Frame should be considered by distribution logic -{ - RunAnimationTest( - { - new SetNewAnimationData(8, 0, AnimationDistributionFlags::None, 4), - new RenderingData(0.0f, 1), - new RenderingData(0.3f, 1), - new RenderingData(0.6f, 2), - new RenderingData(0.8f, 2), - new GameTickData(3, 0, 1), - new RenderingData(0.0f, 3), - new RenderingData(0.3f, 3), - new RenderingData(0.6f, 4), - new RenderingData(0.8f, 4), - new GameTickData(7, 0, 3), - new RenderingData(0.0f, 5), - new RenderingData(0.3f, 5), - new RenderingData(0.6f, 6), - new RenderingData(0.8f, 6), - new GameTickData(8, 0), - new RenderingData(0.0f, 7), - new RenderingData(0.3f, 7), - new RenderingData(0.6f, 8), - new RenderingData(0.8f, 8), - // Animation stopped cause PM_DoGotHit would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pHFrames) {" - }); -} -TEST(AnimationInfo, Stand) // Distribution Logic shouldn't change anything here -{ - RunAnimationTest( - { - new SetNewAnimationData(10, 3), - new RenderingData(0.1f, 1), - new GameTickData(1, 1), - new RenderingData(0.6f, 1), - new GameTickData(1, 2), - new RenderingData(0.6f, 1), - new GameTickData(1, 3), - new RenderingData(0.6f, 1), - - new GameTickData(2, 0), - new RenderingData(0.6f, 2), - new GameTickData(2, 1), - new RenderingData(0.6f, 2), - new GameTickData(2, 2), - new RenderingData(0.6f, 2), - new GameTickData(2, 3), - new RenderingData(0.6f, 2), - - new GameTickData(3, 0), - new RenderingData(0.6f, 3), - new GameTickData(3, 1), - new RenderingData(0.6f, 3), - new GameTickData(3, 2), - new RenderingData(0.6f, 3), - new GameTickData(3, 3), - new RenderingData(0.6f, 3), - - new GameTickData(4, 0), - new RenderingData(0.6f, 4), - new GameTickData(4, 1), - new RenderingData(0.6f, 4), - new GameTickData(4, 2), - new RenderingData(0.6f, 4), - new GameTickData(4, 3), - new RenderingData(0.6f, 4), - - new GameTickData(5, 0), - new RenderingData(0.6f, 5), - new GameTickData(5, 1), - new RenderingData(0.6f, 5), - new GameTickData(5, 2), - new RenderingData(0.6f, 5), - new GameTickData(5, 3), - new RenderingData(0.6f, 5), - - new GameTickData(6, 0), - new RenderingData(0.6f, 6), - new GameTickData(6, 1), - new RenderingData(0.6f, 6), - new GameTickData(6, 2), - new RenderingData(0.6f, 6), - new GameTickData(6, 3), - new RenderingData(0.6f, 6), - - new GameTickData(7, 0), - new RenderingData(0.6f, 7), - new GameTickData(7, 1), - new RenderingData(0.6f, 7), - new GameTickData(7, 2), - new RenderingData(0.6f, 7), - new GameTickData(7, 3), - new RenderingData(0.6f, 7), - - new GameTickData(8, 0), - new RenderingData(0.6f, 8), - new GameTickData(8, 1), - new RenderingData(0.6f, 8), - new GameTickData(8, 2), - new RenderingData(0.6f, 8), - new GameTickData(8, 3), - new RenderingData(0.6f, 8), - - new GameTickData(9, 0), - new RenderingData(0.6f, 9), - new GameTickData(9, 1), - new RenderingData(0.6f, 9), - new GameTickData(9, 2), - new RenderingData(0.6f, 9), - new GameTickData(9, 3), - new RenderingData(0.6f, 9), - - new GameTickData(10, 0), - new RenderingData(0.6f, 10), - new GameTickData(10, 1), - new RenderingData(0.6f, 10), - new GameTickData(10, 2), - new RenderingData(0.6f, 10), - new GameTickData(10, 3), - new RenderingData(0.6f, 10), - - // Animation starts again - new GameTickData(1, 0), - new RenderingData(0.1f, 1), - new GameTickData(1, 1), - new RenderingData(0.6f, 1), - new GameTickData(1, 2), - new RenderingData(0.6f, 1), - new GameTickData(1, 3), - new RenderingData(0.6f, 1), - }); -} +#include + +#include "nthread.h" +#include "engine/animationinfo.h" + +using namespace devilution; + +/** + * @brief Represents a Action in the game_logic or rendering. + */ +struct TestData { + virtual ~TestData() = default; +}; + +/** + * @brief Represents a call to SetNewAnimation + */ +struct SetNewAnimationData : TestData { + SetNewAnimationData(int numberOfFrames, int delayLen, AnimationDistributionFlags params = AnimationDistributionFlags::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0) + { + _NumberOfFrames = numberOfFrames; + _DelayLen = delayLen; + _Params = params; + _NumSkippedFrames = numSkippedFrames; + _DistributeFramesBeforeFrame = distributeFramesBeforeFrame; + } + int _NumberOfFrames; + int _DelayLen; + AnimationDistributionFlags _Params; + int _NumSkippedFrames; + int _DistributeFramesBeforeFrame; +}; + +/** + * @brief Represents a GameTick, this includes skipping of Frames (for example because of Fastest Attack Modifier) and ProcessAnimation (which also updates Animation Frame/Count). + */ +struct GameTickData : TestData { + int _ExpectedAnimationFrame; + int _ExpectedAnimationCnt; + int _FramesToSkip; + GameTickData(int expectedAnimationFrame, int expectedAnimationCnt, int framesToSkip = 0) + { + _ExpectedAnimationFrame = expectedAnimationFrame; + _ExpectedAnimationCnt = expectedAnimationCnt; + _FramesToSkip = framesToSkip; + } +}; + +/** + * @brief Represents a rendering/drawing Action. + * This happens directly after the game_logic (_fProgressToNextGameTick = 0) or if no GameTick is due between two GameTicks (_fProgressToNextGameTick != 0). + */ +struct RenderingData : TestData { + /* + * @brief the progress as a fraction (0.0f to 1.0f) in time to the next game tick + */ + float _fProgressToNextGameTick; + int _ExpectedRenderingFrame; + RenderingData(float fProgressToNextGameTick, int expectedRenderingFrame) + { + this->_fProgressToNextGameTick = fProgressToNextGameTick; + this->_ExpectedRenderingFrame = expectedRenderingFrame; + } +}; + +/* +* @brief +* This UnitTest tries to simulate the GameLoop (GameTickData) and the Rendering that can happen (RenderingData). +* Rendering can happen more often then GameTicks and at any time between two GameTicks. +* The Animation Distribution Logic must adjust to the Rendering that happens at any give point in time. +*/ +void RunAnimationTest(const std::vector &vecTestData) +{ + const int pnum = 0; + AnimationInfo animInfo = {}; + + int currentGameTick = 0; + for (TestData *x : vecTestData) { + auto setNewAnimationData = dynamic_cast(x); + if (setNewAnimationData != nullptr) { + animInfo.SetNewAnimation(nullptr, setNewAnimationData->_NumberOfFrames, setNewAnimationData->_DelayLen, setNewAnimationData->_Params, setNewAnimationData->_NumSkippedFrames, setNewAnimationData->_DistributeFramesBeforeFrame); + } + + auto gameTickData = dynamic_cast(x); + if (gameTickData != nullptr) { + currentGameTick += 1; + if (gameTickData->_FramesToSkip != 0) + animInfo.CurrentFrame += gameTickData->_FramesToSkip; + animInfo.ProcessAnimation(); + EXPECT_EQ(animInfo.CurrentFrame, gameTickData->_ExpectedAnimationFrame); + EXPECT_EQ(animInfo.DelayCounter, gameTickData->_ExpectedAnimationCnt); + } + + auto renderingData = dynamic_cast(x); + if (renderingData != nullptr) { + gfProgressToNextGameTick = renderingData->_fProgressToNextGameTick; + EXPECT_EQ(animInfo.GetFrameToUseForRendering(), renderingData->_ExpectedRenderingFrame) + << std::fixed << std::setprecision(2) + << "ProgressToNextGameTick: " << renderingData->_fProgressToNextGameTick + << " CurrentFrame: " << animInfo.CurrentFrame + << " DelayCounter: " << animInfo.DelayCounter + << " GameTick: " << currentGameTick; + } + } + for (TestData *x : vecTestData) { + delete x; + } +} + +TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be considered by distribution logic +{ + RunAnimationTest( + { + new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 0, 9), + // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 1), + new GameTickData(3, 0), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 2), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 3), + new GameTickData(4, 0), + new RenderingData(0.0f, 3), + new RenderingData(0.3f, 3), + new RenderingData(0.6f, 3), + new RenderingData(0.8f, 4), + new GameTickData(5, 0), + new RenderingData(0.0f, 4), + new RenderingData(0.3f, 4), + new RenderingData(0.6f, 5), + new RenderingData(0.8f, 5), + new GameTickData(6, 0), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 5), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 6), + new GameTickData(7, 0), + new RenderingData(0.0f, 6), + new RenderingData(0.3f, 7), + new RenderingData(0.6f, 7), + new RenderingData(0.8f, 7), + new GameTickData(8, 0), + new RenderingData(0.0f, 7), + new RenderingData(0.3f, 8), + new RenderingData(0.6f, 8), + new RenderingData(0.8f, 8), + + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), + new RenderingData(0.1f, 9), + new GameTickData(10, 0), + new RenderingData(0.4f, 10), + new GameTickData(11, 0), + new RenderingData(0.4f, 11), + new GameTickData(12, 0), + new RenderingData(0.3f, 12), + new GameTickData(13, 0), + new RenderingData(0.0f, 13), + new GameTickData(14, 0), + new RenderingData(0.6f, 14), + new GameTickData(15, 0), + new RenderingData(0.6f, 15), + new GameTickData(16, 0), + new RenderingData(0.6f, 16), + // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" + }); +} + +TEST(AnimationInfo, AttackSwordWarriorWithFastestAttack) // Skipped frames and ProcessAnimationPending should be considered by distribution logic +{ + RunAnimationTest( + { + new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 2, 9), + // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 2), + new GameTickData(3, 0), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 3), + new RenderingData(0.6f, 3), + new RenderingData(0.8f, 3), + new GameTickData(4, 0), + new RenderingData(0.0f, 4), + new RenderingData(0.3f, 4), + new RenderingData(0.6f, 5), + new RenderingData(0.8f, 5), + new GameTickData(7, 0, 2), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 6), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 7), + new GameTickData(8, 0), + new RenderingData(0.0f, 7), + new RenderingData(0.3f, 7), + new RenderingData(0.6f, 8), + new RenderingData(0.8f, 8), + + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), + new RenderingData(0.1f, 9), + new GameTickData(10, 0), + new RenderingData(0.4f, 10), + new GameTickData(11, 0), + new RenderingData(0.4f, 11), + new GameTickData(12, 0), + new RenderingData(0.3f, 12), + new GameTickData(13, 0), + new RenderingData(0.0f, 13), + new GameTickData(14, 0), + new RenderingData(0.6f, 14), + new GameTickData(15, 0), + new RenderingData(0.6f, 15), + new GameTickData(16, 0), + new RenderingData(0.6f, 16), + // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" + }); +} + +/* +* @brief The Warrior make two attacks. The second queued attack cancels the first after the Hit Frame. +*/ +TEST(AnimationInfo, AttackSwordWarriorRepeated) +{ + RunAnimationTest( + { + new SetNewAnimationData(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 0, 9), + // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 1), + new GameTickData(3, 0), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 2), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 3), + new GameTickData(4, 0), + new RenderingData(0.0f, 3), + new RenderingData(0.3f, 3), + new RenderingData(0.6f, 3), + new RenderingData(0.8f, 4), + new GameTickData(5, 0), + new RenderingData(0.0f, 4), + new RenderingData(0.3f, 4), + new RenderingData(0.6f, 5), + new RenderingData(0.8f, 5), + new GameTickData(6, 0), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 5), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 6), + new GameTickData(7, 0), + new RenderingData(0.0f, 6), + new RenderingData(0.3f, 7), + new RenderingData(0.6f, 7), + new RenderingData(0.8f, 7), + new GameTickData(8, 0), + new RenderingData(0.0f, 7), + new RenderingData(0.3f, 8), + new RenderingData(0.6f, 8), + new RenderingData(0.8f, 8), + + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), + new RenderingData(0.1f, 9), + new GameTickData(10, 0), + new RenderingData(0.3f, 10), + + // Start of repeated attack, cause plr[pnum].AnimInfo.CurrentFrame > plr[myplr]._pAFNum + new SetNewAnimationData(16, 0, static_cast(AnimationDistributionFlags::ProcessAnimationPending | AnimationDistributionFlags::RepeatedAction), 0, 9), + // ProcessAnimation directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), + new RenderingData(0.0f, 11), + new RenderingData(0.3f, 11), + new RenderingData(0.6f, 12), + new RenderingData(0.8f, 12), + new GameTickData(3, 0), + new RenderingData(0.0f, 13), + new RenderingData(0.3f, 13), + new RenderingData(0.6f, 14), + new RenderingData(0.8f, 14), + new GameTickData(4, 0), + new RenderingData(0.0f, 15), + new RenderingData(0.3f, 15), + new RenderingData(0.6f, 16), + new RenderingData(0.8f, 16), + new GameTickData(5, 0), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 2), + new GameTickData(6, 0), + new RenderingData(0.0f, 3), + new RenderingData(0.3f, 3), + new RenderingData(0.6f, 4), + new RenderingData(0.8f, 4), + new GameTickData(7, 0), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 5), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 6), + new GameTickData(8, 0), + new RenderingData(0.0f, 7), + new RenderingData(0.3f, 7), + new RenderingData(0.6f, 8), + new RenderingData(0.8f, 8), + + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), + new RenderingData(0.1f, 9), + new GameTickData(10, 0), + new RenderingData(0.4f, 10), + new GameTickData(11, 0), + new RenderingData(0.4f, 11), + new GameTickData(12, 0), + new RenderingData(0.3f, 12), + new GameTickData(13, 0), + new RenderingData(0.0f, 13), + new GameTickData(14, 0), + new RenderingData(0.6f, 14), + new GameTickData(15, 0), + new RenderingData(0.6f, 15), + new GameTickData(16, 0), + new RenderingData(0.6f, 16), + // Animation stopped cause PM_DoAttack would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame == plr[pnum]._pAFrames) {" + }); +} + +TEST(AnimationInfo, BlockingWarriorNormal) // Ignored delay for last Frame should be considered by distribution logic +{ + RunAnimationTest( + { + new SetNewAnimationData(2, 2, AnimationDistributionFlags::SkipsDelayOfLastFrame), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 1), + new GameTickData(1, 1), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 1), + new GameTickData(1, 2), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 2), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 2), + new GameTickData(2, 0), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 2), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 2), + // Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {" + }); +} + +TEST(AnimationInfo, BlockingSorcererWithFastBlock) // Skipped frames and ignored delay for last Frame should be considered by distribution logic +{ + RunAnimationTest( + { + new SetNewAnimationData(6, 2, AnimationDistributionFlags::SkipsDelayOfLastFrame, 4), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 1), + new RenderingData(0.8f, 2), + new GameTickData(1, 1), + new RenderingData(0.0f, 2), + new RenderingData(0.3f, 2), + new RenderingData(0.6f, 3), + new RenderingData(0.8f, 3), + new GameTickData(1, 2), + new RenderingData(0.0f, 4), + new RenderingData(0.3f, 4), + new RenderingData(0.6f, 4), + new RenderingData(0.8f, 5), + new GameTickData(2, 0), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 5), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 6), + // Animation stopped cause PM_DoBlock would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pBFrames) {" + }); +} + +TEST(AnimationInfo, HitRecoverySorcererZenMode) // Skipped frames and ignored delay for last Frame should be considered by distribution logic +{ + RunAnimationTest( + { + new SetNewAnimationData(8, 0, AnimationDistributionFlags::None, 4), + new RenderingData(0.0f, 1), + new RenderingData(0.3f, 1), + new RenderingData(0.6f, 2), + new RenderingData(0.8f, 2), + new GameTickData(3, 0, 1), + new RenderingData(0.0f, 3), + new RenderingData(0.3f, 3), + new RenderingData(0.6f, 4), + new RenderingData(0.8f, 4), + new GameTickData(7, 0, 3), + new RenderingData(0.0f, 5), + new RenderingData(0.3f, 5), + new RenderingData(0.6f, 6), + new RenderingData(0.8f, 6), + new GameTickData(8, 0), + new RenderingData(0.0f, 7), + new RenderingData(0.3f, 7), + new RenderingData(0.6f, 8), + new RenderingData(0.8f, 8), + // Animation stopped cause PM_DoGotHit would stop the Animation "if (plr[pnum].AnimInfo.CurrentFrame >= plr[pnum]._pHFrames) {" + }); +} +TEST(AnimationInfo, Stand) // Distribution Logic shouldn't change anything here +{ + RunAnimationTest( + { + new SetNewAnimationData(10, 3), + new RenderingData(0.1f, 1), + new GameTickData(1, 1), + new RenderingData(0.6f, 1), + new GameTickData(1, 2), + new RenderingData(0.6f, 1), + new GameTickData(1, 3), + new RenderingData(0.6f, 1), + + new GameTickData(2, 0), + new RenderingData(0.6f, 2), + new GameTickData(2, 1), + new RenderingData(0.6f, 2), + new GameTickData(2, 2), + new RenderingData(0.6f, 2), + new GameTickData(2, 3), + new RenderingData(0.6f, 2), + + new GameTickData(3, 0), + new RenderingData(0.6f, 3), + new GameTickData(3, 1), + new RenderingData(0.6f, 3), + new GameTickData(3, 2), + new RenderingData(0.6f, 3), + new GameTickData(3, 3), + new RenderingData(0.6f, 3), + + new GameTickData(4, 0), + new RenderingData(0.6f, 4), + new GameTickData(4, 1), + new RenderingData(0.6f, 4), + new GameTickData(4, 2), + new RenderingData(0.6f, 4), + new GameTickData(4, 3), + new RenderingData(0.6f, 4), + + new GameTickData(5, 0), + new RenderingData(0.6f, 5), + new GameTickData(5, 1), + new RenderingData(0.6f, 5), + new GameTickData(5, 2), + new RenderingData(0.6f, 5), + new GameTickData(5, 3), + new RenderingData(0.6f, 5), + + new GameTickData(6, 0), + new RenderingData(0.6f, 6), + new GameTickData(6, 1), + new RenderingData(0.6f, 6), + new GameTickData(6, 2), + new RenderingData(0.6f, 6), + new GameTickData(6, 3), + new RenderingData(0.6f, 6), + + new GameTickData(7, 0), + new RenderingData(0.6f, 7), + new GameTickData(7, 1), + new RenderingData(0.6f, 7), + new GameTickData(7, 2), + new RenderingData(0.6f, 7), + new GameTickData(7, 3), + new RenderingData(0.6f, 7), + + new GameTickData(8, 0), + new RenderingData(0.6f, 8), + new GameTickData(8, 1), + new RenderingData(0.6f, 8), + new GameTickData(8, 2), + new RenderingData(0.6f, 8), + new GameTickData(8, 3), + new RenderingData(0.6f, 8), + + new GameTickData(9, 0), + new RenderingData(0.6f, 9), + new GameTickData(9, 1), + new RenderingData(0.6f, 9), + new GameTickData(9, 2), + new RenderingData(0.6f, 9), + new GameTickData(9, 3), + new RenderingData(0.6f, 9), + + new GameTickData(10, 0), + new RenderingData(0.6f, 10), + new GameTickData(10, 1), + new RenderingData(0.6f, 10), + new GameTickData(10, 2), + new RenderingData(0.6f, 10), + new GameTickData(10, 3), + new RenderingData(0.6f, 10), + + // Animation starts again + new GameTickData(1, 0), + new RenderingData(0.1f, 1), + new GameTickData(1, 1), + new RenderingData(0.6f, 1), + new GameTickData(1, 2), + new RenderingData(0.6f, 1), + new GameTickData(1, 3), + new RenderingData(0.6f, 1), + }); +} diff --git a/test/appfat_test.cpp b/test/appfat_test.cpp index 2fa42b39a..f810f62ec 100644 --- a/test/appfat_test.cpp +++ b/test/appfat_test.cpp @@ -1,31 +1,31 @@ -#include - -#include "appfat.h" -#include "diablo.h" - -using namespace devilution; - -TEST(Appfat, app_fatal) -{ - EXPECT_EXIT(app_fatal("test"), ::testing::ExitedWithCode(1), "test"); -} - -TEST(Appfat, ErrDlg) -{ - EXPECT_EXIT(ErrDlg("Title", "Unknown error", "appfat.cpp", 7), ::testing::ExitedWithCode(1), "Unknown error\n\nThe error occurred at: appfat.cpp line 7"); -} - -TEST(Appfat, FileErrDlg) -{ - EXPECT_EXIT(FileErrDlg("devilution/image.cl2"), ::testing::ExitedWithCode(1), "devilution/image.cl2"); -} - -TEST(Appfat, InsertCDDlg) -{ - EXPECT_EXIT(InsertCDDlg(), ::testing::ExitedWithCode(1), "diabdat.mpq"); -} - -TEST(Appfat, DirErrorDlg) -{ - EXPECT_EXIT(DirErrorDlg("/"), ::testing::ExitedWithCode(1), "Unable to write to location:\n/"); -} +#include + +#include "appfat.h" +#include "diablo.h" + +using namespace devilution; + +TEST(Appfat, app_fatal) +{ + EXPECT_EXIT(app_fatal("test"), ::testing::ExitedWithCode(1), "test"); +} + +TEST(Appfat, ErrDlg) +{ + EXPECT_EXIT(ErrDlg("Title", "Unknown error", "appfat.cpp", 7), ::testing::ExitedWithCode(1), "Unknown error\n\nThe error occurred at: appfat.cpp line 7"); +} + +TEST(Appfat, FileErrDlg) +{ + EXPECT_EXIT(FileErrDlg("devilution/image.cl2"), ::testing::ExitedWithCode(1), "devilution/image.cl2"); +} + +TEST(Appfat, InsertCDDlg) +{ + EXPECT_EXIT(InsertCDDlg(), ::testing::ExitedWithCode(1), "diabdat.mpq"); +} + +TEST(Appfat, DirErrorDlg) +{ + EXPECT_EXIT(DirErrorDlg("/"), ::testing::ExitedWithCode(1), "Unable to write to location:\n/"); +} diff --git a/test/automap_test.cpp b/test/automap_test.cpp index 3e783e3ef..d603fb00f 100644 --- a/test/automap_test.cpp +++ b/test/automap_test.cpp @@ -1,127 +1,127 @@ -#include - -#include "automap.h" - -using namespace devilution; - -TEST(Automap, InitAutomap) -{ - InitAutomapOnce(); - EXPECT_EQ(AutomapActive, false); - EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine64, 32); - EXPECT_EQ(AmLine32, 16); - EXPECT_EQ(AmLine16, 8); - EXPECT_EQ(AmLine8, 4); - EXPECT_EQ(AmLine4, 2); -} - -TEST(Automap, StartAutomap) -{ - StartAutomap(); - EXPECT_EQ(AutomapOffset.x, 0); - EXPECT_EQ(AutomapOffset.y, 0); - EXPECT_EQ(AutomapActive, true); -} - -TEST(Automap, AutomapUp) -{ - AutomapOffset.x = 1; - AutomapOffset.y = 1; - AutomapUp(); - EXPECT_EQ(AutomapOffset.x, 0); - EXPECT_EQ(AutomapOffset.y, 0); -} - -TEST(Automap, AutomapDown) -{ - AutomapOffset.x = 1; - AutomapOffset.y = 1; - AutomapDown(); - EXPECT_EQ(AutomapOffset.x, 2); - EXPECT_EQ(AutomapOffset.y, 2); -} - -TEST(Automap, AutomapLeft) -{ - AutomapOffset.x = 1; - AutomapOffset.y = 1; - AutomapLeft(); - EXPECT_EQ(AutomapOffset.x, 0); - EXPECT_EQ(AutomapOffset.y, 2); -} - -TEST(Automap, AutomapRight) -{ - AutomapOffset.x = 1; - AutomapOffset.y = 1; - AutomapRight(); - EXPECT_EQ(AutomapOffset.x, 2); - EXPECT_EQ(AutomapOffset.y, 0); -} - -TEST(Automap, AutomapZoomIn) -{ - AutoMapScale = 50; - AutomapZoomIn(); - EXPECT_EQ(AutoMapScale, 55); - EXPECT_EQ(AmLine64, 35); - EXPECT_EQ(AmLine32, 17); - EXPECT_EQ(AmLine16, 8); - EXPECT_EQ(AmLine8, 4); - EXPECT_EQ(AmLine4, 2); -} - -TEST(Automap, AutomapZoomIn_Max) -{ - AutoMapScale = 195; - AutomapZoomIn(); - AutomapZoomIn(); - EXPECT_EQ(AutoMapScale, 200); - EXPECT_EQ(AmLine64, 128); - EXPECT_EQ(AmLine32, 64); - EXPECT_EQ(AmLine16, 32); - EXPECT_EQ(AmLine8, 16); - EXPECT_EQ(AmLine4, 8); -} - -TEST(Automap, AutomapZoomOut) -{ - AutoMapScale = 200; - AutomapZoomOut(); - EXPECT_EQ(AutoMapScale, 195); - EXPECT_EQ(AmLine64, 124); - EXPECT_EQ(AmLine32, 62); - EXPECT_EQ(AmLine16, 31); - EXPECT_EQ(AmLine8, 15); - EXPECT_EQ(AmLine4, 7); -} - -TEST(Automap, AutomapZoomOut_Min) -{ - AutoMapScale = 55; - AutomapZoomOut(); - AutomapZoomOut(); - EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine64, 32); - EXPECT_EQ(AmLine32, 16); - EXPECT_EQ(AmLine16, 8); - EXPECT_EQ(AmLine8, 4); - EXPECT_EQ(AmLine4, 2); -} - -TEST(Automap, AutomapZoomReset) -{ - AutoMapScale = 50; - AutomapOffset.x = 1; - AutomapOffset.y = 1; - AutomapZoomReset(); - EXPECT_EQ(AutomapOffset.x, 0); - EXPECT_EQ(AutomapOffset.y, 0); - EXPECT_EQ(AutoMapScale, 50); - EXPECT_EQ(AmLine64, 32); - EXPECT_EQ(AmLine32, 16); - EXPECT_EQ(AmLine16, 8); - EXPECT_EQ(AmLine8, 4); - EXPECT_EQ(AmLine4, 2); -} +#include + +#include "automap.h" + +using namespace devilution; + +TEST(Automap, InitAutomap) +{ + InitAutomapOnce(); + EXPECT_EQ(AutomapActive, false); + EXPECT_EQ(AutoMapScale, 50); + EXPECT_EQ(AmLine64, 32); + EXPECT_EQ(AmLine32, 16); + EXPECT_EQ(AmLine16, 8); + EXPECT_EQ(AmLine8, 4); + EXPECT_EQ(AmLine4, 2); +} + +TEST(Automap, StartAutomap) +{ + StartAutomap(); + EXPECT_EQ(AutomapOffset.x, 0); + EXPECT_EQ(AutomapOffset.y, 0); + EXPECT_EQ(AutomapActive, true); +} + +TEST(Automap, AutomapUp) +{ + AutomapOffset.x = 1; + AutomapOffset.y = 1; + AutomapUp(); + EXPECT_EQ(AutomapOffset.x, 0); + EXPECT_EQ(AutomapOffset.y, 0); +} + +TEST(Automap, AutomapDown) +{ + AutomapOffset.x = 1; + AutomapOffset.y = 1; + AutomapDown(); + EXPECT_EQ(AutomapOffset.x, 2); + EXPECT_EQ(AutomapOffset.y, 2); +} + +TEST(Automap, AutomapLeft) +{ + AutomapOffset.x = 1; + AutomapOffset.y = 1; + AutomapLeft(); + EXPECT_EQ(AutomapOffset.x, 0); + EXPECT_EQ(AutomapOffset.y, 2); +} + +TEST(Automap, AutomapRight) +{ + AutomapOffset.x = 1; + AutomapOffset.y = 1; + AutomapRight(); + EXPECT_EQ(AutomapOffset.x, 2); + EXPECT_EQ(AutomapOffset.y, 0); +} + +TEST(Automap, AutomapZoomIn) +{ + AutoMapScale = 50; + AutomapZoomIn(); + EXPECT_EQ(AutoMapScale, 55); + EXPECT_EQ(AmLine64, 35); + EXPECT_EQ(AmLine32, 17); + EXPECT_EQ(AmLine16, 8); + EXPECT_EQ(AmLine8, 4); + EXPECT_EQ(AmLine4, 2); +} + +TEST(Automap, AutomapZoomIn_Max) +{ + AutoMapScale = 195; + AutomapZoomIn(); + AutomapZoomIn(); + EXPECT_EQ(AutoMapScale, 200); + EXPECT_EQ(AmLine64, 128); + EXPECT_EQ(AmLine32, 64); + EXPECT_EQ(AmLine16, 32); + EXPECT_EQ(AmLine8, 16); + EXPECT_EQ(AmLine4, 8); +} + +TEST(Automap, AutomapZoomOut) +{ + AutoMapScale = 200; + AutomapZoomOut(); + EXPECT_EQ(AutoMapScale, 195); + EXPECT_EQ(AmLine64, 124); + EXPECT_EQ(AmLine32, 62); + EXPECT_EQ(AmLine16, 31); + EXPECT_EQ(AmLine8, 15); + EXPECT_EQ(AmLine4, 7); +} + +TEST(Automap, AutomapZoomOut_Min) +{ + AutoMapScale = 55; + AutomapZoomOut(); + AutomapZoomOut(); + EXPECT_EQ(AutoMapScale, 50); + EXPECT_EQ(AmLine64, 32); + EXPECT_EQ(AmLine32, 16); + EXPECT_EQ(AmLine16, 8); + EXPECT_EQ(AmLine8, 4); + EXPECT_EQ(AmLine4, 2); +} + +TEST(Automap, AutomapZoomReset) +{ + AutoMapScale = 50; + AutomapOffset.x = 1; + AutomapOffset.y = 1; + AutomapZoomReset(); + EXPECT_EQ(AutomapOffset.x, 0); + EXPECT_EQ(AutomapOffset.y, 0); + EXPECT_EQ(AutoMapScale, 50); + EXPECT_EQ(AmLine64, 32); + EXPECT_EQ(AmLine32, 16); + EXPECT_EQ(AmLine16, 8); + EXPECT_EQ(AmLine8, 4); + EXPECT_EQ(AmLine4, 2); +} diff --git a/test/codec_test.cpp b/test/codec_test.cpp index 00dd13f8d..aff8c86fe 100644 --- a/test/codec_test.cpp +++ b/test/codec_test.cpp @@ -1,15 +1,15 @@ -#include - -#include "codec.h" - -using namespace devilution; - -TEST(Codec, codec_get_encoded_len) -{ - EXPECT_EQ(codec_get_encoded_len(50), 72); -} - -TEST(Codec, codec_get_encoded_len_eq) -{ - EXPECT_EQ(codec_get_encoded_len(128), 136); -} +#include + +#include "codec.h" + +using namespace devilution; + +TEST(Codec, codec_get_encoded_len) +{ + EXPECT_EQ(codec_get_encoded_len(50), 72); +} + +TEST(Codec, codec_get_encoded_len_eq) +{ + EXPECT_EQ(codec_get_encoded_len(128), 136); +} diff --git a/test/control_test.cpp b/test/control_test.cpp index 181472384..977a1dcf8 100644 --- a/test/control_test.cpp +++ b/test/control_test.cpp @@ -1,29 +1,29 @@ -#include - -#include "control.h" - -using namespace devilution; - -TEST(Control, SetSpell) -{ - pnumlines = 1; - pinfoflag = true; - pSpell = SPL_FIREBOLT; - pSplType = RSPLTYPE_CHARGES; - SetSpell(); - EXPECT_EQ(spselflag, false); - EXPECT_EQ(plr[myplr]._pRSpell, SPL_FIREBOLT); - EXPECT_EQ(plr[myplr]._pRSplType, RSPLTYPE_CHARGES); - EXPECT_EQ(pnumlines, 0); - EXPECT_EQ(pinfoflag, false); - EXPECT_EQ(force_redraw, 255); -} - -TEST(Control, ClearPanel) -{ - pnumlines = 1; - pinfoflag = true; - ClearPanel(); - EXPECT_EQ(pnumlines, 0); - EXPECT_EQ(pinfoflag, false); -} +#include + +#include "control.h" + +using namespace devilution; + +TEST(Control, SetSpell) +{ + pnumlines = 1; + pinfoflag = true; + pSpell = SPL_FIREBOLT; + pSplType = RSPLTYPE_CHARGES; + SetSpell(); + EXPECT_EQ(spselflag, false); + EXPECT_EQ(plr[myplr]._pRSpell, SPL_FIREBOLT); + EXPECT_EQ(plr[myplr]._pRSplType, RSPLTYPE_CHARGES); + EXPECT_EQ(pnumlines, 0); + EXPECT_EQ(pinfoflag, false); + EXPECT_EQ(force_redraw, 255); +} + +TEST(Control, ClearPanel) +{ + pnumlines = 1; + pinfoflag = true; + ClearPanel(); + EXPECT_EQ(pnumlines, 0); + EXPECT_EQ(pinfoflag, false); +} diff --git a/test/cursor_test.cpp b/test/cursor_test.cpp index e7516429d..e3cf71a2d 100644 --- a/test/cursor_test.cpp +++ b/test/cursor_test.cpp @@ -1,19 +1,19 @@ -#include - -#include "cursor.h" -#include "itemdat.h" - -using namespace devilution; - -TEST(Cursor, SetCursor) -{ - int i = ICURS_SPIKED_CLUB + CURSOR_FIRSTITEM; - NewCursor(i); - EXPECT_EQ(pcurs, i); - EXPECT_EQ(cursW, 1 * 28); - EXPECT_EQ(cursH, 3 * 28); - EXPECT_EQ(icursW, 1 * 28); - EXPECT_EQ(icursH, 3 * 28); - EXPECT_EQ(icursW28, 1); - EXPECT_EQ(icursH28, 3); -} +#include + +#include "cursor.h" +#include "itemdat.h" + +using namespace devilution; + +TEST(Cursor, SetCursor) +{ + int i = ICURS_SPIKED_CLUB + CURSOR_FIRSTITEM; + NewCursor(i); + EXPECT_EQ(pcurs, i); + EXPECT_EQ(cursW, 1 * 28); + EXPECT_EQ(cursH, 3 * 28); + EXPECT_EQ(icursW, 1 * 28); + EXPECT_EQ(icursH, 3 * 28); + EXPECT_EQ(icursW28, 1); + EXPECT_EQ(icursH28, 3); +} diff --git a/test/dead_test.cpp b/test/dead_test.cpp index 4b4fcabd6..ab86e1276 100644 --- a/test/dead_test.cpp +++ b/test/dead_test.cpp @@ -1,19 +1,19 @@ -#include - -#include "dead.h" -#include "engine.h" -#include "gendung.h" - -using namespace devilution; - -TEST(Dead, AddDead) -{ - AddDead({21, 48}, 8, DIR_W); - EXPECT_EQ(dDead[21][48], 8 + (DIR_W << 5)); -} - -TEST(Dead, AddDead_OOB) -{ - AddDead({21, 48}, MaxDead + 1, DIR_W); - EXPECT_EQ(dDead[21][48], 0 + (DIR_W << 5)); -} +#include + +#include "dead.h" +#include "engine.h" +#include "gendung.h" + +using namespace devilution; + +TEST(Dead, AddDead) +{ + AddDead({21, 48}, 8, DIR_W); + EXPECT_EQ(dDead[21][48], 8 + (DIR_W << 5)); +} + +TEST(Dead, AddDead_OOB) +{ + AddDead({21, 48}, MaxDead + 1, DIR_W); + EXPECT_EQ(dDead[21][48], 0 + (DIR_W << 5)); +} diff --git a/test/diablo_test.cpp b/test/diablo_test.cpp index f2c0ca13e..64c432ec3 100644 --- a/test/diablo_test.cpp +++ b/test/diablo_test.cpp @@ -1,14 +1,14 @@ -#include - -#include "diablo.h" -#include "multi.h" - -using namespace devilution; - -TEST(Diablo, diablo_pause_game_unpause) -{ - gbIsMultiplayer = false; - PauseMode = 1; - diablo_pause_game(); - EXPECT_EQ(PauseMode, 0); -} +#include + +#include "diablo.h" +#include "multi.h" + +using namespace devilution; + +TEST(Diablo, diablo_pause_game_unpause) +{ + gbIsMultiplayer = false; + PauseMode = 1; + diablo_pause_game(); + EXPECT_EQ(PauseMode, 0); +} diff --git a/test/doom_test.cpp b/test/doom_test.cpp index 9cb914f79..c20a20ffa 100644 --- a/test/doom_test.cpp +++ b/test/doom_test.cpp @@ -1,17 +1,17 @@ -#include - -#include "doom.h" - -using namespace devilution; - -TEST(Doom, doom_get_frame_from_time) -{ - DoomQuestState = 1200 * 8 + 548; - EXPECT_EQ(doom_get_frame_from_time(), 8); -} - -TEST(Doom, doom_get_frame_from_time_max) -{ - DoomQuestState = 1200 * 30 + 1; - EXPECT_EQ(doom_get_frame_from_time(), 31); -} +#include + +#include "doom.h" + +using namespace devilution; + +TEST(Doom, doom_get_frame_from_time) +{ + DoomQuestState = 1200 * 8 + 548; + EXPECT_EQ(doom_get_frame_from_time(), 8); +} + +TEST(Doom, doom_get_frame_from_time_max) +{ + DoomQuestState = 1200 * 30 + 1; + EXPECT_EQ(doom_get_frame_from_time(), 31); +} diff --git a/test/drlg_l1_test.cpp b/test/drlg_l1_test.cpp index 89b979755..366967a3c 100644 --- a/test/drlg_l1_test.cpp +++ b/test/drlg_l1_test.cpp @@ -1,21 +1,21 @@ -#include - -#include "diablo.h" -#include "drlg_l1.h" -#include "lighting.h" - -using namespace devilution; - -TEST(Drlg_l1, DRLG_Init_Globals_noflag) -{ - lightflag = false; - DRLG_Init_Globals(); - EXPECT_EQ(dLight[0][0], 15); -} - -TEST(Drlg_l1, DRLG_Init_Globals) -{ - lightflag = true; - DRLG_Init_Globals(); - EXPECT_EQ(dLight[0][0], 0); -} +#include + +#include "diablo.h" +#include "drlg_l1.h" +#include "lighting.h" + +using namespace devilution; + +TEST(Drlg_l1, DRLG_Init_Globals_noflag) +{ + lightflag = false; + DRLG_Init_Globals(); + EXPECT_EQ(dLight[0][0], 15); +} + +TEST(Drlg_l1, DRLG_Init_Globals) +{ + lightflag = true; + DRLG_Init_Globals(); + EXPECT_EQ(dLight[0][0], 0); +} diff --git a/test/drlg_l2_test.cpp b/test/drlg_l2_test.cpp index 4d98edac2..2f1041160 100644 --- a/test/drlg_l2_test.cpp +++ b/test/drlg_l2_test.cpp @@ -1,12 +1,12 @@ -#include - -#include "drlg_l2.h" - -using namespace devilution; - -TEST(Drlg_l2, InitDungeon) -{ - InitDungeon(); - EXPECT_EQ(predungeon[0][0], 32); - EXPECT_EQ(dflags[0][0], 0); -} +#include + +#include "drlg_l2.h" + +using namespace devilution; + +TEST(Drlg_l2, InitDungeon) +{ + InitDungeon(); + EXPECT_EQ(predungeon[0][0], 32); + EXPECT_EQ(dflags[0][0], 0); +} diff --git a/test/drlg_l3_test.cpp b/test/drlg_l3_test.cpp index f1e4a5b5f..a0ade7700 100644 --- a/test/drlg_l3_test.cpp +++ b/test/drlg_l3_test.cpp @@ -1,35 +1,35 @@ -#include - -#include "drlg_l3.h" - -using namespace devilution; - -TEST(Drlg_l3, AddFenceDoors_x) -{ - memset(dungeon, 0, sizeof(dungeon)); - dungeon[5][5] = 7; - dungeon[5 - 1][5] = 130; - dungeon[5 + 1][5] = 152; - AddFenceDoors(); - EXPECT_EQ(dungeon[5][5], 146); -} - -TEST(Drlg_l3, AddFenceDoors_y) -{ - memset(dungeon, 0, sizeof(dungeon)); - dungeon[5][5] = 7; - dungeon[5][5 - 1] = 130; - dungeon[5][5 + 1] = 152; - AddFenceDoors(); - EXPECT_EQ(dungeon[5][5], 147); -} - -TEST(Drlg_l3, AddFenceDoors_no) -{ - memset(dungeon, 0, sizeof(dungeon)); - dungeon[5][5] = 7; - dungeon[5 - 1][5] = 130; - dungeon[5 + 1][5] = 153; - AddFenceDoors(); - EXPECT_EQ(dungeon[5][5], 7); -} +#include + +#include "drlg_l3.h" + +using namespace devilution; + +TEST(Drlg_l3, AddFenceDoors_x) +{ + memset(dungeon, 0, sizeof(dungeon)); + dungeon[5][5] = 7; + dungeon[5 - 1][5] = 130; + dungeon[5 + 1][5] = 152; + AddFenceDoors(); + EXPECT_EQ(dungeon[5][5], 146); +} + +TEST(Drlg_l3, AddFenceDoors_y) +{ + memset(dungeon, 0, sizeof(dungeon)); + dungeon[5][5] = 7; + dungeon[5][5 - 1] = 130; + dungeon[5][5 + 1] = 152; + AddFenceDoors(); + EXPECT_EQ(dungeon[5][5], 147); +} + +TEST(Drlg_l3, AddFenceDoors_no) +{ + memset(dungeon, 0, sizeof(dungeon)); + dungeon[5][5] = 7; + dungeon[5 - 1][5] = 130; + dungeon[5 + 1][5] = 153; + AddFenceDoors(); + EXPECT_EQ(dungeon[5][5], 7); +} diff --git a/test/drlg_l4_test.cpp b/test/drlg_l4_test.cpp index 7f351e309..5c3a5371b 100644 --- a/test/drlg_l4_test.cpp +++ b/test/drlg_l4_test.cpp @@ -1,21 +1,21 @@ -#include - -#include "drlg_l4.h" - -using namespace devilution; - -TEST(Drlg_l4, IsDURWall) -{ - EXPECT_EQ(IsDURWall(25), true); - EXPECT_EQ(IsDURWall(28), true); - EXPECT_EQ(IsDURWall(23), true); - EXPECT_EQ(IsDURWall(20), false); -} - -TEST(Drlg_l4, IsDLLWall) -{ - EXPECT_EQ(IsDLLWall(27), true); - EXPECT_EQ(IsDLLWall(26), true); - EXPECT_EQ(IsDLLWall(22), true); - EXPECT_EQ(IsDLLWall(20), false); -} +#include + +#include "drlg_l4.h" + +using namespace devilution; + +TEST(Drlg_l4, IsDURWall) +{ + EXPECT_EQ(IsDURWall(25), true); + EXPECT_EQ(IsDURWall(28), true); + EXPECT_EQ(IsDURWall(23), true); + EXPECT_EQ(IsDURWall(20), false); +} + +TEST(Drlg_l4, IsDLLWall) +{ + EXPECT_EQ(IsDLLWall(27), true); + EXPECT_EQ(IsDLLWall(26), true); + EXPECT_EQ(IsDLLWall(22), true); + EXPECT_EQ(IsDLLWall(20), false); +} diff --git a/test/effects_test.cpp b/test/effects_test.cpp index 25ef2cf3e..aaf556c2e 100644 --- a/test/effects_test.cpp +++ b/test/effects_test.cpp @@ -1,56 +1,56 @@ -#include - -#include "effects.h" -#include "player.h" - -using namespace devilution; - -TEST(Effects, calc_snd_position_center) -{ - plr[myplr].position.tile = { 50, 50 }; - int plVolume = 0; - int plPan = 0; - EXPECT_EQ(calc_snd_position(50, 50, &plVolume, &plPan), true); - EXPECT_EQ(plVolume, 0); - EXPECT_EQ(plPan, 0); -} - -TEST(Effects, calc_snd_position_near) -{ - plr[myplr].position.tile = { 50, 50 }; - int plVolume = 0; - int plPan = 0; - EXPECT_EQ(calc_snd_position(55, 50, &plVolume, &plPan), true); - EXPECT_EQ(plVolume, -320); - EXPECT_EQ(plPan, 1280); -} - -TEST(Effects, calc_snd_position_out_of_range) -{ - plr[myplr].position.tile = { 12, 12 }; - int plVolume = 1234; - int plPan = 0; - EXPECT_EQ(calc_snd_position(112, 112, &plVolume, &plPan), false); - EXPECT_EQ(plVolume, 1234); - EXPECT_EQ(plPan, 0); -} - -TEST(Effects, calc_snd_position_extreme_right) -{ - plr[myplr].position.tile = { 50, 50 }; - int plVolume = 0; - int plPan = 0; - EXPECT_EQ(calc_snd_position(75, 25, &plVolume, &plPan), true); - EXPECT_EQ(plVolume, -2176); - EXPECT_EQ(plPan, 6400); -} - -TEST(Effects, calc_snd_position_extreme_left) -{ - plr[myplr].position.tile = { 50, 50 }; - int plVolume = 0; - int plPan = 0; - EXPECT_EQ(calc_snd_position(25, 75, &plVolume, &plPan), true); - EXPECT_EQ(plVolume, -2176); - EXPECT_EQ(plPan, -6400); -} +#include + +#include "effects.h" +#include "player.h" + +using namespace devilution; + +TEST(Effects, calc_snd_position_center) +{ + plr[myplr].position.tile = { 50, 50 }; + int plVolume = 0; + int plPan = 0; + EXPECT_EQ(calc_snd_position(50, 50, &plVolume, &plPan), true); + EXPECT_EQ(plVolume, 0); + EXPECT_EQ(plPan, 0); +} + +TEST(Effects, calc_snd_position_near) +{ + plr[myplr].position.tile = { 50, 50 }; + int plVolume = 0; + int plPan = 0; + EXPECT_EQ(calc_snd_position(55, 50, &plVolume, &plPan), true); + EXPECT_EQ(plVolume, -320); + EXPECT_EQ(plPan, 1280); +} + +TEST(Effects, calc_snd_position_out_of_range) +{ + plr[myplr].position.tile = { 12, 12 }; + int plVolume = 1234; + int plPan = 0; + EXPECT_EQ(calc_snd_position(112, 112, &plVolume, &plPan), false); + EXPECT_EQ(plVolume, 1234); + EXPECT_EQ(plPan, 0); +} + +TEST(Effects, calc_snd_position_extreme_right) +{ + plr[myplr].position.tile = { 50, 50 }; + int plVolume = 0; + int plPan = 0; + EXPECT_EQ(calc_snd_position(75, 25, &plVolume, &plPan), true); + EXPECT_EQ(plVolume, -2176); + EXPECT_EQ(plPan, 6400); +} + +TEST(Effects, calc_snd_position_extreme_left) +{ + plr[myplr].position.tile = { 50, 50 }; + int plVolume = 0; + int plPan = 0; + EXPECT_EQ(calc_snd_position(25, 75, &plVolume, &plPan), true); + EXPECT_EQ(plVolume, -2176); + EXPECT_EQ(plPan, -6400); +} diff --git a/test/inv_test.cpp b/test/inv_test.cpp index 4fa3b3a17..92face666 100644 --- a/test/inv_test.cpp +++ b/test/inv_test.cpp @@ -1,226 +1,226 @@ -#include - -#include "cursor.h" -#include "inv.h" -#include "player.h" - -using namespace devilution; - -/* Set up a given item as a spell scroll, allowing for its usage. */ -void set_up_scroll(ItemStruct &item, spell_id spell) -{ - pcurs = CURSOR_HAND; - leveltype = DTYPE_CATACOMBS; - plr[myplr]._pRSpell = static_cast(spell); - item._itype = ITYPE_MISC; - item._iMiscId = IMISC_SCROLL; - item._iSpell = spell; -} - -/* Clear the inventory of myplr. */ -void clear_inventory() -{ - for (int i = 0; i < NUM_INV_GRID_ELEM; i++) { - memset(&plr[myplr].InvList[i], 0, sizeof(ItemStruct)); - plr[myplr].InvGrid[i] = 0; - } - plr[myplr]._pNumInv = 0; -} - -// Test that the scroll is used in the inventory in correct conditions -TEST(Inv, UseScroll_from_inventory) -{ - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - plr[myplr]._pNumInv = 5; - EXPECT_TRUE(UseScroll()); -} - -// Test that the scroll is used in the belt in correct conditions -TEST(Inv, UseScroll_from_belt) -{ - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - EXPECT_TRUE(UseScroll()); -} - -// Test that the scroll is not used in the inventory for each invalid condition -TEST(Inv, UseScroll_from_inventory_invalid_conditions) -{ - // Empty the belt to prevent using a scroll from the belt - for (int i = 0; i < MAXBELTITEMS; i++) { - plr[myplr].SpdList[i]._itype = ITYPE_NONE; - } - - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - pcurs = CURSOR_IDENTIFY; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - leveltype = DTYPE_TOWN; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - plr[myplr]._pRSpell = static_cast(SPL_HEAL); - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - plr[myplr].InvList[2]._iMiscId = IMISC_STAFF; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); - plr[myplr].InvList[2]._itype = ITYPE_NONE; - EXPECT_FALSE(UseScroll()); -} - -// Test that the scroll is not used in the belt for each invalid condition -TEST(Inv, UseScroll_from_belt_invalid_conditions) -{ - // Disable the inventory to prevent using a scroll from the inventory - plr[myplr]._pNumInv = 0; - - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - pcurs = CURSOR_IDENTIFY; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - leveltype = DTYPE_TOWN; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - plr[myplr]._pRSpell = static_cast(SPL_HEAL); - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - plr[myplr].SpdList[2]._iMiscId = IMISC_STAFF; - EXPECT_FALSE(UseScroll()); - - set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); - plr[myplr].SpdList[2]._itype = ITYPE_NONE; - EXPECT_FALSE(UseScroll()); -} - -// Test gold calculation -TEST(Inv, CalculateGold) -{ - plr[myplr]._pNumInv = 10; - // Set up two slots of gold both in the belt and inventory - plr[myplr].SpdList[1]._itype = ITYPE_GOLD; - plr[myplr].SpdList[5]._itype = ITYPE_GOLD; - plr[myplr].InvList[2]._itype = ITYPE_GOLD; - plr[myplr].InvList[3]._itype = ITYPE_GOLD; - // Set the gold amount to arbitrary values - plr[myplr].SpdList[1]._ivalue = 100; - plr[myplr].SpdList[5]._ivalue = 200; - plr[myplr].InvList[2]._ivalue = 3; - plr[myplr].InvList[3]._ivalue = 30; - - EXPECT_EQ(CalculateGold(myplr), 333); -} - -// Test automatic gold placing -TEST(Inv, GoldAutoPlace) -{ - // Empty the inventory - clear_inventory(); - - // Put gold into the inventory: - // | 1000 | ... | ... - plr[myplr].InvList[0]._itype = ITYPE_GOLD; - plr[myplr].InvList[0]._ivalue = 1000; - plr[myplr]._pNumInv = 1; - // Put (max gold - 100) gold, which is 4900, into the player's hand - plr[myplr].HoldItem._itype = ITYPE_GOLD; - plr[myplr].HoldItem._ivalue = GOLD_MAX_LIMIT - 100; - - GoldAutoPlace(myplr); - // We expect the inventory: - // | 5000 | 900 | ... - EXPECT_EQ(plr[myplr].InvList[0]._ivalue, GOLD_MAX_LIMIT); - EXPECT_EQ(plr[myplr].InvList[1]._ivalue, 900); -} - -// Test removing an item from inventory with no other items. -TEST(Inv, RemoveInvItem) -{ - clear_inventory(); - // Put a two-slot misc item into the inventory: - // | (item) | (item) | ... | ... - plr[myplr]._pNumInv = 1; - plr[myplr].InvGrid[0] = 1; - plr[myplr].InvGrid[1] = -1; - plr[myplr].InvList[0]._itype = ITYPE_MISC; - - plr[myplr].RemoveInvItem(0); - EXPECT_EQ(plr[myplr].InvGrid[0], 0); - EXPECT_EQ(plr[myplr].InvGrid[1], 0); - EXPECT_EQ(plr[myplr]._pNumInv, 0); -} - -// Test removing an item from inventory with other items in it. -TEST(Inv, RemoveInvItem_other_item) -{ - clear_inventory(); - // Put a two-slot misc item and a ring into the inventory: - // | (item) | (item) | (ring) | ... - plr[myplr]._pNumInv = 2; - plr[myplr].InvGrid[0] = 1; - plr[myplr].InvGrid[1] = -1; - plr[myplr].InvList[0]._itype = ITYPE_MISC; - - plr[myplr].InvGrid[2] = 2; - plr[myplr].InvList[1]._itype = ITYPE_RING; - - plr[myplr].RemoveInvItem(0); - EXPECT_EQ(plr[myplr].InvGrid[0], 0); - EXPECT_EQ(plr[myplr].InvGrid[1], 0); - EXPECT_EQ(plr[myplr].InvGrid[2], 1); - EXPECT_EQ(plr[myplr].InvList[0]._itype, ITYPE_RING); - EXPECT_EQ(plr[myplr]._pNumInv, 1); -} - -// Test removing an item from the belt -TEST(Inv, RemoveSpdBarItem) -{ - // Clear the belt - for (int i = 0; i < MAXBELTITEMS; i++) { - plr[myplr].SpdList[i]._itype = ITYPE_NONE; - } - // Put an item in the belt: | x | x | item | x | x | x | x | x | - plr[myplr].SpdList[3]._itype = ITYPE_MISC; - - RemoveSpdBarItem(myplr, 3); - EXPECT_EQ(plr[myplr].SpdList[3]._itype, ITYPE_NONE); -} - -// Test removing a scroll from the inventory -TEST(Inv, RemoveScroll_inventory) -{ - clear_inventory(); - - // Put a firebolt scroll into the inventory - plr[myplr]._pNumInv = 1; - plr[myplr]._pRSpell = static_cast(SPL_FIREBOLT); - plr[myplr].InvList[0]._itype = ITYPE_MISC; - plr[myplr].InvList[0]._iMiscId = IMISC_SCROLL; - plr[myplr].InvList[0]._iSpell = SPL_FIREBOLT; - - RemoveScroll(myplr); - EXPECT_EQ(plr[myplr].InvGrid[0], 0); - EXPECT_EQ(plr[myplr]._pNumInv, 0); -} - -// Test removing a scroll from the belt -TEST(Inv, RemoveScroll_belt) -{ - // Clear the belt - for (int i = 0; i < MAXBELTITEMS; i++) { - plr[myplr].SpdList[i]._itype = ITYPE_NONE; - } - // Put a firebolt scroll into the belt - plr[myplr]._pSpell = static_cast(SPL_FIREBOLT); - plr[myplr].SpdList[3]._itype = ITYPE_MISC; - plr[myplr].SpdList[3]._iMiscId = IMISC_SCROLL; - plr[myplr].SpdList[3]._iSpell = SPL_FIREBOLT; - - RemoveScroll(myplr); - EXPECT_EQ(plr[myplr].SpdList[3]._itype, ITYPE_NONE); -} +#include + +#include "cursor.h" +#include "inv.h" +#include "player.h" + +using namespace devilution; + +/* Set up a given item as a spell scroll, allowing for its usage. */ +void set_up_scroll(ItemStruct &item, spell_id spell) +{ + pcurs = CURSOR_HAND; + leveltype = DTYPE_CATACOMBS; + plr[myplr]._pRSpell = static_cast(spell); + item._itype = ITYPE_MISC; + item._iMiscId = IMISC_SCROLL; + item._iSpell = spell; +} + +/* Clear the inventory of myplr. */ +void clear_inventory() +{ + for (int i = 0; i < NUM_INV_GRID_ELEM; i++) { + memset(&plr[myplr].InvList[i], 0, sizeof(ItemStruct)); + plr[myplr].InvGrid[i] = 0; + } + plr[myplr]._pNumInv = 0; +} + +// Test that the scroll is used in the inventory in correct conditions +TEST(Inv, UseScroll_from_inventory) +{ + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + plr[myplr]._pNumInv = 5; + EXPECT_TRUE(UseScroll()); +} + +// Test that the scroll is used in the belt in correct conditions +TEST(Inv, UseScroll_from_belt) +{ + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + EXPECT_TRUE(UseScroll()); +} + +// Test that the scroll is not used in the inventory for each invalid condition +TEST(Inv, UseScroll_from_inventory_invalid_conditions) +{ + // Empty the belt to prevent using a scroll from the belt + for (int i = 0; i < MAXBELTITEMS; i++) { + plr[myplr].SpdList[i]._itype = ITYPE_NONE; + } + + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + pcurs = CURSOR_IDENTIFY; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + leveltype = DTYPE_TOWN; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + plr[myplr]._pRSpell = static_cast(SPL_HEAL); + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + plr[myplr].InvList[2]._iMiscId = IMISC_STAFF; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].InvList[2], SPL_FIREBOLT); + plr[myplr].InvList[2]._itype = ITYPE_NONE; + EXPECT_FALSE(UseScroll()); +} + +// Test that the scroll is not used in the belt for each invalid condition +TEST(Inv, UseScroll_from_belt_invalid_conditions) +{ + // Disable the inventory to prevent using a scroll from the inventory + plr[myplr]._pNumInv = 0; + + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + pcurs = CURSOR_IDENTIFY; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + leveltype = DTYPE_TOWN; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + plr[myplr]._pRSpell = static_cast(SPL_HEAL); + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + plr[myplr].SpdList[2]._iMiscId = IMISC_STAFF; + EXPECT_FALSE(UseScroll()); + + set_up_scroll(plr[myplr].SpdList[2], SPL_FIREBOLT); + plr[myplr].SpdList[2]._itype = ITYPE_NONE; + EXPECT_FALSE(UseScroll()); +} + +// Test gold calculation +TEST(Inv, CalculateGold) +{ + plr[myplr]._pNumInv = 10; + // Set up two slots of gold both in the belt and inventory + plr[myplr].SpdList[1]._itype = ITYPE_GOLD; + plr[myplr].SpdList[5]._itype = ITYPE_GOLD; + plr[myplr].InvList[2]._itype = ITYPE_GOLD; + plr[myplr].InvList[3]._itype = ITYPE_GOLD; + // Set the gold amount to arbitrary values + plr[myplr].SpdList[1]._ivalue = 100; + plr[myplr].SpdList[5]._ivalue = 200; + plr[myplr].InvList[2]._ivalue = 3; + plr[myplr].InvList[3]._ivalue = 30; + + EXPECT_EQ(CalculateGold(myplr), 333); +} + +// Test automatic gold placing +TEST(Inv, GoldAutoPlace) +{ + // Empty the inventory + clear_inventory(); + + // Put gold into the inventory: + // | 1000 | ... | ... + plr[myplr].InvList[0]._itype = ITYPE_GOLD; + plr[myplr].InvList[0]._ivalue = 1000; + plr[myplr]._pNumInv = 1; + // Put (max gold - 100) gold, which is 4900, into the player's hand + plr[myplr].HoldItem._itype = ITYPE_GOLD; + plr[myplr].HoldItem._ivalue = GOLD_MAX_LIMIT - 100; + + GoldAutoPlace(myplr); + // We expect the inventory: + // | 5000 | 900 | ... + EXPECT_EQ(plr[myplr].InvList[0]._ivalue, GOLD_MAX_LIMIT); + EXPECT_EQ(plr[myplr].InvList[1]._ivalue, 900); +} + +// Test removing an item from inventory with no other items. +TEST(Inv, RemoveInvItem) +{ + clear_inventory(); + // Put a two-slot misc item into the inventory: + // | (item) | (item) | ... | ... + plr[myplr]._pNumInv = 1; + plr[myplr].InvGrid[0] = 1; + plr[myplr].InvGrid[1] = -1; + plr[myplr].InvList[0]._itype = ITYPE_MISC; + + plr[myplr].RemoveInvItem(0); + EXPECT_EQ(plr[myplr].InvGrid[0], 0); + EXPECT_EQ(plr[myplr].InvGrid[1], 0); + EXPECT_EQ(plr[myplr]._pNumInv, 0); +} + +// Test removing an item from inventory with other items in it. +TEST(Inv, RemoveInvItem_other_item) +{ + clear_inventory(); + // Put a two-slot misc item and a ring into the inventory: + // | (item) | (item) | (ring) | ... + plr[myplr]._pNumInv = 2; + plr[myplr].InvGrid[0] = 1; + plr[myplr].InvGrid[1] = -1; + plr[myplr].InvList[0]._itype = ITYPE_MISC; + + plr[myplr].InvGrid[2] = 2; + plr[myplr].InvList[1]._itype = ITYPE_RING; + + plr[myplr].RemoveInvItem(0); + EXPECT_EQ(plr[myplr].InvGrid[0], 0); + EXPECT_EQ(plr[myplr].InvGrid[1], 0); + EXPECT_EQ(plr[myplr].InvGrid[2], 1); + EXPECT_EQ(plr[myplr].InvList[0]._itype, ITYPE_RING); + EXPECT_EQ(plr[myplr]._pNumInv, 1); +} + +// Test removing an item from the belt +TEST(Inv, RemoveSpdBarItem) +{ + // Clear the belt + for (int i = 0; i < MAXBELTITEMS; i++) { + plr[myplr].SpdList[i]._itype = ITYPE_NONE; + } + // Put an item in the belt: | x | x | item | x | x | x | x | x | + plr[myplr].SpdList[3]._itype = ITYPE_MISC; + + RemoveSpdBarItem(myplr, 3); + EXPECT_EQ(plr[myplr].SpdList[3]._itype, ITYPE_NONE); +} + +// Test removing a scroll from the inventory +TEST(Inv, RemoveScroll_inventory) +{ + clear_inventory(); + + // Put a firebolt scroll into the inventory + plr[myplr]._pNumInv = 1; + plr[myplr]._pRSpell = static_cast(SPL_FIREBOLT); + plr[myplr].InvList[0]._itype = ITYPE_MISC; + plr[myplr].InvList[0]._iMiscId = IMISC_SCROLL; + plr[myplr].InvList[0]._iSpell = SPL_FIREBOLT; + + RemoveScroll(myplr); + EXPECT_EQ(plr[myplr].InvGrid[0], 0); + EXPECT_EQ(plr[myplr]._pNumInv, 0); +} + +// Test removing a scroll from the belt +TEST(Inv, RemoveScroll_belt) +{ + // Clear the belt + for (int i = 0; i < MAXBELTITEMS; i++) { + plr[myplr].SpdList[i]._itype = ITYPE_NONE; + } + // Put a firebolt scroll into the belt + plr[myplr]._pSpell = static_cast(SPL_FIREBOLT); + plr[myplr].SpdList[3]._itype = ITYPE_MISC; + plr[myplr].SpdList[3]._iMiscId = IMISC_SCROLL; + plr[myplr].SpdList[3]._iSpell = SPL_FIREBOLT; + + RemoveScroll(myplr); + EXPECT_EQ(plr[myplr].SpdList[3]._itype, ITYPE_NONE); +} diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp index fa3ed0b46..d778f6aa5 100644 --- a/test/lighting_test.cpp +++ b/test/lighting_test.cpp @@ -1,38 +1,38 @@ -#include - -#include "control.h" -#include "lighting.h" - -using namespace devilution; - -TEST(Lighting, CrawlTables) -{ - int CrawlNum[19] = { 0, 3, 12, 45, 94, 159, 240, 337, 450, 579, 724, 885, 1062, 1255, 1464, 1689, 1930, 2187, 2460 }; - - bool added[40][40]; - memset(added, 0, sizeof(added)); - - for (int j = 0; j < 19; j++) { - int x = 20; - int y = 20; - int cr = CrawlNum[j] + 1; - for (unsigned i = (uint8_t)CrawlTable[cr - 1]; i > 0; i--, cr += 2) { - int dx = x + CrawlTable[cr]; - int dy = y + CrawlTable[cr + 1]; - sprintf(tempstr, "location %d:%d added twice.", dx - 20, dy - 20); - EXPECT_EQ(added[dx][dy], false) << tempstr; - added[dx][dy] = true; - } - } - - for (int i = -18; i <= 18; i++) { - for (int j = -18; j <= 18; j++) { - if (added[i + 20][j + 20]) - continue; - if ((i == -18 && j == -18) || (i == -18 && j == 18) || (i == 18 && j == -18) || (i == 18 && j == 18)) - continue; // Limit of the crawl table rage - sprintf(tempstr, "while checking location %d:%d.", i, j); - EXPECT_EQ(false, true) << tempstr; - } - } -} +#include + +#include "control.h" +#include "lighting.h" + +using namespace devilution; + +TEST(Lighting, CrawlTables) +{ + int CrawlNum[19] = { 0, 3, 12, 45, 94, 159, 240, 337, 450, 579, 724, 885, 1062, 1255, 1464, 1689, 1930, 2187, 2460 }; + + bool added[40][40]; + memset(added, 0, sizeof(added)); + + for (int j = 0; j < 19; j++) { + int x = 20; + int y = 20; + int cr = CrawlNum[j] + 1; + for (unsigned i = (uint8_t)CrawlTable[cr - 1]; i > 0; i--, cr += 2) { + int dx = x + CrawlTable[cr]; + int dy = y + CrawlTable[cr + 1]; + sprintf(tempstr, "location %d:%d added twice.", dx - 20, dy - 20); + EXPECT_EQ(added[dx][dy], false) << tempstr; + added[dx][dy] = true; + } + } + + for (int i = -18; i <= 18; i++) { + for (int j = -18; j <= 18; j++) { + if (added[i + 20][j + 20]) + continue; + if ((i == -18 && j == -18) || (i == -18 && j == 18) || (i == 18 && j == -18) || (i == 18 && j == 18)) + continue; // Limit of the crawl table rage + sprintf(tempstr, "while checking location %d:%d.", i, j); + EXPECT_EQ(false, true) << tempstr; + } + } +} diff --git a/test/missiles_test.cpp b/test/missiles_test.cpp index 2f99ca1e7..e9c2f8313 100644 --- a/test/missiles_test.cpp +++ b/test/missiles_test.cpp @@ -1,87 +1,87 @@ -#include - -#include "missiles.h" - -using namespace devilution; - -TEST(Missiles, GetDirection8) -{ - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 15 })); - EXPECT_EQ(1, GetDirection({ 0, 0 }, { 0, 15 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 15 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 8 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 8 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 7 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 11, 7 })); - EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 11 })); - EXPECT_EQ(4, GetDirection({ 15, 15 }, { 0, 0 })); - EXPECT_EQ(5, GetDirection({ 0, 15 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 8, 15 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 8, 8 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 15, 8 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 15, 7 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 11, 7 }, { 0, 0 })); - EXPECT_EQ(4, GetDirection({ 8, 11 }, { 0, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 15 }, { 15, 0 })); - EXPECT_EQ(7, GetDirection({ 0, 0 }, { 15, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 8 }, { 15, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 8 }, { 8, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 15 }, { 8, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 15 }, { 7, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 11 }, { 7, 0 })); - EXPECT_EQ(6, GetDirection({ 0, 8 }, { 11, 0 })); - - EXPECT_EQ(0, GetDirection({ 1, 1 }, { 2, 2 })); - EXPECT_EQ(1, GetDirection({ 1, 1 }, { 1, 2 })); - EXPECT_EQ(2, GetDirection({ 1, 1 }, { 0, 2 })); - EXPECT_EQ(3, GetDirection({ 1, 1 }, { 0, 1 })); - EXPECT_EQ(4, GetDirection({ 1, 1 }, { 0, 0 })); - EXPECT_EQ(5, GetDirection({ 1, 1 }, { 1, 0 })); - EXPECT_EQ(6, GetDirection({ 1, 1 }, { 2, 0 })); - EXPECT_EQ(7, GetDirection({ 1, 1 }, { 2, 1 })); -} - -TEST(Missiles, GetDirection16) -{ - EXPECT_EQ(0, GetDirection16(0, 0, 15, 15)); - EXPECT_EQ(2, GetDirection16(0, 0, 0, 15)); - EXPECT_EQ(1, GetDirection16(0, 0, 8, 15)); - EXPECT_EQ(0, GetDirection16(0, 0, 8, 8)); - EXPECT_EQ(15, GetDirection16(0, 0, 15, 8)); - EXPECT_EQ(15, GetDirection16(0, 0, 15, 7)); - EXPECT_EQ(15, GetDirection16(0, 0, 11, 7)); - EXPECT_EQ(0, GetDirection16(0, 0, 8, 11)); - EXPECT_EQ(8, GetDirection16(15, 15, 0, 0)); - EXPECT_EQ(10, GetDirection16(0, 15, 0, 0)); - EXPECT_EQ(9, GetDirection16(8, 15, 0, 0)); - EXPECT_EQ(8, GetDirection16(8, 8, 0, 0)); - EXPECT_EQ(7, GetDirection16(15, 8, 0, 0)); - EXPECT_EQ(7, GetDirection16(15, 7, 0, 0)); - EXPECT_EQ(7, GetDirection16(11, 7, 0, 0)); - EXPECT_EQ(8, GetDirection16(8, 11, 0, 0)); - EXPECT_EQ(12, GetDirection16(0, 15, 15, 0)); - EXPECT_EQ(14, GetDirection16(0, 0, 15, 0)); - EXPECT_EQ(13, GetDirection16(0, 8, 15, 0)); - EXPECT_EQ(12, GetDirection16(0, 8, 8, 0)); - EXPECT_EQ(11, GetDirection16(0, 15, 8, 0)); - EXPECT_EQ(11, GetDirection16(0, 15, 7, 0)); - EXPECT_EQ(11, GetDirection16(0, 11, 7, 0)); - EXPECT_EQ(12, GetDirection16(0, 8, 11, 0)); - - EXPECT_EQ(0, GetDirection16(2, 2, 3, 3)); - EXPECT_EQ(1, GetDirection16(2, 2, 3, 4)); - EXPECT_EQ(2, GetDirection16(2, 2, 2, 4)); - EXPECT_EQ(3, GetDirection16(2, 2, 1, 4)); - EXPECT_EQ(4, GetDirection16(2, 2, 1, 3)); - EXPECT_EQ(5, GetDirection16(2, 2, 0, 3)); - EXPECT_EQ(6, GetDirection16(2, 2, 0, 2)); - EXPECT_EQ(7, GetDirection16(2, 2, 0, 1)); - EXPECT_EQ(8, GetDirection16(2, 2, 1, 1)); - EXPECT_EQ(9, GetDirection16(2, 2, 1, 0)); - EXPECT_EQ(10, GetDirection16(2, 2, 2, 0)); - EXPECT_EQ(11, GetDirection16(2, 2, 3, 0)); - EXPECT_EQ(12, GetDirection16(2, 2, 3, 1)); - EXPECT_EQ(13, GetDirection16(2, 2, 4, 1)); - EXPECT_EQ(14, GetDirection16(2, 2, 4, 2)); - EXPECT_EQ(15, GetDirection16(2, 2, 4, 3)); -} +#include + +#include "missiles.h" + +using namespace devilution; + +TEST(Missiles, GetDirection8) +{ + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 15 })); + EXPECT_EQ(1, GetDirection({ 0, 0 }, { 0, 15 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 15 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 8 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 8 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 15, 7 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 11, 7 })); + EXPECT_EQ(0, GetDirection({ 0, 0 }, { 8, 11 })); + EXPECT_EQ(4, GetDirection({ 15, 15 }, { 0, 0 })); + EXPECT_EQ(5, GetDirection({ 0, 15 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 8, 15 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 8, 8 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 15, 8 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 15, 7 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 11, 7 }, { 0, 0 })); + EXPECT_EQ(4, GetDirection({ 8, 11 }, { 0, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 15 }, { 15, 0 })); + EXPECT_EQ(7, GetDirection({ 0, 0 }, { 15, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 8 }, { 15, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 8 }, { 8, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 15 }, { 8, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 15 }, { 7, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 11 }, { 7, 0 })); + EXPECT_EQ(6, GetDirection({ 0, 8 }, { 11, 0 })); + + EXPECT_EQ(0, GetDirection({ 1, 1 }, { 2, 2 })); + EXPECT_EQ(1, GetDirection({ 1, 1 }, { 1, 2 })); + EXPECT_EQ(2, GetDirection({ 1, 1 }, { 0, 2 })); + EXPECT_EQ(3, GetDirection({ 1, 1 }, { 0, 1 })); + EXPECT_EQ(4, GetDirection({ 1, 1 }, { 0, 0 })); + EXPECT_EQ(5, GetDirection({ 1, 1 }, { 1, 0 })); + EXPECT_EQ(6, GetDirection({ 1, 1 }, { 2, 0 })); + EXPECT_EQ(7, GetDirection({ 1, 1 }, { 2, 1 })); +} + +TEST(Missiles, GetDirection16) +{ + EXPECT_EQ(0, GetDirection16(0, 0, 15, 15)); + EXPECT_EQ(2, GetDirection16(0, 0, 0, 15)); + EXPECT_EQ(1, GetDirection16(0, 0, 8, 15)); + EXPECT_EQ(0, GetDirection16(0, 0, 8, 8)); + EXPECT_EQ(15, GetDirection16(0, 0, 15, 8)); + EXPECT_EQ(15, GetDirection16(0, 0, 15, 7)); + EXPECT_EQ(15, GetDirection16(0, 0, 11, 7)); + EXPECT_EQ(0, GetDirection16(0, 0, 8, 11)); + EXPECT_EQ(8, GetDirection16(15, 15, 0, 0)); + EXPECT_EQ(10, GetDirection16(0, 15, 0, 0)); + EXPECT_EQ(9, GetDirection16(8, 15, 0, 0)); + EXPECT_EQ(8, GetDirection16(8, 8, 0, 0)); + EXPECT_EQ(7, GetDirection16(15, 8, 0, 0)); + EXPECT_EQ(7, GetDirection16(15, 7, 0, 0)); + EXPECT_EQ(7, GetDirection16(11, 7, 0, 0)); + EXPECT_EQ(8, GetDirection16(8, 11, 0, 0)); + EXPECT_EQ(12, GetDirection16(0, 15, 15, 0)); + EXPECT_EQ(14, GetDirection16(0, 0, 15, 0)); + EXPECT_EQ(13, GetDirection16(0, 8, 15, 0)); + EXPECT_EQ(12, GetDirection16(0, 8, 8, 0)); + EXPECT_EQ(11, GetDirection16(0, 15, 8, 0)); + EXPECT_EQ(11, GetDirection16(0, 15, 7, 0)); + EXPECT_EQ(11, GetDirection16(0, 11, 7, 0)); + EXPECT_EQ(12, GetDirection16(0, 8, 11, 0)); + + EXPECT_EQ(0, GetDirection16(2, 2, 3, 3)); + EXPECT_EQ(1, GetDirection16(2, 2, 3, 4)); + EXPECT_EQ(2, GetDirection16(2, 2, 2, 4)); + EXPECT_EQ(3, GetDirection16(2, 2, 1, 4)); + EXPECT_EQ(4, GetDirection16(2, 2, 1, 3)); + EXPECT_EQ(5, GetDirection16(2, 2, 0, 3)); + EXPECT_EQ(6, GetDirection16(2, 2, 0, 2)); + EXPECT_EQ(7, GetDirection16(2, 2, 0, 1)); + EXPECT_EQ(8, GetDirection16(2, 2, 1, 1)); + EXPECT_EQ(9, GetDirection16(2, 2, 1, 0)); + EXPECT_EQ(10, GetDirection16(2, 2, 2, 0)); + EXPECT_EQ(11, GetDirection16(2, 2, 3, 0)); + EXPECT_EQ(12, GetDirection16(2, 2, 3, 1)); + EXPECT_EQ(13, GetDirection16(2, 2, 4, 1)); + EXPECT_EQ(14, GetDirection16(2, 2, 4, 2)); + EXPECT_EQ(15, GetDirection16(2, 2, 4, 3)); +} diff --git a/test/pack_test.cpp b/test/pack_test.cpp index f56ec9a1a..d93e702d3 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -1,606 +1,606 @@ -#include -#include - -#include "pack.h" - -using namespace devilution; - -static void ComparePackedItems(const PkItemStruct *item1, const PkItemStruct *item2) -{ - // `PkItemStruct` is packed, so we copy the unaligned values out before comparing them. - // This avoids the following UBSAN error such as this one: - // runtime error: load of misaligned address for type 'const unsigned int', which requires 4 byte alignment - { - const auto item1_iSeed = item1->iSeed; - const auto item2_iSeed = item2->iSeed; - EXPECT_EQ(item1_iSeed, item2_iSeed); - } - { - const auto item1_iCreateInfo = item1->iCreateInfo; - const auto item2_iCreateInfo = item2->iCreateInfo; - EXPECT_EQ(item1_iCreateInfo, item2_iCreateInfo); - } - { - const auto item1_idx = item1->idx; - const auto item2_idx = item2->idx; - EXPECT_EQ(item1_idx, item2_idx); - } - EXPECT_EQ(item1->bId, item2->bId); - EXPECT_EQ(item1->bDur, item2->bDur); - EXPECT_EQ(item1->bMDur, item2->bMDur); - EXPECT_EQ(item1->bCh, item2->bCh); - EXPECT_EQ(item1->bMCh, item2->bMCh); - { - const auto item1_wValue = item1->wValue; - const auto item2_wValue = item2->wValue; - EXPECT_EQ(item1_wValue, item2_wValue); - } - { - const auto item1_dwBuff = item1->dwBuff; - const auto item2_dwBuff = item2->dwBuff; - EXPECT_EQ(item1_dwBuff, item2_dwBuff); - } -} -typedef struct TestItemStruct { - char _iIName[64]; - int _itype; - int _iClass; - int _iCurs; - int _iIvalue; - int _iMinDam; - int _iMaxDam; - int _iAC; - int _iFlags; - int _iMiscId; - int _iSpell; - int _iCharges; - int _iMaxCharges; - int _iDurability; - int _iMaxDur; - int _iPLDam; - int _iPLToHit; - int _iPLAC; - int _iPLStr; - int _iPLMag; - int _iPLDex; - int _iPLVit; - int _iPLFR; - int _iPLLR; - int _iPLMR; - int _iPLMana; - int _iPLHP; - int _iPLDamMod; - int _iPLGetHit; - int _iPLLight; - int8_t _iSplLvlAdd; - int _iUid; - int _iFMinDam; - int _iFMaxDam; - int _iLMinDam; - int _iLMaxDam; - int8_t _iPrePower; - int8_t _iSufPower; - int8_t _iMinStr; - uint8_t _iMinMag; - int8_t _iMinDex; - int IDidx; -} TestItemStruct; - -static void CompareItems(const ItemStruct *item1, const TestItemStruct *item2) -{ - ASSERT_STREQ(item1->_iIName, item2->_iIName); - EXPECT_EQ(item1->_itype, item2->_itype); - EXPECT_EQ(item1->_iClass, item2->_iClass); - EXPECT_EQ(item1->_iCurs, item2->_iCurs); - EXPECT_EQ(item1->_iIvalue, item2->_iIvalue); - EXPECT_EQ(item1->_iMinDam, item2->_iMinDam); - EXPECT_EQ(item1->_iMaxDam, item2->_iMaxDam); - EXPECT_EQ(item1->_iAC, item2->_iAC); - EXPECT_EQ(item1->_iFlags, item2->_iFlags); - EXPECT_EQ(item1->_iMiscId, item2->_iMiscId); - EXPECT_EQ(item1->_iSpell, item2->_iSpell); - EXPECT_EQ(item1->_iCharges, item2->_iCharges); - EXPECT_EQ(item1->_iMaxCharges, item2->_iMaxCharges); - EXPECT_EQ(item1->_iDurability, item2->_iDurability); - EXPECT_EQ(item1->_iMaxDur, item2->_iMaxDur); - EXPECT_EQ(item1->_iPLDam, item2->_iPLDam); - EXPECT_EQ(item1->_iPLToHit, item2->_iPLToHit); - EXPECT_EQ(item1->_iPLAC, item2->_iPLAC); - EXPECT_EQ(item1->_iPLStr, item2->_iPLStr); - EXPECT_EQ(item1->_iPLMag, item2->_iPLMag); - EXPECT_EQ(item1->_iPLDex, item2->_iPLDex); - EXPECT_EQ(item1->_iPLVit, item2->_iPLVit); - EXPECT_EQ(item1->_iPLFR, item2->_iPLFR); - EXPECT_EQ(item1->_iPLLR, item2->_iPLLR); - EXPECT_EQ(item1->_iPLMR, item2->_iPLMR); - EXPECT_EQ(item1->_iPLMana, item2->_iPLMana); - EXPECT_EQ(item1->_iPLHP, item2->_iPLHP); - EXPECT_EQ(item1->_iPLDamMod, item2->_iPLDamMod); - EXPECT_EQ(item1->_iPLGetHit, item2->_iPLGetHit); - EXPECT_EQ(item1->_iPLLight, item2->_iPLLight); - EXPECT_EQ(item1->_iSplLvlAdd, item2->_iSplLvlAdd); - EXPECT_EQ(item1->_iUid, item2->_iUid); - EXPECT_EQ(item1->_iFMinDam, item2->_iFMinDam); - EXPECT_EQ(item1->_iFMaxDam, item2->_iFMaxDam); - EXPECT_EQ(item1->_iLMinDam, item2->_iLMinDam); - EXPECT_EQ(item1->_iLMaxDam, item2->_iLMaxDam); - EXPECT_EQ(item1->_iPrePower, item2->_iPrePower); - EXPECT_EQ(item1->_iSufPower, item2->_iSufPower); - EXPECT_EQ(item1->_iMinStr, item2->_iMinStr); - EXPECT_EQ(item1->_iMinMag, item2->_iMinMag); - EXPECT_EQ(item1->_iMinDex, item2->_iMinDex); - EXPECT_EQ(item1->IDidx, item2->IDidx); -} - -const PkItemStruct PackedDiabloItems[] = { - // clang-format off - // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff - { 2082213289, 0x119, 53, 3, 60, 60, 0, 0, 0, 0 }, // Amber Helm of harmony - { 338833725, 0x118, 154, 3, 0, 0, 0, 0, 0, 0 }, // Cobalt Amulet of giants - { 545145866, 0x11A, 120, 3, 38, 40, 0, 0, 0, 0 }, // Brutal Sword of gore - { 1504248345, 0x35A, 70, 5, 255, 255, 0, 0, 0, 0 }, // Demonspike Coat - { 1884424756, 0x146, 151, 3, 0, 0, 0, 0, 0, 0 }, // Steel Ring of the jaguar - { 1712759905, 0xDC, 151, 3, 0, 0, 0, 0, 0, 0 }, // Ring of the heavens - { 981777658, 0x11E, 153, 2, 0, 0, 0, 0, 0, 0 }, // Ring of sorcery - { 844854815, 0x11A, 75, 3, 255, 255, 0, 0, 0, 0 }, // Shield of the ages - { 1151513535, 0x11E, 73, 2, 12, 32, 0, 0, 0, 0 }, // Sapphire Shield - { 640243885, 6, 27, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Town Portal - { 741806938, 9, 25, 0, 0, 0, 0, 0, 0, 0 }, // IDI_MANA - { 1456608333, 257, 79, 0, 0, 0, 0, 0, 0, 0 }, // Mana - { 554676945, 16, 30, 0, 0, 0, 0, 0, 0, 0 }, // Full mana - { 355389938, 0, 24, 0, 0, 0, 0, 0, 0, 0 }, // Healing - { 868435486, 16, 29, 0, 0, 0, 0, 0, 0, 0 }, // Full healing - { 1372832903, 0, 4, 0, 30, 30, 0, 0, 0, 0 }, // IDI_ROGUE - { 896239556, 2068, 53, 3, 56, 60, 0, 0, 0, 0 }, // Jade Helm of the wolf - { 1286225254, 269, 151, 3, 0, 0, 0, 0, 0, 0 }, // Steel Ring of accuracy - { 548642293, 266, 21, 0, 0, 0, 0, 0, 0, 0 }, // Blood Stone - { 307669016, 270, 151, 3, 0, 0, 0, 0, 0, 0 }, // Ring of power - { 204766888, 332, 154, 3, 0, 0, 0, 0, 0, 0 }, // Gold Amulet of accuracy - { 1642744332, 273, 122, 3, 25, 60, 0, 0, 0, 0 }, // Sword of the bat - { 1031508271, 1036, 72, 0, 14, 24, 0, 0, 0, 0 }, // Small Shield - { 1384620228, 338, 65, 3, 44, 80, 0, 0, 0, 0 }, // Plate of giant - { 681296530, 266, 87, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Healing - { 109984285, 16390, 81, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Rejuvenation - made by Pepin - { 1857753366, 260, 81, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Rejuvenation - { 965103261, 273, 82, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Full Rejuvenation - { 430621075, 217, 141, 3, 45, 45, 0, 0, 0, 0 }, // Savage Bow of perfection - { 1272669062, 258, 115, 0, 10, 20, 0, 0, 0, 0 }, // Falchion - { 1133884051, 278, 120, 2, 18, 40, 0, 0, 0, 0 }, // Sword of vim - { 1743897351, 259, 146, 2, 10, 25, 60, 60, 0, 0 }, // Frog's Staff of Holy Bolt - { 429107209, 0, 5, 0, 25, 25, 9, 40, 0, 0 }, // IDI_SORCERER - { 466015738, 257, 146, 0, 18, 25, 50, 50, 0, 0 }, // Staff of Charged Bolt - { 686949358, 193, 48, 3, 12, 15, 0, 0, 0, 0 }, // Cap of the mind armor - { 888855755, 195, 58, 3, 30, 30, 0, 0, 0, 0 }, // Armor of protection - { 2, 776, 8, 5, 0, 0, 0, 0, 0, 0 }, // Empyrean Band, - { 3, 776, 10, 5, 0, 0, 0, 0, 0, 0 }, // Optic Amulet - { 4, 772, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth - { 5, 776, 13, 5, 15, 15, 0, 0, 0, 0 }, // Harlequin Crest - { 6, 527, 14, 5, 60, 60, 0, 0, 0, 0 }, // Veil of Steel - { 7, 781, 28, 5, 39, 40, 0, 0, 0, 0 }, // Arkaine's Valor - { 8, 787, 31, 5, 42, 44, 0, 0, 0, 0 }, // Griswold's Edge - { 557339094, 8208, 150, 3, 75, 75, 0, 0, 0, 0 }, // Staff of haste - { 1684844665, 8208, 150, 3, 75, 75, 56, 56, 0, 0 }, // White Staff of Lightning - { 1297052552, 2074, 137, 3, 50, 50, 0, 0, 0, 0 }, // Lightning Maul - { 981895960, 2073, 130, 3, 75, 75, 0, 0, 0, 0 }, // Ivory Axe of blood - { 935416728, 2070, 52, 3, 18, 40, 0, 0, 0, 0 }, // Jade Crown of vim - { 1140525626, 257, 138, 3, 16, 30, 0, 0, 0, 0 }, // Bow of atrophy - { 1187758333, 258, 113, 3, 11, 16, 0, 0, 0, 0 }, // Brass Dagger of weakness - { 1283803700, 267, 138, 3, 16, 30, 0, 0, 0, 0 }, // Clumsy Bow - { 1317748726, 261, 114, 3, 17, 24, 0, 0, 0, 0 }, // Tin Sword of the fool - { 1331764286, 261, 135, 3, 6, 20, 0, 0, 0, 0 }, // Club of paralysis - { 1375639122, 269, 146, 3, 18, 25, 46, 46, 0, 0 }, // Dull Staff of Lightning - { 145523894, 277, 115, 3, 6, 20, 0, 0, 0, 0 }, // Sword of speed - { 1527777846, 259, 115, 3, 14, 20, 0, 0, 0, 0 }, // Bent Falchion - { 1655088363, 288, 146, 3, 13, 25, 98, 98, 0, 0 }, // Plentiful Staff of Firebolt - { 1679472538, 263, 113, 3, 6, 16, 0, 0, 0, 0 }, // Dagger of illness - { 1812092773, 264, 54, 3, 4, 12, 0, 0, 0, 0 }, // Cape of corruption - { 1965885799, 278, 119, 3, 33, 45, 0, 0, 0, 0 }, // Sabre of trouble - { 1970135469, 258, 48, 3, 5, 15, 0, 0, 0, 0 }, // Cap of tears - { 1979635474, 261, 135, 3, 14, 20, 0, 0, 0, 0 }, // Tin Club - { 2008721689, 258, 54, 3, 9, 12, 0, 0, 0, 0 }, // Rusted Cape of dyslexia - { 2082373839, 262, 48, 3, 10, 15, 0, 0, 0, 0 }, // Cap of pain - { 278391972, 263, 119, 3, 1, 1, 0, 0, 0, 0 }, // Clumsy Sabre of fragility - { 283130709, 260, 48, 3, 5, 15, 0, 0, 0, 0 }, // Vulnerable Cap of health - { 308974695, 264, 113, 3, 5, 16, 0, 0, 0, 0 }, // Useless Dagger - { 588501657, 300, 146, 3, 17, 25, 36, 36, 0, 0 }, // Bountiful Staff of Fire Wall - { 640482348, 259, 131, 3, 22, 32, 0, 0, 0, 0 }, // Mace of frailty - { 715324531, 258, 138, 3, 20, 30, 0, 0, 0, 0 }, // Weak Bow - { 794222370, 259, 146, 3, 12, 25, 0, 0, 0, 0 }, // Staff of readiness - // clang-format on -}; - -const TestItemStruct DiabloItems[] = { - // clang-format off - //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); - { "Amber Helm of harmony", 7, 2, 98, 21100, 0, 0, 11, 8388608, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 59, 50, 0, 0, 53 }, - { "Cobalt Amulet of giants", 13, 3, 45, 26840, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 0, 0, 159 }, - { "Brutal Sword of gore", 1, 1, 60, 13119, 2, 10, 0, 0, 0, 0, 0, 0, 38, 40, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 2, 61, 30, 0, 30, 125 }, - { "Demonspike Coat", 9, 2, 151, 251175, 0, 0, 100, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 10, 0, 0, 0, 50, 0, 0, 0, 0, 0, -6, 0, 0, 78, 0, 0, 0, 0, -1, -1, 90, 0, 0, 70 }, - { "Steel Ring of the jaguar", 12, 3, 12, 10600, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 156 }, - { "Ring of the heavens", 12, 3, 12, 37552, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 27, 0, 0, 0, 156 }, - { "Ring of sorcery", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 158 }, - { "Shield of the ages", 5, 2, 132, 4850, 0, 0, 18, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 37, 60, 0, 0, 75 }, - { "Sapphire Shield", 5, 2, 147, 21000, 0, 0, 7, 0, 0, 0, 0, 0, 12, 32, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, -1, 40, 0, 0, 73 }, - { "Scroll of Town Portal", 0, 3, 1, 200, 0, 0, 0, 0, 21, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 27 }, - { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 25 }, - { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 79 }, - { "Potion of Full Mana", 0, 3, 0, 150, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 30 }, - { "Potion of Healing", 0, 3, 32, 50, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 24 }, - { "Potion of Full Healing", 0, 3, 35, 150, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 29 }, - { "Short Bow", 3, 1, 118, 100, 1, 4, 0, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 4 }, - { "Jade Helm of the wolf", 7, 2, 98, 22310, 0, 0, 14, 0, 0, 0, 0, 0, 56, 60, 0, 0, 0, 0, 0, 0, 0, 27, 27, 27, 0, 2112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 31, 50, 0, 0, 53 }, - { "Steel Ring of accuracy", 12, 3, 12, 13400, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 156 }, - { "Blood Stone", 0, 5, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 21 }, - { "Ring of power", 12, 3, 12, 6400, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 0, 0, 0, 156 }, - { "Gold Amulet of accuracy", 13, 3, 45, 20896, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 159 }, - { "Sword of the bat", 1, 1, 57, 10500, 6, 15, 0, 8192, 0, 0, 0, 0, 25, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 55, 50, 0, 0, 127 }, - { "Small Shield", 5, 2, 105, 90, 0, 0, 3, 0, 0, 0, 0, 0, 14, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 25, 0, 0, 72 }, - { "Plate of giants", 9, 2, 153, 23250, 0, 0, 20, 0, 0, 0, 0, 0, 44, 80, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 40, 0, 0, 65 }, - { "Scroll of Healing", 0, 3, 1, 50, 0, 0, 0, 0, 21, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 91 }, - { "Potion of Rejuvenation", 0, 3, 37, 120, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 81 }, - { "Potion of Rejuvenation", 0, 3, 37, 120, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 81 }, - { "Potion of Full Rejuvenation", 0, 3, 33, 600, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 82 }, - { "Savage Bow of perfection", 3, 1, 133, 23438, 3, 6, 0, 0, 0, 0, 0, 0, 45, 45, 117, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 23, 25, 0, 40, 146 }, - { "Falchion", 1, 1, 62, 250, 4, 8, 0, 0, 0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 30, 0, 0, 120 }, - { "Sword of vim", 1, 1, 60, 4400, 2, 10, 0, 0, 0, 0, 0, 0, 18, 40, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 25, 30, 0, 30, 125 }, - { "Frog's Staff of Holy Bolt", 10, 1, 109, 1, 2, 4, 0, 0, 23, 31, 60, 60, 10, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -384, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, -1, 0, 20, 0, 151 }, - { "Short Staff of Charged Bolt", 10, 1, 109, 520, 2, 4, 0, 0, 23, 30, 9, 40, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 20, 0, 166 }, - { "Staff of Charged Bolt", 10, 1, 109, 1, 2, 4, 0, 0, 23, 30, 50, 50, 18, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 25, 0, 151 }, - { "Cap of the mind", 7, 2, 91, 1845, 0, 0, 2, 0, 0, 0, 0, 0, 12, 15, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 48 }, - { "Armor of protection", 6, 2, 129, 1200, 0, 0, 7, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, -1, 30, 0, 0, 0, 58 }, - { "Empyrean Band", 12, 3, 18, 8000, 0, 0, 0, 270532608, 27, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 8 }, - { "Optic Amulet", 13, 3, 44, 9750, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 20, 0, 0, 0, 0, -1, 2, 0, 3, 0, 0, 0, 0, -1, -1, 0, 0, 0, 10 }, - { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, - { "Harlequin Crest", 7, 2, 81, 4000, 0, 0, -3, 0, 27, 0, 0, 0, 15, 15, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 448, 448, 0, -1, 0, 0, 5, 0, 0, 0, 0, -1, -1, 0, 0, 0, 13 }, - { "Veil of Steel", 7, 2, 85, 63800, 0, 0, 18, 0, 27, 0, 0, 0, 60, 60, 0, 0, 60, 15, 0, 0, 15, 50, 50, 50, -1920, 0, 0, 0, -2, 0, 6, 0, 0, 0, 0, -1, -1, 0, 0, 0, 14 }, - { "Arkaine's Valor", 8, 2, 157, 42000, 0, 0, 25, 8388608, 27, 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, -3, 0, 0, 7, 0, 0, 0, 0, -1, -1, 0, 0, 0, 28 }, - { "Griswold's Edge", 1, 1, 61, 42000, 4, 12, 0, 264208, 27, 0, 0, 0, 42, 44, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1280, -1280, 0, 0, 0, 0, 8, 1, 10, 0, 0, -1, -1, 40, 0, 0, 31 }, - { "Staff of haste", 10, 1, 124, 40000, 8, 16, 0, 1048576, 23, 0, 0, 0, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 30, 0, 0, 155 }, - { "White Staff of Lightning", 10, 1, 124, 7160, 8, 16, 0, 0, 23, 3, 56, 56, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, -1, 30, 20, 0, 155 }, - { "Lightning Maul", 4, 1, 122, 11800, 6, 20, 0, 32, 0, 0, 0, 0, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 17, -1, 55, 0, 0, 142 }, - { "Ivory Axe of blood", 2, 1, 143, 31194, 12, 30, 0, 65536, 0, 0, 0, 0, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 56, 80, 0, 0, 135 }, - { "Jade Crown of vim", 7, 2, 95, 19200, 0, 0, 10, 0, 0, 0, 0, 0, 18, 40, 0, 0, 0, 0, 0, 0, 14, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 25, 0, 0, 0, 52 }, - { "Bow of atrophy", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 16, 30, 0, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 24, 0, 0, 0, 143 }, - { "Brass Dagger of weakness", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 11, 16, 0, -1, 0, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 20, 0, 0, 0, 118 }, - { "Clumsy Bow", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 16, 30, -67, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -1, 0, 0, 0, 143 }, - { "Tin Sword of the fool", 1, 1, 64, 1, 2, 6, 0, 0, 0, 0, 0, 0, 17, 24, 0, -7, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 22, 18, 0, 0, 119 }, - { "Club of paralysis", 4, 1, 66, 1, 1, 6, 0, 0, 0, 0, 0, 0, 6, 20, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 24, 0, 0, 0, 140 }, - { "Dull Staff of Lightning", 10, 1, 109, 1, 2, 4, 0, 0, 23, 3, 46, 46, 18, 25, -28, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -1, 0, 20, 0, 151 }, - { "Sword of speed", 1, 1, 62, 10000, 4, 8, 0, 524288, 0, 0, 0, 0, 6, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 30, 0, 0, 120 }, - { "Bent Falchion", 1, 1, 62, 1, 4, 8, 0, 0, 0, 0, 0, 0, 14, 20, -68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 30, 0, 0, 120 }, - { "Plentiful Staff of Firebolt", 10, 1, 109, 3040, 2, 4, 0, 0, 23, 1, 98, 98, 13, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, -1, 0, 15, 0, 151 }, - { "Dagger of illness", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 6, 16, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 26, 0, 0, 0, 118 }, - { "Cape of corruption", 6, 2, 150, 1, 0, 0, 3, 134217728, 0, 0, 0, 0, 4, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 46, 0, 0, 0, 54 }, - { "Sabre of trouble", 1, 1, 67, 1, 1, 8, 0, 0, 0, 0, 0, 0, 33, 45, 0, 0, 0, -6, -6, -6, -6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 28, 17, 0, 0, 124 }, - { "Cap of tears", 7, 2, 91, 1, 0, 0, 2, 0, 0, 0, 0, 0, 5, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, 29, 0, 0, 0, 48 }, - { "Tin Club", 4, 1, 66, 1, 1, 6, 0, 0, 0, 0, 0, 0, 14, 20, 0, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 140 }, - { "Rusted Cape of dyslexia", 6, 2, 150, 1, 0, 0, 2, 0, 0, 0, 0, 0, 9, 12, 0, 0, -34, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 22, 0, 0, 0, 54 }, - { "Cap of pain", 7, 2, 91, 1, 0, 0, 2, 0, 0, 0, 0, 0, 10, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -1, 29, 0, 0, 0, 48 }, - { "Clumsy Sabre of fragility", 1, 1, 67, 1, 1, 8, 0, 0, 0, 0, 0, 0, 1, 1, -75, -10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 36, 17, 0, 0, 124 }, - { "Vulnerable Cap of health", 7, 2, 91, 185, 0, 0, 1, 0, 0, 0, 0, 0, 5, 15, 0, 0, -63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 7, 30, 0, 0, 0, 48 }, - { "Useless Dagger", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 5, 16, -100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 0, 0, 0, 118 }, - { "Bountiful Staff of Fire Wall", 10, 1, 109, 5970, 2, 4, 0, 0, 23, 6, 36, 36, 17, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, -1, 0, 27, 0, 151 }, - { "Mace of frailty", 4, 1, 59, 1, 1, 8, 0, 0, 0, 0, 0, 0, 22, 32, 0, 0, 0, -7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 20, 16, 0, 0, 136 }, - { "Weak Bow", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 20, 30, -44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 0, 0, 0, 143 }, - { "Staff of readiness", 10, 1, 109, 2060, 2, 4, 0, 131072, 23, 0, 0, 0, 12, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 0, 0, 0, 151 }, - // clang-format on -}; - -TEST(pack, UnPackItem_diablo) -{ - ItemStruct id; - PkItemStruct is; - - gbIsHellfire = false; - gbIsMultiplayer = false; - - for (size_t i = 0; i < sizeof(PackedDiabloItems) / sizeof(*PackedDiabloItems); i++) { - UnPackItem(&PackedDiabloItems[i], &id, false); - CompareItems(&id, &DiabloItems[i]); - - PackItem(&is, &id); - ComparePackedItems(&is, &PackedDiabloItems[i]); - } -} - -TEST(pack, UnPackItem_diablo_unique_bug) -{ - PkItemStruct pkItemBug = { 6, 911, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - with morph bug - PkItemStruct pkItem = { 6, 655, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - fixed - - gbIsHellfire = false; - gbIsMultiplayer = false; - - ItemStruct id; - UnPackItem(&pkItemBug, &id, false); - ASSERT_STREQ(id._iIName, "Veil of Steel"); - ASSERT_EQ(id._itype, ITYPE_HELM); - ASSERT_EQ(id._iClass, ICLASS_ARMOR); - ASSERT_EQ(id._iCurs, 85); - ASSERT_EQ(id._iIvalue, 63800); - ASSERT_EQ(id._iAC, 18); - ASSERT_EQ(id._iMiscId, IMISC_UNIQUE); - ASSERT_EQ(id._iPLAC, 60); - ASSERT_EQ(id._iPLStr, 15); - ASSERT_EQ(id._iPLVit, 15); - ASSERT_EQ(id._iPLFR, 50); - ASSERT_EQ(id._iPLLR, 50); - ASSERT_EQ(id._iPLMR, 50); - ASSERT_EQ(id._iPLMana, -1920); - ASSERT_EQ(id._iPLLight, -2); - ASSERT_EQ(id._iUid, 6); - ASSERT_EQ(id.IDidx, IDI_STEELVEIL); - - PkItemStruct is; - PackItem(&is, &id); - ComparePackedItems(&is, &pkItem); -} - -const PkItemStruct PackedDiabloMPItems[] = { - // clang-format off - // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff - { 309674341, 193, 109, 0, 0, 0, 0, 0, 0, 0 }, // Book of Firebolt - { 1291471654, 6, 34, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Resurrect - // clang-format on -}; - -const TestItemStruct DiabloMPItems[] = { - // clang-format off - //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); - { "Book of Firebolt", 0, 3, 87, 1000, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 15, 0, 114 }, - { "Scroll of Resurrect", 0, 3, 1, 250, 0, 0, 0, 0, 22, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 34 }, - // clang-format on -}; - -TEST(pack, UnPackItem_diablo_multiplayer) -{ - ItemStruct id; - PkItemStruct is; - - gbIsHellfire = false; - gbIsMultiplayer = true; - - for (size_t i = 0; i < sizeof(PackedDiabloMPItems) / sizeof(*PackedDiabloMPItems); i++) { - UnPackItem(&PackedDiabloMPItems[i], &id, false); - CompareItems(&id, &DiabloMPItems[i]); - - PackItem(&is, &id); - ComparePackedItems(&is, &PackedDiabloMPItems[i]); - } -} - -const PkItemStruct PackedHellfireItems[] = { - // clang-format off - // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff - { 1717442367, 266, 156, 3, 0, 0, 0, 0, 0, 0 }, // Ring of stability - { 1268518156, 338, 157, 3, 0, 0, 0, 0, 0, 0 }, // Ring of precision - { 132733863, 283, 157, 3, 0, 0, 0, 0, 0, 0 }, // Obsidian Ring of wizardry - { 511953594, 283, 158, 3, 0, 0, 0, 0, 0, 0 }, // Ring of precision - { 1183326923, 346, 160, 3, 0, 0, 0, 0, 0, 0 }, // Amulet of titans - { 1863009736, 280, 160, 3, 0, 0, 0, 0, 0, 0 }, // Gold Amulet - { 1872860650, 734, 135, 5, 75, 75, 0, 0, 0, 0 }, // Messerschmidt's Reaver - { 1584694222, 281, 142, 3, 127, 128, 0, 0, 0, 0 }, // Vicious Maul of structure - { 669112929, 280, 119, 0, 15, 24, 0, 0, 0, 0 }, // Short Sword - { 303108965, 280, 148, 3, 18, 50, 0, 0, 0, 0 }, // Bow of shock - { 575830996, 257, 143, 3, 30, 30, 0, 0, 0, 0 }, // Bow of magic - { 1488880650, 194, 152, 3, 35, 35, 22, 33, 0, 0 }, // Red Staff of Healing - { 1864450901, 263, 71, 0, 6, 16, 0, 0, 0, 0 }, // Buckler - { 28387651, 263, 49, 0, 15, 20, 0, 0, 0, 0 }, // Skull Cap - { 1298183212, 257, 55, 0, 6, 6, 0, 0, 0, 0 }, // Rags - { 1113945523, 260, 58, 0, 30, 30, 0, 0, 0, 0 }, // Quilted Armor - { 765757608, 260, 58, 2, 12, 30, 0, 0, 0, 0 }, // Armor of light - { 188812770, 346, 67, 3, 75, 75, 0, 0, 0, 0 }, // Saintly Plate of the stars - { 283577043, 2070, 67, 3, 63, 75, 0, 0, 0, 0 }, // Plate of the stars - { 123272767, 16, 24, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Healing - { 433688373, 16, 29, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Full Healing - { 1213385484, 32770, 25, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Mana - { 1405075219, 280, 110, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Golem - { 1478792102, 259, 92, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Search - { 1569255955, 262, 94, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Identify - { 1291205782, 261, 98, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Town Portal - { 811925807, 260, 91, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Healing - { 1275007287, 257, 161, 0, 0, 0, 0, 0, 0, 0 }, // Rune of Fire - { 561216242, 278, 0, 0, 0, 0, 0, 0, 1663, 0 }, // Gold - { 1, 515, 7, 5, 45, 50, 0, 0, 0, 0 }, // The Undead Crown - { 2, 774, 8, 5, 0, 0, 0, 0, 0, 0 }, // Empyrean Band - { 4, 769, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth - { 8, 512, 31, 5, 50, 50, 0, 0, 0, 0 }, // Griswold's Edge - { 9, 850, 32, 5, 255, 255, 0, 0, 0, 0 }, // Bovine Plate - { 410929431, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Healing - { 876535546, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Charged Bolt - { 1009350361, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Firebolt - { 41417651, 258, 83, 0, 0, 0, 0, 0, 0, 0 }, // Blacksmith Oil - { 132200437, 258, 84, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Accuracy - { 385651490, 257, 85, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Sharpness - { 1154514759, 290, 86, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Permanence - { 2020998927, 2066, 131, 3, 23, 32, 0, 0, 0, 0 }, // Doppelganger's Axe - { 581541889, 2067, 141, 3, 36, 36, 0, 0, 0, 0 }, // Flail of vampires - { 1069448901, 844, 157, 5, 0, 0, 0, 0, 0, 0 }, // Gladiator's Ring - { 1670063399, 2068, 155, 3, 75, 75, 0, 0, 0, 0 }, // Warrior's Staff of the moon - { 342570085, 4114, 74, 3, 255, 255, 0, 0, 0, 0 }, // Shield of the ages - { 1514523617, 2066, 139, 3, 20, 20, 0, 0, 0, 0 }, // Heavy Club of puncturing - { 701987341, 8208, 114, 1, 0, 0, 0, 0, 0, 0 }, // Book of Lightning - { 568338383, 196, 124, 3, 23, 45, 0, 0, 0, 0 }, // Jester's Sabre - { 1308277119, 2056, 72, 3, 24, 24, 0, 0, 0, 0 }, // Shield of blocking - { 0, 512, 6, 5, 10, 10, 0, 0, 0, 0 }, // The Butcher's Cleaver - { 1621745295, 2057, 121, 3, 28, 28, 0, 0, 0, 0 }, // Scimitar of peril - { 492619876, 2054, 132, 3, 12, 12, 0, 0, 0, 0 }, // Crystalline Axe - { 1859493982, 2053, 56, 3, 18, 18, 0, 0, 0, 0 }, // Red Cloak - { 1593032051, 2050, 136, 3, 32, 32, 0, 0, 0, 0 }, // Mace of decay - { 4, 512, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth - { 1500728519, 260, 61, 3, 18, 45, 0, 0, 0, 0 }, // Red Armor of paralysis - { 954183925, 261, 144, 3, 26, 40, 0, 0, 0, 0 }, // Bent Bow - { 438248055, 711, 136, 5, 32, 32, 0, 0, 0, 0 }, // Civerb's Cudgel - { 1133027011, 328, 139, 3, 8, 20, 0, 0, 0, 0 }, // Deadly Club - { 224835143, 732, 144, 5, 255, 255, 0, 0, 0, 0 }, // Gnat Sting - { 1498080548, 796, 138, 5, 255, 255, 0, 0, 0, 0 }, // Thunderclap - // clang-format on -}; - -const TestItemStruct HellfireItems[] = { - // clang-format off - //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); - { "Ring of stability", 12, 3, 12, 8000, 0, 0, 0, 4194304, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 59, 0, 0, 0, 156 }, - { "Ring of precision", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 23, 0, 0, 0, 157 }, - { "Obsidian Ring of wizardry", 12, 3, 12, 56928, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 37, 37, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 21, 0, 0, 0, 157 }, - { "Ring of precision", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 23, 0, 0, 0, 158 }, - { "Amulet of titans", 13, 3, 45, 20896, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 0, 0, 0, 160 }, - { "Gold Amulet", 13, 3, 45, 13692, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 160 }, - { "Messerschmidt's Reaver", 2, 1, 163, 58000, 12, 30, 0, 16, 0, 0, 0, 0, 75, 75, 200, 0, 0, 5, 5, 5, 5, 0, 0, 0, 0, -3200, 15, 0, 0, 0, 44, 2, 12, 0, 0, -1, -1, 80, 0, 0, 135 }, - { "Vicious Maul of structure", 4, 1, 122, 10489, 6, 20, 0, 0, 0, 0, 0, 0, 127, 128, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 35, 55, 0, 0, 142 }, - { "Short Sword", 1, 1, 64, 120, 2, 6, 0, 0, 0, 0, 0, 0, 15, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 18, 0, 0, 119 }, - { "Bow of shock", 3, 1, 119, 8000, 1, 10, 0, 33554432, 0, 0, 0, 0, 18, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, -1, 43, 30, 0, 60, 148 }, - { "Bow of magic", 3, 1, 118, 400, 1, 4, 0, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 143 }, - { "Red Staff of Healing", 10, 1, 123, 1360, 4, 8, 0, 0, 23, 2, 22, 33, 35, 35, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, -1, 0, 17, 0, 152 }, - { "Buckler", 5, 2, 83, 30, 0, 0, 5, 0, 0, 0, 0, 0, 6, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 71 }, - { "Skull Cap", 7, 2, 90, 25, 0, 0, 3, 0, 0, 0, 0, 0, 15, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 49 }, - { "Rags", 6, 2, 128, 5, 0, 0, 4, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 55 }, - { "Quilted Armor", 6, 2, 129, 200, 0, 0, 7, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 58 }, - { "Armor of light", 6, 2, 129, 1150, 0, 0, 10, 0, 0, 0, 0, 0, 12, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -1, 38, 0, 0, 0, 58 }, - { "Saintly Plate of the stars", 9, 2, 103, 140729, 0, 0, 46, 0, 0, 0, 0, 0, 75, 75, 0, 0, 121, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 27, 60, 0, 0, 67 }, - { "Plate of the stars", 9, 2, 103, 77800, 0, 0, 49, 0, 0, 0, 0, 0, 63, 75, 0, 0, 0, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 27, 60, 0, 0, 67 }, - { "Potion of Healing", 0, 3, 32, 50, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 24 }, - { "Potion of Full Healing", 0, 3, 35, 150, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 29 }, - { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 25 }, - { "Scroll of Golem", 0, 3, 1, 1100, 0, 0, 0, 0, 22, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 51, 0, 110 }, - { "Scroll of Search", 0, 3, 1, 50, 0, 0, 0, 0, 21, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 92 }, - { "Scroll of Identify", 0, 3, 1, 100, 0, 0, 0, 0, 21, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 94 }, - { "Scroll of Town Portal", 0, 3, 1, 200, 0, 0, 0, 0, 21, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 98 }, - { "Scroll of Healing", 0, 3, 1, 50, 0, 0, 0, 0, 21, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 91 }, - { "Rune of Fire", 0, 3, 193, 100, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 161 }, - { "Gold", 11, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0 }, - { "The Undead Crown", 7, 2, 77, 16650, 0, 0, 8, 2, 27, 0, 0, 0, 45, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 7 }, - { "Empyrean Band", 12, 3, 18, 8000, 0, 0, 0, 270532608, 27, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 8 }, - { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, - { "Griswold's Edge", 1, 1, 61, 42000, 4, 12, 0, 264208, 27, 0, 0, 0, 50, 50, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1280, -1280, 0, 0, 0, 0, 8, 1, 10, 0, 0, -1, -1, 40, 0, 0, 31 }, - { "Bovine Plate", 9, 2, 226, 400, 0, 0, 150, 0, 27, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 30, 30, 30, -3200, 0, 0, 0, 5, -2, 9, 0, 0, 0, 0, -1, -1, 50, 0, 0, 32 }, - { "Book of Healing", 0, 3, 86, 1000, 0, 0, 0, 0, 24, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 17, 0, 114 }, - { "Book of Charged Bolt", 0, 3, 88, 1000, 0, 0, 0, 0, 24, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 25, 0, 114 }, - { "Book of Firebolt", 0, 3, 87, 1000, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 15, 0, 114 }, - { "Blacksmith Oil", 0, 3, 30, 100, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 83 }, - { "Oil of Accuracy", 0, 3, 30, 500, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 84 }, - { "Oil of Sharpness", 0, 3, 30, 500, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 85 }, - { "Oil of Permanence", 0, 3, 30, 15000, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 86 }, - { "Doppelganger's Axe", 2, 1, 144, 6640, 4, 12, 0, 0, 0, 0, 0, 0, 23, 32, 86, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, -1, 22, 0, 0, 131 }, - { "Flail of vampires", 4, 1, 131, 16500, 2, 12, 0, 16384, 0, 0, 0, 0, 36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 55, 30, 0, 0, 141 }, - { "Gladiator's Ring", 12, 3, 186, 10000, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, -1, -1, 0, 0, 0, 157 }, - { "Warrior's Staff of the moon", 10, 1, 124, 42332, 8, 16, 0, 0, 23, 0, 0, 0, 75, 75, 54, 15, 0, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 27, 30, 0, 0, 155 }, - { "Shield of the ages", 5, 2, 113, 2600, 0, 0, 10, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 37, 50, 0, 0, 74 }, - { "Heavy Club of puncturing", 4, 1, 70, 5239, 3, 6, 0, 0, 0, 0, 0, 0, 20, 20, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 57, 18, 0, 0, 139 }, - { "Book of Lightning", 0, 3, 88, 3000, 0, 0, 0, 0, 24, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 20, 0, 114 }, - { "Jester's Sabre", 1, 1, 67, 1710, 1, 8, 0, 0, 0, 0, 0, 0, 23, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, -1, 17, 0, 0, 124 }, - { "Shield of blocking", 5, 2, 105, 4360, 0, 0, 6, 16777216, 0, 0, 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 60, 25, 0, 0, 72 }, - { "The Butcher's Cleaver", 2, 1, 106, 3650, 4, 24, 0, 0, 27, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 6 }, - { "Scimitar of peril", 1, 1, 72, 700, 3, 7, 0, 0, 0, 0, 0, 0, 28, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 86, 23, 0, 23, 121 }, - { "Crystalline Axe", 2, 1, 142, 5250, 6, 16, 0, 0, 0, 0, 0, 0, 12, 12, 280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, -1, 30, 0, 0, 132 }, - { "Red Cloak", 6, 2, 149, 580, 0, 0, 3, 0, 0, 0, 0, 0, 18, 18, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, -1, 0, 0, 0, 56 }, - { "Mace of decay", 4, 1, 59, 600, 1, 8, 0, 0, 0, 0, 0, 0, 32, 32, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 85, 16, 0, 0, 136 }, - { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, - { "Red Armor of paralysis", 6, 2, 107, 800, 0, 0, 17, 0, 0, 0, 0, 0, 18, 45, 0, 0, 0, 0, 0, -8, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 24, 20, 0, 0, 61 }, - { "Bent Bow", 3, 1, 102, 1, 2, 5, 0, 0, 0, 0, 0, 0, 26, 40, -69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 20, 0, 35, 144 }, - { "Civerb's Cudgel", 4, 1, 59, 2000, 1, 8, 0, 1073741824, 0, 0, 0, 0, 32, 32, 0, 0, 0, 0, -2, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, -1, -1, 16, 0, 0, 136 }, - { "Deadly Club", 4, 1, 70, 1556, 3, 6, 0, 0, 0, 0, 0, 0, 8, 20, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, 18, 0, 0, 139 }, - { "Gnat Sting", 3, 1, 210, 30000, 1, 2, 0, 131584, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, -1, -1, 20, 0, 35, 144 }, - { "Thunderclap", 4, 1, 205, 30000, 5, 9, 0, 48, 0, 0, 0, 0, 255, 255, 0, 0, 0, 20, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 2, 0, 102, 3, 6, 2, 0, -1, -1, 40, 0, 0, 138 }, - // clang-format on -}; - -TEST(pack, UnPackItem_hellfire) -{ - ItemStruct id; - PkItemStruct is; - - gbIsHellfire = true; - gbIsMultiplayer = false; - - for (size_t i = 0; i < sizeof(PackedHellfireItems) / sizeof(*PackedHellfireItems); i++) { - UnPackItem(&PackedHellfireItems[i], &id, true); - CompareItems(&id, &HellfireItems[i]); - - PackItem(&is, &id); - is.dwBuff &= ~CF_HELLFIRE; - ComparePackedItems(&is, &PackedHellfireItems[i]); - } -} - -TEST(pack, UnPackItem_diablo_strip_hellfire_items) -{ - PkItemStruct is = { 1478792102, 259, 92, 0, 0, 0, 0, 0, 0, 0 }; // Scroll of Search - ItemStruct id; - - gbIsHellfire = false; - gbIsMultiplayer = false; - - UnPackItem(&is, &id, true); - - ASSERT_EQ(id._itype, ITYPE_NONE); -} - -TEST(pack, UnPackItem_empty) -{ - PkItemStruct is = { 0, 0, 0xFFFF, 0, 0, 0, 0, 0, 0, 0 }; - ItemStruct id; - - UnPackItem(&is, &id, false); - - ASSERT_EQ(id._itype, ITYPE_NONE); -} - -TEST(pack, PackItem_empty) -{ - PkItemStruct is; - ItemStruct id; - - id._itype = ITYPE_NONE; - - PackItem(&is, &id); - - ASSERT_EQ(is.idx, 0xFFFF); -} - -static void compareGold(const PkItemStruct *is, int iCurs) -{ - ItemStruct id; - UnPackItem(is, &id, false); - ASSERT_EQ(id._iCurs, iCurs); - ASSERT_EQ(id.IDidx, IDI_GOLD); - ASSERT_EQ(id._ivalue, is->wValue); - ASSERT_EQ(id._itype, ITYPE_GOLD); - ASSERT_EQ(id._iClass, ICLASS_GOLD); - - PkItemStruct is2; - PackItem(&is2, &id); - ComparePackedItems(is, &is2); -} - -TEST(pack, UnPackItem_gold_small) -{ - PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1000, 0 }; - compareGold(&is, ICURS_GOLD_SMALL); -} - -TEST(pack, UnPackItem_gold_medium) -{ - PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1001, 0 }; - compareGold(&is, ICURS_GOLD_MEDIUM); -} - -TEST(pack, UnPackItem_gold_large) -{ - PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 2500, 0 }; - compareGold(&is, ICURS_GOLD_LARGE); -} - -TEST(pack, UnPackItem_ear) -{ - PkItemStruct is = { 1633955154, 17509, 23, 111, 103, 117, 101, 68, 19843, 0 }; - ItemStruct id; - - UnPackItem(&is, &id, false); - ASSERT_STREQ(id._iName, "Ear of Dead-RogueDM"); - ASSERT_EQ(id._ivalue, 3); - - PkItemStruct is2; - PackItem(&is2, &id); - ComparePackedItems(&is, &is2); -} +#include +#include + +#include "pack.h" + +using namespace devilution; + +static void ComparePackedItems(const PkItemStruct *item1, const PkItemStruct *item2) +{ + // `PkItemStruct` is packed, so we copy the unaligned values out before comparing them. + // This avoids the following UBSAN error such as this one: + // runtime error: load of misaligned address for type 'const unsigned int', which requires 4 byte alignment + { + const auto item1_iSeed = item1->iSeed; + const auto item2_iSeed = item2->iSeed; + EXPECT_EQ(item1_iSeed, item2_iSeed); + } + { + const auto item1_iCreateInfo = item1->iCreateInfo; + const auto item2_iCreateInfo = item2->iCreateInfo; + EXPECT_EQ(item1_iCreateInfo, item2_iCreateInfo); + } + { + const auto item1_idx = item1->idx; + const auto item2_idx = item2->idx; + EXPECT_EQ(item1_idx, item2_idx); + } + EXPECT_EQ(item1->bId, item2->bId); + EXPECT_EQ(item1->bDur, item2->bDur); + EXPECT_EQ(item1->bMDur, item2->bMDur); + EXPECT_EQ(item1->bCh, item2->bCh); + EXPECT_EQ(item1->bMCh, item2->bMCh); + { + const auto item1_wValue = item1->wValue; + const auto item2_wValue = item2->wValue; + EXPECT_EQ(item1_wValue, item2_wValue); + } + { + const auto item1_dwBuff = item1->dwBuff; + const auto item2_dwBuff = item2->dwBuff; + EXPECT_EQ(item1_dwBuff, item2_dwBuff); + } +} +typedef struct TestItemStruct { + char _iIName[64]; + int _itype; + int _iClass; + int _iCurs; + int _iIvalue; + int _iMinDam; + int _iMaxDam; + int _iAC; + int _iFlags; + int _iMiscId; + int _iSpell; + int _iCharges; + int _iMaxCharges; + int _iDurability; + int _iMaxDur; + int _iPLDam; + int _iPLToHit; + int _iPLAC; + int _iPLStr; + int _iPLMag; + int _iPLDex; + int _iPLVit; + int _iPLFR; + int _iPLLR; + int _iPLMR; + int _iPLMana; + int _iPLHP; + int _iPLDamMod; + int _iPLGetHit; + int _iPLLight; + int8_t _iSplLvlAdd; + int _iUid; + int _iFMinDam; + int _iFMaxDam; + int _iLMinDam; + int _iLMaxDam; + int8_t _iPrePower; + int8_t _iSufPower; + int8_t _iMinStr; + uint8_t _iMinMag; + int8_t _iMinDex; + int IDidx; +} TestItemStruct; + +static void CompareItems(const ItemStruct *item1, const TestItemStruct *item2) +{ + ASSERT_STREQ(item1->_iIName, item2->_iIName); + EXPECT_EQ(item1->_itype, item2->_itype); + EXPECT_EQ(item1->_iClass, item2->_iClass); + EXPECT_EQ(item1->_iCurs, item2->_iCurs); + EXPECT_EQ(item1->_iIvalue, item2->_iIvalue); + EXPECT_EQ(item1->_iMinDam, item2->_iMinDam); + EXPECT_EQ(item1->_iMaxDam, item2->_iMaxDam); + EXPECT_EQ(item1->_iAC, item2->_iAC); + EXPECT_EQ(item1->_iFlags, item2->_iFlags); + EXPECT_EQ(item1->_iMiscId, item2->_iMiscId); + EXPECT_EQ(item1->_iSpell, item2->_iSpell); + EXPECT_EQ(item1->_iCharges, item2->_iCharges); + EXPECT_EQ(item1->_iMaxCharges, item2->_iMaxCharges); + EXPECT_EQ(item1->_iDurability, item2->_iDurability); + EXPECT_EQ(item1->_iMaxDur, item2->_iMaxDur); + EXPECT_EQ(item1->_iPLDam, item2->_iPLDam); + EXPECT_EQ(item1->_iPLToHit, item2->_iPLToHit); + EXPECT_EQ(item1->_iPLAC, item2->_iPLAC); + EXPECT_EQ(item1->_iPLStr, item2->_iPLStr); + EXPECT_EQ(item1->_iPLMag, item2->_iPLMag); + EXPECT_EQ(item1->_iPLDex, item2->_iPLDex); + EXPECT_EQ(item1->_iPLVit, item2->_iPLVit); + EXPECT_EQ(item1->_iPLFR, item2->_iPLFR); + EXPECT_EQ(item1->_iPLLR, item2->_iPLLR); + EXPECT_EQ(item1->_iPLMR, item2->_iPLMR); + EXPECT_EQ(item1->_iPLMana, item2->_iPLMana); + EXPECT_EQ(item1->_iPLHP, item2->_iPLHP); + EXPECT_EQ(item1->_iPLDamMod, item2->_iPLDamMod); + EXPECT_EQ(item1->_iPLGetHit, item2->_iPLGetHit); + EXPECT_EQ(item1->_iPLLight, item2->_iPLLight); + EXPECT_EQ(item1->_iSplLvlAdd, item2->_iSplLvlAdd); + EXPECT_EQ(item1->_iUid, item2->_iUid); + EXPECT_EQ(item1->_iFMinDam, item2->_iFMinDam); + EXPECT_EQ(item1->_iFMaxDam, item2->_iFMaxDam); + EXPECT_EQ(item1->_iLMinDam, item2->_iLMinDam); + EXPECT_EQ(item1->_iLMaxDam, item2->_iLMaxDam); + EXPECT_EQ(item1->_iPrePower, item2->_iPrePower); + EXPECT_EQ(item1->_iSufPower, item2->_iSufPower); + EXPECT_EQ(item1->_iMinStr, item2->_iMinStr); + EXPECT_EQ(item1->_iMinMag, item2->_iMinMag); + EXPECT_EQ(item1->_iMinDex, item2->_iMinDex); + EXPECT_EQ(item1->IDidx, item2->IDidx); +} + +const PkItemStruct PackedDiabloItems[] = { + // clang-format off + // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff + { 2082213289, 0x119, 53, 3, 60, 60, 0, 0, 0, 0 }, // Amber Helm of harmony + { 338833725, 0x118, 154, 3, 0, 0, 0, 0, 0, 0 }, // Cobalt Amulet of giants + { 545145866, 0x11A, 120, 3, 38, 40, 0, 0, 0, 0 }, // Brutal Sword of gore + { 1504248345, 0x35A, 70, 5, 255, 255, 0, 0, 0, 0 }, // Demonspike Coat + { 1884424756, 0x146, 151, 3, 0, 0, 0, 0, 0, 0 }, // Steel Ring of the jaguar + { 1712759905, 0xDC, 151, 3, 0, 0, 0, 0, 0, 0 }, // Ring of the heavens + { 981777658, 0x11E, 153, 2, 0, 0, 0, 0, 0, 0 }, // Ring of sorcery + { 844854815, 0x11A, 75, 3, 255, 255, 0, 0, 0, 0 }, // Shield of the ages + { 1151513535, 0x11E, 73, 2, 12, 32, 0, 0, 0, 0 }, // Sapphire Shield + { 640243885, 6, 27, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Town Portal + { 741806938, 9, 25, 0, 0, 0, 0, 0, 0, 0 }, // IDI_MANA + { 1456608333, 257, 79, 0, 0, 0, 0, 0, 0, 0 }, // Mana + { 554676945, 16, 30, 0, 0, 0, 0, 0, 0, 0 }, // Full mana + { 355389938, 0, 24, 0, 0, 0, 0, 0, 0, 0 }, // Healing + { 868435486, 16, 29, 0, 0, 0, 0, 0, 0, 0 }, // Full healing + { 1372832903, 0, 4, 0, 30, 30, 0, 0, 0, 0 }, // IDI_ROGUE + { 896239556, 2068, 53, 3, 56, 60, 0, 0, 0, 0 }, // Jade Helm of the wolf + { 1286225254, 269, 151, 3, 0, 0, 0, 0, 0, 0 }, // Steel Ring of accuracy + { 548642293, 266, 21, 0, 0, 0, 0, 0, 0, 0 }, // Blood Stone + { 307669016, 270, 151, 3, 0, 0, 0, 0, 0, 0 }, // Ring of power + { 204766888, 332, 154, 3, 0, 0, 0, 0, 0, 0 }, // Gold Amulet of accuracy + { 1642744332, 273, 122, 3, 25, 60, 0, 0, 0, 0 }, // Sword of the bat + { 1031508271, 1036, 72, 0, 14, 24, 0, 0, 0, 0 }, // Small Shield + { 1384620228, 338, 65, 3, 44, 80, 0, 0, 0, 0 }, // Plate of giant + { 681296530, 266, 87, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Healing + { 109984285, 16390, 81, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Rejuvenation - made by Pepin + { 1857753366, 260, 81, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Rejuvenation + { 965103261, 273, 82, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Full Rejuvenation + { 430621075, 217, 141, 3, 45, 45, 0, 0, 0, 0 }, // Savage Bow of perfection + { 1272669062, 258, 115, 0, 10, 20, 0, 0, 0, 0 }, // Falchion + { 1133884051, 278, 120, 2, 18, 40, 0, 0, 0, 0 }, // Sword of vim + { 1743897351, 259, 146, 2, 10, 25, 60, 60, 0, 0 }, // Frog's Staff of Holy Bolt + { 429107209, 0, 5, 0, 25, 25, 9, 40, 0, 0 }, // IDI_SORCERER + { 466015738, 257, 146, 0, 18, 25, 50, 50, 0, 0 }, // Staff of Charged Bolt + { 686949358, 193, 48, 3, 12, 15, 0, 0, 0, 0 }, // Cap of the mind armor + { 888855755, 195, 58, 3, 30, 30, 0, 0, 0, 0 }, // Armor of protection + { 2, 776, 8, 5, 0, 0, 0, 0, 0, 0 }, // Empyrean Band, + { 3, 776, 10, 5, 0, 0, 0, 0, 0, 0 }, // Optic Amulet + { 4, 772, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth + { 5, 776, 13, 5, 15, 15, 0, 0, 0, 0 }, // Harlequin Crest + { 6, 527, 14, 5, 60, 60, 0, 0, 0, 0 }, // Veil of Steel + { 7, 781, 28, 5, 39, 40, 0, 0, 0, 0 }, // Arkaine's Valor + { 8, 787, 31, 5, 42, 44, 0, 0, 0, 0 }, // Griswold's Edge + { 557339094, 8208, 150, 3, 75, 75, 0, 0, 0, 0 }, // Staff of haste + { 1684844665, 8208, 150, 3, 75, 75, 56, 56, 0, 0 }, // White Staff of Lightning + { 1297052552, 2074, 137, 3, 50, 50, 0, 0, 0, 0 }, // Lightning Maul + { 981895960, 2073, 130, 3, 75, 75, 0, 0, 0, 0 }, // Ivory Axe of blood + { 935416728, 2070, 52, 3, 18, 40, 0, 0, 0, 0 }, // Jade Crown of vim + { 1140525626, 257, 138, 3, 16, 30, 0, 0, 0, 0 }, // Bow of atrophy + { 1187758333, 258, 113, 3, 11, 16, 0, 0, 0, 0 }, // Brass Dagger of weakness + { 1283803700, 267, 138, 3, 16, 30, 0, 0, 0, 0 }, // Clumsy Bow + { 1317748726, 261, 114, 3, 17, 24, 0, 0, 0, 0 }, // Tin Sword of the fool + { 1331764286, 261, 135, 3, 6, 20, 0, 0, 0, 0 }, // Club of paralysis + { 1375639122, 269, 146, 3, 18, 25, 46, 46, 0, 0 }, // Dull Staff of Lightning + { 145523894, 277, 115, 3, 6, 20, 0, 0, 0, 0 }, // Sword of speed + { 1527777846, 259, 115, 3, 14, 20, 0, 0, 0, 0 }, // Bent Falchion + { 1655088363, 288, 146, 3, 13, 25, 98, 98, 0, 0 }, // Plentiful Staff of Firebolt + { 1679472538, 263, 113, 3, 6, 16, 0, 0, 0, 0 }, // Dagger of illness + { 1812092773, 264, 54, 3, 4, 12, 0, 0, 0, 0 }, // Cape of corruption + { 1965885799, 278, 119, 3, 33, 45, 0, 0, 0, 0 }, // Sabre of trouble + { 1970135469, 258, 48, 3, 5, 15, 0, 0, 0, 0 }, // Cap of tears + { 1979635474, 261, 135, 3, 14, 20, 0, 0, 0, 0 }, // Tin Club + { 2008721689, 258, 54, 3, 9, 12, 0, 0, 0, 0 }, // Rusted Cape of dyslexia + { 2082373839, 262, 48, 3, 10, 15, 0, 0, 0, 0 }, // Cap of pain + { 278391972, 263, 119, 3, 1, 1, 0, 0, 0, 0 }, // Clumsy Sabre of fragility + { 283130709, 260, 48, 3, 5, 15, 0, 0, 0, 0 }, // Vulnerable Cap of health + { 308974695, 264, 113, 3, 5, 16, 0, 0, 0, 0 }, // Useless Dagger + { 588501657, 300, 146, 3, 17, 25, 36, 36, 0, 0 }, // Bountiful Staff of Fire Wall + { 640482348, 259, 131, 3, 22, 32, 0, 0, 0, 0 }, // Mace of frailty + { 715324531, 258, 138, 3, 20, 30, 0, 0, 0, 0 }, // Weak Bow + { 794222370, 259, 146, 3, 12, 25, 0, 0, 0, 0 }, // Staff of readiness + // clang-format on +}; + +const TestItemStruct DiabloItems[] = { + // clang-format off + //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); + { "Amber Helm of harmony", 7, 2, 98, 21100, 0, 0, 11, 8388608, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0, 0, 0, 0, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 59, 50, 0, 0, 53 }, + { "Cobalt Amulet of giants", 13, 3, 45, 26840, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 0, 0, 0, 159 }, + { "Brutal Sword of gore", 1, 1, 60, 13119, 2, 10, 0, 0, 0, 0, 0, 0, 38, 40, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 2, 61, 30, 0, 30, 125 }, + { "Demonspike Coat", 9, 2, 151, 251175, 0, 0, 100, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 10, 0, 0, 0, 50, 0, 0, 0, 0, 0, -6, 0, 0, 78, 0, 0, 0, 0, -1, -1, 90, 0, 0, 70 }, + { "Steel Ring of the jaguar", 12, 3, 12, 10600, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1024, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 156 }, + { "Ring of the heavens", 12, 3, 12, 37552, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 14, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 27, 0, 0, 0, 156 }, + { "Ring of sorcery", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 158 }, + { "Shield of the ages", 5, 2, 132, 4850, 0, 0, 18, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 37, 60, 0, 0, 75 }, + { "Sapphire Shield", 5, 2, 147, 21000, 0, 0, 7, 0, 0, 0, 0, 0, 12, 32, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, -1, 40, 0, 0, 73 }, + { "Scroll of Town Portal", 0, 3, 1, 200, 0, 0, 0, 0, 21, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 27 }, + { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 25 }, + { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 79 }, + { "Potion of Full Mana", 0, 3, 0, 150, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 30 }, + { "Potion of Healing", 0, 3, 32, 50, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 24 }, + { "Potion of Full Healing", 0, 3, 35, 150, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 29 }, + { "Short Bow", 3, 1, 118, 100, 1, 4, 0, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 4 }, + { "Jade Helm of the wolf", 7, 2, 98, 22310, 0, 0, 14, 0, 0, 0, 0, 0, 56, 60, 0, 0, 0, 0, 0, 0, 0, 27, 27, 27, 0, 2112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 31, 50, 0, 0, 53 }, + { "Steel Ring of accuracy", 12, 3, 12, 13400, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 156 }, + { "Blood Stone", 0, 5, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 21 }, + { "Ring of power", 12, 3, 12, 6400, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 0, 0, 0, 156 }, + { "Gold Amulet of accuracy", 13, 3, 45, 20896, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 159 }, + { "Sword of the bat", 1, 1, 57, 10500, 6, 15, 0, 8192, 0, 0, 0, 0, 25, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 55, 50, 0, 0, 127 }, + { "Small Shield", 5, 2, 105, 90, 0, 0, 3, 0, 0, 0, 0, 0, 14, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 25, 0, 0, 72 }, + { "Plate of giants", 9, 2, 153, 23250, 0, 0, 20, 0, 0, 0, 0, 0, 44, 80, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 40, 0, 0, 65 }, + { "Scroll of Healing", 0, 3, 1, 50, 0, 0, 0, 0, 21, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 91 }, + { "Potion of Rejuvenation", 0, 3, 37, 120, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 81 }, + { "Potion of Rejuvenation", 0, 3, 37, 120, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 81 }, + { "Potion of Full Rejuvenation", 0, 3, 33, 600, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 82 }, + { "Savage Bow of perfection", 3, 1, 133, 23438, 3, 6, 0, 0, 0, 0, 0, 0, 45, 45, 117, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 23, 25, 0, 40, 146 }, + { "Falchion", 1, 1, 62, 250, 4, 8, 0, 0, 0, 0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 30, 0, 0, 120 }, + { "Sword of vim", 1, 1, 60, 4400, 2, 10, 0, 0, 0, 0, 0, 0, 18, 40, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 25, 30, 0, 30, 125 }, + { "Frog's Staff of Holy Bolt", 10, 1, 109, 1, 2, 4, 0, 0, 23, 31, 60, 60, 10, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -384, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, -1, 0, 20, 0, 151 }, + { "Short Staff of Charged Bolt", 10, 1, 109, 520, 2, 4, 0, 0, 23, 30, 9, 40, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 20, 0, 166 }, + { "Staff of Charged Bolt", 10, 1, 109, 1, 2, 4, 0, 0, 23, 30, 50, 50, 18, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 25, 0, 151 }, + { "Cap of the mind", 7, 2, 91, 1845, 0, 0, 2, 0, 0, 0, 0, 0, 12, 15, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 48 }, + { "Armor of protection", 6, 2, 129, 1200, 0, 0, 7, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, -1, 30, 0, 0, 0, 58 }, + { "Empyrean Band", 12, 3, 18, 8000, 0, 0, 0, 270532608, 27, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 8 }, + { "Optic Amulet", 13, 3, 44, 9750, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 20, 0, 0, 0, 0, -1, 2, 0, 3, 0, 0, 0, 0, -1, -1, 0, 0, 0, 10 }, + { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, + { "Harlequin Crest", 7, 2, 81, 4000, 0, 0, -3, 0, 27, 0, 0, 0, 15, 15, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 448, 448, 0, -1, 0, 0, 5, 0, 0, 0, 0, -1, -1, 0, 0, 0, 13 }, + { "Veil of Steel", 7, 2, 85, 63800, 0, 0, 18, 0, 27, 0, 0, 0, 60, 60, 0, 0, 60, 15, 0, 0, 15, 50, 50, 50, -1920, 0, 0, 0, -2, 0, 6, 0, 0, 0, 0, -1, -1, 0, 0, 0, 14 }, + { "Arkaine's Valor", 8, 2, 157, 42000, 0, 0, 25, 8388608, 27, 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, -3, 0, 0, 7, 0, 0, 0, 0, -1, -1, 0, 0, 0, 28 }, + { "Griswold's Edge", 1, 1, 61, 42000, 4, 12, 0, 264208, 27, 0, 0, 0, 42, 44, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1280, -1280, 0, 0, 0, 0, 8, 1, 10, 0, 0, -1, -1, 40, 0, 0, 31 }, + { "Staff of haste", 10, 1, 124, 40000, 8, 16, 0, 1048576, 23, 0, 0, 0, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 30, 0, 0, 155 }, + { "White Staff of Lightning", 10, 1, 124, 7160, 8, 16, 0, 0, 23, 3, 56, 56, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, -1, 30, 20, 0, 155 }, + { "Lightning Maul", 4, 1, 122, 11800, 6, 20, 0, 32, 0, 0, 0, 0, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 17, -1, 55, 0, 0, 142 }, + { "Ivory Axe of blood", 2, 1, 143, 31194, 12, 30, 0, 65536, 0, 0, 0, 0, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 56, 80, 0, 0, 135 }, + { "Jade Crown of vim", 7, 2, 95, 19200, 0, 0, 10, 0, 0, 0, 0, 0, 18, 40, 0, 0, 0, 0, 0, 0, 14, 30, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 25, 0, 0, 0, 52 }, + { "Bow of atrophy", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 16, 30, 0, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 24, 0, 0, 0, 143 }, + { "Brass Dagger of weakness", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 11, 16, 0, -1, 0, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 20, 0, 0, 0, 118 }, + { "Clumsy Bow", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 16, 30, -67, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -1, 0, 0, 0, 143 }, + { "Tin Sword of the fool", 1, 1, 64, 1, 2, 6, 0, 0, 0, 0, 0, 0, 17, 24, 0, -7, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 22, 18, 0, 0, 119 }, + { "Club of paralysis", 4, 1, 66, 1, 1, 6, 0, 0, 0, 0, 0, 0, 6, 20, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 24, 0, 0, 0, 140 }, + { "Dull Staff of Lightning", 10, 1, 109, 1, 2, 4, 0, 0, 23, 3, 46, 46, 18, 25, -28, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -1, 0, 20, 0, 151 }, + { "Sword of speed", 1, 1, 62, 10000, 4, 8, 0, 524288, 0, 0, 0, 0, 6, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 30, 0, 0, 120 }, + { "Bent Falchion", 1, 1, 62, 1, 4, 8, 0, 0, 0, 0, 0, 0, 14, 20, -68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 30, 0, 0, 120 }, + { "Plentiful Staff of Firebolt", 10, 1, 109, 3040, 2, 4, 0, 0, 23, 1, 98, 98, 13, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, -1, 0, 15, 0, 151 }, + { "Dagger of illness", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 6, 16, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 26, 0, 0, 0, 118 }, + { "Cape of corruption", 6, 2, 150, 1, 0, 0, 3, 134217728, 0, 0, 0, 0, 4, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 46, 0, 0, 0, 54 }, + { "Sabre of trouble", 1, 1, 67, 1, 1, 8, 0, 0, 0, 0, 0, 0, 33, 45, 0, 0, 0, -6, -6, -6, -6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 28, 17, 0, 0, 124 }, + { "Cap of tears", 7, 2, 91, 1, 0, 0, 2, 0, 0, 0, 0, 0, 5, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, 29, 0, 0, 0, 48 }, + { "Tin Club", 4, 1, 66, 1, 1, 6, 0, 0, 0, 0, 0, 0, 14, 20, 0, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 140 }, + { "Rusted Cape of dyslexia", 6, 2, 150, 1, 0, 0, 2, 0, 0, 0, 0, 0, 9, 12, 0, 0, -34, 0, -4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 22, 0, 0, 0, 54 }, + { "Cap of pain", 7, 2, 91, 1, 0, 0, 2, 0, 0, 0, 0, 0, 10, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -1, 29, 0, 0, 0, 48 }, + { "Clumsy Sabre of fragility", 1, 1, 67, 1, 1, 8, 0, 0, 0, 0, 0, 0, 1, 1, -75, -10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 36, 17, 0, 0, 124 }, + { "Vulnerable Cap of health", 7, 2, 91, 185, 0, 0, 1, 0, 0, 0, 0, 0, 5, 15, 0, 0, -63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 7, 30, 0, 0, 0, 48 }, + { "Useless Dagger", 1, 1, 51, 1, 1, 4, 0, 0, 0, 0, 0, 0, 5, 16, -100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 0, 0, 0, 118 }, + { "Bountiful Staff of Fire Wall", 10, 1, 109, 5970, 2, 4, 0, 0, 23, 6, 36, 36, 17, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, -1, 0, 27, 0, 151 }, + { "Mace of frailty", 4, 1, 59, 1, 1, 8, 0, 0, 0, 0, 0, 0, 22, 32, 0, 0, 0, -7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 20, 16, 0, 0, 136 }, + { "Weak Bow", 3, 1, 118, 1, 1, 4, 0, 0, 0, 0, 0, 0, 20, 30, -44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 0, 0, 0, 143 }, + { "Staff of readiness", 10, 1, 109, 2060, 2, 4, 0, 131072, 23, 0, 0, 0, 12, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 58, 0, 0, 0, 151 }, + // clang-format on +}; + +TEST(pack, UnPackItem_diablo) +{ + ItemStruct id; + PkItemStruct is; + + gbIsHellfire = false; + gbIsMultiplayer = false; + + for (size_t i = 0; i < sizeof(PackedDiabloItems) / sizeof(*PackedDiabloItems); i++) { + UnPackItem(&PackedDiabloItems[i], &id, false); + CompareItems(&id, &DiabloItems[i]); + + PackItem(&is, &id); + ComparePackedItems(&is, &PackedDiabloItems[i]); + } +} + +TEST(pack, UnPackItem_diablo_unique_bug) +{ + PkItemStruct pkItemBug = { 6, 911, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - with morph bug + PkItemStruct pkItem = { 6, 655, 14, 5, 60, 60, 0, 0, 0, 0 }; // Veil of Steel - fixed + + gbIsHellfire = false; + gbIsMultiplayer = false; + + ItemStruct id; + UnPackItem(&pkItemBug, &id, false); + ASSERT_STREQ(id._iIName, "Veil of Steel"); + ASSERT_EQ(id._itype, ITYPE_HELM); + ASSERT_EQ(id._iClass, ICLASS_ARMOR); + ASSERT_EQ(id._iCurs, 85); + ASSERT_EQ(id._iIvalue, 63800); + ASSERT_EQ(id._iAC, 18); + ASSERT_EQ(id._iMiscId, IMISC_UNIQUE); + ASSERT_EQ(id._iPLAC, 60); + ASSERT_EQ(id._iPLStr, 15); + ASSERT_EQ(id._iPLVit, 15); + ASSERT_EQ(id._iPLFR, 50); + ASSERT_EQ(id._iPLLR, 50); + ASSERT_EQ(id._iPLMR, 50); + ASSERT_EQ(id._iPLMana, -1920); + ASSERT_EQ(id._iPLLight, -2); + ASSERT_EQ(id._iUid, 6); + ASSERT_EQ(id.IDidx, IDI_STEELVEIL); + + PkItemStruct is; + PackItem(&is, &id); + ComparePackedItems(&is, &pkItem); +} + +const PkItemStruct PackedDiabloMPItems[] = { + // clang-format off + // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff + { 309674341, 193, 109, 0, 0, 0, 0, 0, 0, 0 }, // Book of Firebolt + { 1291471654, 6, 34, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Resurrect + // clang-format on +}; + +const TestItemStruct DiabloMPItems[] = { + // clang-format off + //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); + { "Book of Firebolt", 0, 3, 87, 1000, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 15, 0, 114 }, + { "Scroll of Resurrect", 0, 3, 1, 250, 0, 0, 0, 0, 22, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 34 }, + // clang-format on +}; + +TEST(pack, UnPackItem_diablo_multiplayer) +{ + ItemStruct id; + PkItemStruct is; + + gbIsHellfire = false; + gbIsMultiplayer = true; + + for (size_t i = 0; i < sizeof(PackedDiabloMPItems) / sizeof(*PackedDiabloMPItems); i++) { + UnPackItem(&PackedDiabloMPItems[i], &id, false); + CompareItems(&id, &DiabloMPItems[i]); + + PackItem(&is, &id); + ComparePackedItems(&is, &PackedDiabloMPItems[i]); + } +} + +const PkItemStruct PackedHellfireItems[] = { + // clang-format off + // iSeed, iCreateInfo, idx, bId, bDur, bMDur, bCh, bMCh, wValue, dwBuff + { 1717442367, 266, 156, 3, 0, 0, 0, 0, 0, 0 }, // Ring of stability + { 1268518156, 338, 157, 3, 0, 0, 0, 0, 0, 0 }, // Ring of precision + { 132733863, 283, 157, 3, 0, 0, 0, 0, 0, 0 }, // Obsidian Ring of wizardry + { 511953594, 283, 158, 3, 0, 0, 0, 0, 0, 0 }, // Ring of precision + { 1183326923, 346, 160, 3, 0, 0, 0, 0, 0, 0 }, // Amulet of titans + { 1863009736, 280, 160, 3, 0, 0, 0, 0, 0, 0 }, // Gold Amulet + { 1872860650, 734, 135, 5, 75, 75, 0, 0, 0, 0 }, // Messerschmidt's Reaver + { 1584694222, 281, 142, 3, 127, 128, 0, 0, 0, 0 }, // Vicious Maul of structure + { 669112929, 280, 119, 0, 15, 24, 0, 0, 0, 0 }, // Short Sword + { 303108965, 280, 148, 3, 18, 50, 0, 0, 0, 0 }, // Bow of shock + { 575830996, 257, 143, 3, 30, 30, 0, 0, 0, 0 }, // Bow of magic + { 1488880650, 194, 152, 3, 35, 35, 22, 33, 0, 0 }, // Red Staff of Healing + { 1864450901, 263, 71, 0, 6, 16, 0, 0, 0, 0 }, // Buckler + { 28387651, 263, 49, 0, 15, 20, 0, 0, 0, 0 }, // Skull Cap + { 1298183212, 257, 55, 0, 6, 6, 0, 0, 0, 0 }, // Rags + { 1113945523, 260, 58, 0, 30, 30, 0, 0, 0, 0 }, // Quilted Armor + { 765757608, 260, 58, 2, 12, 30, 0, 0, 0, 0 }, // Armor of light + { 188812770, 346, 67, 3, 75, 75, 0, 0, 0, 0 }, // Saintly Plate of the stars + { 283577043, 2070, 67, 3, 63, 75, 0, 0, 0, 0 }, // Plate of the stars + { 123272767, 16, 24, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Healing + { 433688373, 16, 29, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Full Healing + { 1213385484, 32770, 25, 0, 0, 0, 0, 0, 0, 0 }, // Potion of Mana + { 1405075219, 280, 110, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Golem + { 1478792102, 259, 92, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Search + { 1569255955, 262, 94, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Identify + { 1291205782, 261, 98, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Town Portal + { 811925807, 260, 91, 0, 0, 0, 0, 0, 0, 0 }, // Scroll of Healing + { 1275007287, 257, 161, 0, 0, 0, 0, 0, 0, 0 }, // Rune of Fire + { 561216242, 278, 0, 0, 0, 0, 0, 0, 1663, 0 }, // Gold + { 1, 515, 7, 5, 45, 50, 0, 0, 0, 0 }, // The Undead Crown + { 2, 774, 8, 5, 0, 0, 0, 0, 0, 0 }, // Empyrean Band + { 4, 769, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth + { 8, 512, 31, 5, 50, 50, 0, 0, 0, 0 }, // Griswold's Edge + { 9, 850, 32, 5, 255, 255, 0, 0, 0, 0 }, // Bovine Plate + { 410929431, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Healing + { 876535546, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Charged Bolt + { 1009350361, 258, 114, 0, 0, 0, 0, 0, 0, 0 }, // Book of Firebolt + { 41417651, 258, 83, 0, 0, 0, 0, 0, 0, 0 }, // Blacksmith Oil + { 132200437, 258, 84, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Accuracy + { 385651490, 257, 85, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Sharpness + { 1154514759, 290, 86, 0, 0, 0, 0, 0, 0, 0 }, // Oil of Permanence + { 2020998927, 2066, 131, 3, 23, 32, 0, 0, 0, 0 }, // Doppelganger's Axe + { 581541889, 2067, 141, 3, 36, 36, 0, 0, 0, 0 }, // Flail of vampires + { 1069448901, 844, 157, 5, 0, 0, 0, 0, 0, 0 }, // Gladiator's Ring + { 1670063399, 2068, 155, 3, 75, 75, 0, 0, 0, 0 }, // Warrior's Staff of the moon + { 342570085, 4114, 74, 3, 255, 255, 0, 0, 0, 0 }, // Shield of the ages + { 1514523617, 2066, 139, 3, 20, 20, 0, 0, 0, 0 }, // Heavy Club of puncturing + { 701987341, 8208, 114, 1, 0, 0, 0, 0, 0, 0 }, // Book of Lightning + { 568338383, 196, 124, 3, 23, 45, 0, 0, 0, 0 }, // Jester's Sabre + { 1308277119, 2056, 72, 3, 24, 24, 0, 0, 0, 0 }, // Shield of blocking + { 0, 512, 6, 5, 10, 10, 0, 0, 0, 0 }, // The Butcher's Cleaver + { 1621745295, 2057, 121, 3, 28, 28, 0, 0, 0, 0 }, // Scimitar of peril + { 492619876, 2054, 132, 3, 12, 12, 0, 0, 0, 0 }, // Crystalline Axe + { 1859493982, 2053, 56, 3, 18, 18, 0, 0, 0, 0 }, // Red Cloak + { 1593032051, 2050, 136, 3, 32, 32, 0, 0, 0, 0 }, // Mace of decay + { 4, 512, 11, 5, 0, 0, 0, 0, 0, 0 }, // Ring of Truth + { 1500728519, 260, 61, 3, 18, 45, 0, 0, 0, 0 }, // Red Armor of paralysis + { 954183925, 261, 144, 3, 26, 40, 0, 0, 0, 0 }, // Bent Bow + { 438248055, 711, 136, 5, 32, 32, 0, 0, 0, 0 }, // Civerb's Cudgel + { 1133027011, 328, 139, 3, 8, 20, 0, 0, 0, 0 }, // Deadly Club + { 224835143, 732, 144, 5, 255, 255, 0, 0, 0, 0 }, // Gnat Sting + { 1498080548, 796, 138, 5, 255, 255, 0, 0, 0, 0 }, // Thunderclap + // clang-format on +}; + +const TestItemStruct HellfireItems[] = { + // clang-format off + //_iIName, _itype, _iClass, _iCurs, _iIvalue, _iMinDam, _iMaxDam, _iAC, _iFlags, _iMiscId, _iSpell, _iCharges, _iMaxCharges, _iDurability, _iMaxDur, _iPLDam, _iPLToHit, _iPLAC, _iPLStr, _iPLMag, _iPLDex, _iPLVit, _iPLFR, _iPLLR, _iPLMR, _iPLMana, _iPLHP, _iPLDamMod, _iPLGetHit, _iPLLight, _iSplLvlAdd, _iUid, _iFMinDam, _iFMaxDam, _iLMinDam, _iLMaxDam, _iPrePower, _iSufPower, _iMinStr, _iMinMag, _iMinDex, IDidx ); + { "Ring of stability", 12, 3, 12, 8000, 0, 0, 0, 4194304, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 59, 0, 0, 0, 156 }, + { "Ring of precision", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 23, 0, 0, 0, 157 }, + { "Obsidian Ring of wizardry", 12, 3, 12, 56928, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 37, 37, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 21, 0, 0, 0, 157 }, + { "Ring of precision", 12, 3, 12, 10200, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 23, 0, 0, 0, 158 }, + { "Amulet of titans", 13, 3, 45, 20896, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 19, 0, 0, 0, 160 }, + { "Gold Amulet", 13, 3, 45, 13692, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 160 }, + { "Messerschmidt's Reaver", 2, 1, 163, 58000, 12, 30, 0, 16, 0, 0, 0, 0, 75, 75, 200, 0, 0, 5, 5, 5, 5, 0, 0, 0, 0, -3200, 15, 0, 0, 0, 44, 2, 12, 0, 0, -1, -1, 80, 0, 0, 135 }, + { "Vicious Maul of structure", 4, 1, 122, 10489, 6, 20, 0, 0, 0, 0, 0, 0, 127, 128, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 35, 55, 0, 0, 142 }, + { "Short Sword", 1, 1, 64, 120, 2, 6, 0, 0, 0, 0, 0, 0, 15, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 18, 0, 0, 119 }, + { "Bow of shock", 3, 1, 119, 8000, 1, 10, 0, 33554432, 0, 0, 0, 0, 18, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, -1, 43, 30, 0, 60, 148 }, + { "Bow of magic", 3, 1, 118, 400, 1, 4, 0, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 21, 0, 0, 0, 143 }, + { "Red Staff of Healing", 10, 1, 123, 1360, 4, 8, 0, 0, 23, 2, 22, 33, 35, 35, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, -1, 0, 17, 0, 152 }, + { "Buckler", 5, 2, 83, 30, 0, 0, 5, 0, 0, 0, 0, 0, 6, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 71 }, + { "Skull Cap", 7, 2, 90, 25, 0, 0, 3, 0, 0, 0, 0, 0, 15, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 49 }, + { "Rags", 6, 2, 128, 5, 0, 0, 4, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 55 }, + { "Quilted Armor", 6, 2, 129, 200, 0, 0, 7, 0, 0, 0, 0, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 58 }, + { "Armor of light", 6, 2, 129, 1150, 0, 0, 10, 0, 0, 0, 0, 0, 12, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -1, 38, 0, 0, 0, 58 }, + { "Saintly Plate of the stars", 9, 2, 103, 140729, 0, 0, 46, 0, 0, 0, 0, 0, 75, 75, 0, 0, 121, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 27, 60, 0, 0, 67 }, + { "Plate of the stars", 9, 2, 103, 77800, 0, 0, 49, 0, 0, 0, 0, 0, 63, 75, 0, 0, 0, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 27, 60, 0, 0, 67 }, + { "Potion of Healing", 0, 3, 32, 50, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 24 }, + { "Potion of Full Healing", 0, 3, 35, 150, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 29 }, + { "Potion of Mana", 0, 3, 39, 50, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 25 }, + { "Scroll of Golem", 0, 3, 1, 1100, 0, 0, 0, 0, 22, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 51, 0, 110 }, + { "Scroll of Search", 0, 3, 1, 50, 0, 0, 0, 0, 21, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 92 }, + { "Scroll of Identify", 0, 3, 1, 100, 0, 0, 0, 0, 21, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 94 }, + { "Scroll of Town Portal", 0, 3, 1, 200, 0, 0, 0, 0, 21, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 98 }, + { "Scroll of Healing", 0, 3, 1, 50, 0, 0, 0, 0, 21, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 91 }, + { "Rune of Fire", 0, 3, 193, 100, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 161 }, + { "Gold", 11, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0 }, + { "The Undead Crown", 7, 2, 77, 16650, 0, 0, 8, 2, 27, 0, 0, 0, 45, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 7 }, + { "Empyrean Band", 12, 3, 18, 8000, 0, 0, 0, 270532608, 27, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, -1, -1, 0, 0, 0, 8 }, + { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, + { "Griswold's Edge", 1, 1, 61, 42000, 4, 12, 0, 264208, 27, 0, 0, 0, 50, 50, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1280, -1280, 0, 0, 0, 0, 8, 1, 10, 0, 0, -1, -1, 40, 0, 0, 31 }, + { "Bovine Plate", 9, 2, 226, 400, 0, 0, 150, 0, 27, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 30, 30, 30, -3200, 0, 0, 0, 5, -2, 9, 0, 0, 0, 0, -1, -1, 50, 0, 0, 32 }, + { "Book of Healing", 0, 3, 86, 1000, 0, 0, 0, 0, 24, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 17, 0, 114 }, + { "Book of Charged Bolt", 0, 3, 88, 1000, 0, 0, 0, 0, 24, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 25, 0, 114 }, + { "Book of Firebolt", 0, 3, 87, 1000, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 15, 0, 114 }, + { "Blacksmith Oil", 0, 3, 30, 100, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 83 }, + { "Oil of Accuracy", 0, 3, 30, 500, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 84 }, + { "Oil of Sharpness", 0, 3, 30, 500, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 85 }, + { "Oil of Permanence", 0, 3, 30, 15000, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 86 }, + { "Doppelganger's Axe", 2, 1, 144, 6640, 4, 12, 0, 0, 0, 0, 0, 0, 23, 32, 86, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, -1, 22, 0, 0, 131 }, + { "Flail of vampires", 4, 1, 131, 16500, 2, 12, 0, 16384, 0, 0, 0, 0, 36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 55, 30, 0, 0, 141 }, + { "Gladiator's Ring", 12, 3, 186, 10000, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, -1, -1, 0, 0, 0, 157 }, + { "Warrior's Staff of the moon", 10, 1, 124, 42332, 8, 16, 0, 0, 23, 0, 0, 0, 75, 75, 54, 15, 0, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 27, 30, 0, 0, 155 }, + { "Shield of the ages", 5, 2, 113, 2600, 0, 0, 10, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 37, 50, 0, 0, 74 }, + { "Heavy Club of puncturing", 4, 1, 70, 5239, 3, 6, 0, 0, 0, 0, 0, 0, 20, 20, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 57, 18, 0, 0, 139 }, + { "Book of Lightning", 0, 3, 88, 3000, 0, 0, 0, 0, 24, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 20, 0, 114 }, + { "Jester's Sabre", 1, 1, 67, 1710, 1, 8, 0, 0, 0, 0, 0, 0, 23, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, -1, 17, 0, 0, 124 }, + { "Shield of blocking", 5, 2, 105, 4360, 0, 0, 6, 16777216, 0, 0, 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 60, 25, 0, 0, 72 }, + { "The Butcher's Cleaver", 2, 1, 106, 3650, 4, 24, 0, 0, 27, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 6 }, + { "Scimitar of peril", 1, 1, 72, 700, 3, 7, 0, 0, 0, 0, 0, 0, 28, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 86, 23, 0, 23, 121 }, + { "Crystalline Axe", 2, 1, 142, 5250, 6, 16, 0, 0, 0, 0, 0, 0, 12, 12, 280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, -1, 30, 0, 0, 132 }, + { "Red Cloak", 6, 2, 149, 580, 0, 0, 3, 0, 0, 0, 0, 0, 18, 18, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, -1, 0, 0, 0, 56 }, + { "Mace of decay", 4, 1, 59, 600, 1, 8, 0, 0, 0, 0, 0, 0, 32, 32, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 85, 16, 0, 0, 136 }, + { "Ring of Truth", 12, 3, 10, 9100, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 640, 0, -1, 0, 0, 4, 0, 0, 0, 0, -1, -1, 0, 0, 0, 11 }, + { "Red Armor of paralysis", 6, 2, 107, 800, 0, 0, 17, 0, 0, 0, 0, 0, 18, 45, 0, 0, 0, 0, 0, -8, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 24, 20, 0, 0, 61 }, + { "Bent Bow", 3, 1, 102, 1, 2, 5, 0, 0, 0, 0, 0, 0, 26, 40, -69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -1, 20, 0, 35, 144 }, + { "Civerb's Cudgel", 4, 1, 59, 2000, 1, 8, 0, 1073741824, 0, 0, 0, 0, 32, 32, 0, 0, 0, 0, -2, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, -1, -1, 16, 0, 0, 136 }, + { "Deadly Club", 4, 1, 70, 1556, 3, 6, 0, 0, 0, 0, 0, 0, 8, 20, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, 18, 0, 0, 139 }, + { "Gnat Sting", 3, 1, 210, 30000, 1, 2, 0, 131584, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 0, -1, -1, 20, 0, 35, 144 }, + { "Thunderclap", 4, 1, 205, 30000, 5, 9, 0, 48, 0, 0, 0, 0, 255, 255, 0, 0, 0, 20, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 2, 0, 102, 3, 6, 2, 0, -1, -1, 40, 0, 0, 138 }, + // clang-format on +}; + +TEST(pack, UnPackItem_hellfire) +{ + ItemStruct id; + PkItemStruct is; + + gbIsHellfire = true; + gbIsMultiplayer = false; + + for (size_t i = 0; i < sizeof(PackedHellfireItems) / sizeof(*PackedHellfireItems); i++) { + UnPackItem(&PackedHellfireItems[i], &id, true); + CompareItems(&id, &HellfireItems[i]); + + PackItem(&is, &id); + is.dwBuff &= ~CF_HELLFIRE; + ComparePackedItems(&is, &PackedHellfireItems[i]); + } +} + +TEST(pack, UnPackItem_diablo_strip_hellfire_items) +{ + PkItemStruct is = { 1478792102, 259, 92, 0, 0, 0, 0, 0, 0, 0 }; // Scroll of Search + ItemStruct id; + + gbIsHellfire = false; + gbIsMultiplayer = false; + + UnPackItem(&is, &id, true); + + ASSERT_EQ(id._itype, ITYPE_NONE); +} + +TEST(pack, UnPackItem_empty) +{ + PkItemStruct is = { 0, 0, 0xFFFF, 0, 0, 0, 0, 0, 0, 0 }; + ItemStruct id; + + UnPackItem(&is, &id, false); + + ASSERT_EQ(id._itype, ITYPE_NONE); +} + +TEST(pack, PackItem_empty) +{ + PkItemStruct is; + ItemStruct id; + + id._itype = ITYPE_NONE; + + PackItem(&is, &id); + + ASSERT_EQ(is.idx, 0xFFFF); +} + +static void compareGold(const PkItemStruct *is, int iCurs) +{ + ItemStruct id; + UnPackItem(is, &id, false); + ASSERT_EQ(id._iCurs, iCurs); + ASSERT_EQ(id.IDidx, IDI_GOLD); + ASSERT_EQ(id._ivalue, is->wValue); + ASSERT_EQ(id._itype, ITYPE_GOLD); + ASSERT_EQ(id._iClass, ICLASS_GOLD); + + PkItemStruct is2; + PackItem(&is2, &id); + ComparePackedItems(is, &is2); +} + +TEST(pack, UnPackItem_gold_small) +{ + PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1000, 0 }; + compareGold(&is, ICURS_GOLD_SMALL); +} + +TEST(pack, UnPackItem_gold_medium) +{ + PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 1001, 0 }; + compareGold(&is, ICURS_GOLD_MEDIUM); +} + +TEST(pack, UnPackItem_gold_large) +{ + PkItemStruct is = { 0, 0, IDI_GOLD, 0, 0, 0, 0, 0, 2500, 0 }; + compareGold(&is, ICURS_GOLD_LARGE); +} + +TEST(pack, UnPackItem_ear) +{ + PkItemStruct is = { 1633955154, 17509, 23, 111, 103, 117, 101, 68, 19843, 0 }; + ItemStruct id; + + UnPackItem(&is, &id, false); + ASSERT_STREQ(id._iName, "Ear of Dead-RogueDM"); + ASSERT_EQ(id._ivalue, 3); + + PkItemStruct is2; + PackItem(&is2, &id); + ComparePackedItems(&is, &is2); +} diff --git a/test/player_test.cpp b/test/player_test.cpp index 4e2dee9d9..4ee88332c 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -1,83 +1,83 @@ -#include - -#include "player.h" - -using namespace devilution; - -namespace devilution { -extern bool PM_DoGotHit(int pnum); -} - -int RunBlockTest(int frames, int flags) -{ - int pnum = 0; - plr[pnum].AnimInfo.CurrentFrame = 1; - plr[pnum]._pHFrames = frames; - plr[pnum].actionFrame = 1; - plr[pnum]._pIFlags = flags; - plr[pnum]._pmode = PM_GOTHIT; - plr[pnum]._pGFXLoad = -1; - - int i = 1; - for (; i < 100; i++) { - PM_DoGotHit(pnum); - if (plr[pnum]._pmode != PM_GOTHIT) - break; - plr[pnum].AnimInfo.CurrentFrame++; - } - - return i; -} - -#define NORM 0 -#define BAL ISPL_FASTRECOVER -#define STA ISPL_FASTERRECOVER -#define HAR ISPL_FASTESTRECOVER -#define BALSTA (ISPL_FASTRECOVER | ISPL_FASTERRECOVER) -#define BALHAR (ISPL_FASTRECOVER | ISPL_FASTESTRECOVER) -#define STAHAR (ISPL_FASTERRECOVER | ISPL_FASTESTRECOVER) -#define ZEN (ISPL_FASTRECOVER | ISPL_FASTERRECOVER | ISPL_FASTESTRECOVER) -#define WAR 6 -#define ROU 7 -#define SRC 8 - -int BlockData[][3] = { - { 6, WAR, NORM }, - { 7, ROU, NORM }, - { 8, SRC, NORM }, - - { 5, WAR, BAL }, - { 6, ROU, BAL }, - { 7, SRC, BAL }, - - { 4, WAR, STA }, - { 5, ROU, STA }, - { 6, SRC, STA }, - - { 3, WAR, HAR }, - { 4, ROU, HAR }, - { 5, SRC, HAR }, - - { 4, WAR, BALSTA }, - { 5, ROU, BALSTA }, - { 6, SRC, BALSTA }, - - { 3, WAR, BALHAR }, - { 4, ROU, BALHAR }, - { 5, SRC, BALHAR }, - - { 3, WAR, STAHAR }, - { 4, ROU, STAHAR }, - { 5, SRC, STAHAR }, - - { 2, WAR, ZEN }, - { 3, ROU, ZEN }, - { 4, SRC, ZEN }, -}; - -TEST(Player, PM_DoGotHit) -{ - for (size_t i = 0; i < sizeof(BlockData) / sizeof(*BlockData); i++) { - EXPECT_EQ(BlockData[i][0], RunBlockTest(BlockData[i][1], BlockData[i][2])); - } -} +#include + +#include "player.h" + +using namespace devilution; + +namespace devilution { +extern bool PM_DoGotHit(int pnum); +} + +int RunBlockTest(int frames, int flags) +{ + int pnum = 0; + plr[pnum].AnimInfo.CurrentFrame = 1; + plr[pnum]._pHFrames = frames; + plr[pnum].actionFrame = 1; + plr[pnum]._pIFlags = flags; + plr[pnum]._pmode = PM_GOTHIT; + plr[pnum]._pGFXLoad = -1; + + int i = 1; + for (; i < 100; i++) { + PM_DoGotHit(pnum); + if (plr[pnum]._pmode != PM_GOTHIT) + break; + plr[pnum].AnimInfo.CurrentFrame++; + } + + return i; +} + +#define NORM 0 +#define BAL ISPL_FASTRECOVER +#define STA ISPL_FASTERRECOVER +#define HAR ISPL_FASTESTRECOVER +#define BALSTA (ISPL_FASTRECOVER | ISPL_FASTERRECOVER) +#define BALHAR (ISPL_FASTRECOVER | ISPL_FASTESTRECOVER) +#define STAHAR (ISPL_FASTERRECOVER | ISPL_FASTESTRECOVER) +#define ZEN (ISPL_FASTRECOVER | ISPL_FASTERRECOVER | ISPL_FASTESTRECOVER) +#define WAR 6 +#define ROU 7 +#define SRC 8 + +int BlockData[][3] = { + { 6, WAR, NORM }, + { 7, ROU, NORM }, + { 8, SRC, NORM }, + + { 5, WAR, BAL }, + { 6, ROU, BAL }, + { 7, SRC, BAL }, + + { 4, WAR, STA }, + { 5, ROU, STA }, + { 6, SRC, STA }, + + { 3, WAR, HAR }, + { 4, ROU, HAR }, + { 5, SRC, HAR }, + + { 4, WAR, BALSTA }, + { 5, ROU, BALSTA }, + { 6, SRC, BALSTA }, + + { 3, WAR, BALHAR }, + { 4, ROU, BALHAR }, + { 5, SRC, BALHAR }, + + { 3, WAR, STAHAR }, + { 4, ROU, STAHAR }, + { 5, SRC, STAHAR }, + + { 2, WAR, ZEN }, + { 3, ROU, ZEN }, + { 4, SRC, ZEN }, +}; + +TEST(Player, PM_DoGotHit) +{ + for (size_t i = 0; i < sizeof(BlockData) / sizeof(*BlockData); i++) { + EXPECT_EQ(BlockData[i][0], RunBlockTest(BlockData[i][1], BlockData[i][2])); + } +} diff --git a/test/scrollrt_test.cpp b/test/scrollrt_test.cpp index 8139298bd..bf330352e 100644 --- a/test/scrollrt_test.cpp +++ b/test/scrollrt_test.cpp @@ -1,164 +1,164 @@ -#include - -#include "diablo.h" -#include "scrollrt.h" -#include "utils/ui_fwd.h" - -using namespace devilution; - -// TilesInView - -TEST(Scrool_rt, calc_tiles_in_view_original) -{ - gnScreenWidth = 640; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight - 128; - zoomflag = true; - int columns = 0; - int rows = 0; - TilesInView(&columns, &rows); - EXPECT_EQ(columns, 10); - EXPECT_EQ(rows, 11); -} - -TEST(Scrool_rt, calc_tiles_in_view_original_zoom) -{ - gnScreenWidth = 640; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight - 128; - zoomflag = false; - int columns = 0; - int rows = 0; - TilesInView(&columns, &rows); - EXPECT_EQ(columns, 5); - EXPECT_EQ(rows, 6); -} - -TEST(Scrool_rt, calc_tiles_in_view_960_540) -{ - gnScreenWidth = 960; - gnScreenHeight = 540; - gnViewportHeight = gnScreenHeight; - zoomflag = true; - int columns = 0; - int rows = 0; - TilesInView(&columns, &rows); - EXPECT_EQ(columns, 15); - EXPECT_EQ(rows, 17); -} - -TEST(Scrool_rt, calc_tiles_in_view_640_512) -{ - gnScreenWidth = 640; - gnScreenHeight = 512; - gnViewportHeight = gnScreenHeight - 128; - zoomflag = true; - int columns = 0; - int rows = 0; - TilesInView(&columns, &rows); - EXPECT_EQ(columns, 10); - EXPECT_EQ(rows, 12); -} - -TEST(Scrool_rt, calc_tiles_in_view_768_480_zoom) -{ - gnScreenWidth = 768; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight; - zoomflag = false; - int columns = 0; - int rows = 0; - TilesInView(&columns, &rows); - EXPECT_EQ(columns, 6); - EXPECT_EQ(rows, 8); -} - -// CalcTileOffset - -TEST(Scrool_rt, calc_tile_offset_original) -{ - gnScreenWidth = 640; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight - 128; - zoomflag = true; - int x = 0; - int y = 0; - CalcTileOffset(&x, &y); - EXPECT_EQ(x, 0); - EXPECT_EQ(y, 0); -} - -TEST(Scrool_rt, calc_tile_offset_original_zoom) -{ - gnScreenWidth = 640; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight - 128; - zoomflag = false; - int x = 0; - int y = 0; - CalcTileOffset(&x, &y); - EXPECT_EQ(x, 0); - EXPECT_EQ(y, 8); -} - -TEST(Scrool_rt, calc_tile_offset_960_540) -{ - gnScreenWidth = 960; - gnScreenHeight = 540; - gnViewportHeight = gnScreenHeight; - zoomflag = true; - int x = 0; - int y = 0; - CalcTileOffset(&x, &y); - EXPECT_EQ(x, 0); - EXPECT_EQ(y, 2); -} - -TEST(Scrool_rt, calc_tile_offset_853_480) -{ - gnScreenWidth = 853; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight; - zoomflag = true; - int x = 0; - int y = 0; - CalcTileOffset(&x, &y); - EXPECT_EQ(x, 21); - EXPECT_EQ(y, 0); -} - -TEST(Scrool_rt, calc_tile_offset_768_480_zoom) -{ - gnScreenWidth = 768; - gnScreenHeight = 480; - gnViewportHeight = gnScreenHeight; - zoomflag = false; - int x = 0; - int y = 0; - CalcTileOffset(&x, &y); - EXPECT_EQ(x, 0); - EXPECT_EQ(y, 8); -} - -// RowsCoveredByPanel - -TEST(Scrool_rt, calc_tiles_covered_by_panel_original) -{ - gnScreenWidth = 640; - zoomflag = true; - EXPECT_EQ(RowsCoveredByPanel(), 0); -} - -TEST(Scrool_rt, calc_tiles_covered_by_panel_960) -{ - gnScreenWidth = 960; - zoomflag = true; - EXPECT_EQ(RowsCoveredByPanel(), 4); -} - -TEST(Scrool_rt, calc_tiles_covered_by_panel_960_zoom) -{ - gnScreenWidth = 960; - zoomflag = false; - EXPECT_EQ(RowsCoveredByPanel(), 2); -} +#include + +#include "diablo.h" +#include "scrollrt.h" +#include "utils/ui_fwd.h" + +using namespace devilution; + +// TilesInView + +TEST(Scrool_rt, calc_tiles_in_view_original) +{ + gnScreenWidth = 640; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight - 128; + zoomflag = true; + int columns = 0; + int rows = 0; + TilesInView(&columns, &rows); + EXPECT_EQ(columns, 10); + EXPECT_EQ(rows, 11); +} + +TEST(Scrool_rt, calc_tiles_in_view_original_zoom) +{ + gnScreenWidth = 640; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight - 128; + zoomflag = false; + int columns = 0; + int rows = 0; + TilesInView(&columns, &rows); + EXPECT_EQ(columns, 5); + EXPECT_EQ(rows, 6); +} + +TEST(Scrool_rt, calc_tiles_in_view_960_540) +{ + gnScreenWidth = 960; + gnScreenHeight = 540; + gnViewportHeight = gnScreenHeight; + zoomflag = true; + int columns = 0; + int rows = 0; + TilesInView(&columns, &rows); + EXPECT_EQ(columns, 15); + EXPECT_EQ(rows, 17); +} + +TEST(Scrool_rt, calc_tiles_in_view_640_512) +{ + gnScreenWidth = 640; + gnScreenHeight = 512; + gnViewportHeight = gnScreenHeight - 128; + zoomflag = true; + int columns = 0; + int rows = 0; + TilesInView(&columns, &rows); + EXPECT_EQ(columns, 10); + EXPECT_EQ(rows, 12); +} + +TEST(Scrool_rt, calc_tiles_in_view_768_480_zoom) +{ + gnScreenWidth = 768; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight; + zoomflag = false; + int columns = 0; + int rows = 0; + TilesInView(&columns, &rows); + EXPECT_EQ(columns, 6); + EXPECT_EQ(rows, 8); +} + +// CalcTileOffset + +TEST(Scrool_rt, calc_tile_offset_original) +{ + gnScreenWidth = 640; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight - 128; + zoomflag = true; + int x = 0; + int y = 0; + CalcTileOffset(&x, &y); + EXPECT_EQ(x, 0); + EXPECT_EQ(y, 0); +} + +TEST(Scrool_rt, calc_tile_offset_original_zoom) +{ + gnScreenWidth = 640; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight - 128; + zoomflag = false; + int x = 0; + int y = 0; + CalcTileOffset(&x, &y); + EXPECT_EQ(x, 0); + EXPECT_EQ(y, 8); +} + +TEST(Scrool_rt, calc_tile_offset_960_540) +{ + gnScreenWidth = 960; + gnScreenHeight = 540; + gnViewportHeight = gnScreenHeight; + zoomflag = true; + int x = 0; + int y = 0; + CalcTileOffset(&x, &y); + EXPECT_EQ(x, 0); + EXPECT_EQ(y, 2); +} + +TEST(Scrool_rt, calc_tile_offset_853_480) +{ + gnScreenWidth = 853; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight; + zoomflag = true; + int x = 0; + int y = 0; + CalcTileOffset(&x, &y); + EXPECT_EQ(x, 21); + EXPECT_EQ(y, 0); +} + +TEST(Scrool_rt, calc_tile_offset_768_480_zoom) +{ + gnScreenWidth = 768; + gnScreenHeight = 480; + gnViewportHeight = gnScreenHeight; + zoomflag = false; + int x = 0; + int y = 0; + CalcTileOffset(&x, &y); + EXPECT_EQ(x, 0); + EXPECT_EQ(y, 8); +} + +// RowsCoveredByPanel + +TEST(Scrool_rt, calc_tiles_covered_by_panel_original) +{ + gnScreenWidth = 640; + zoomflag = true; + EXPECT_EQ(RowsCoveredByPanel(), 0); +} + +TEST(Scrool_rt, calc_tiles_covered_by_panel_960) +{ + gnScreenWidth = 960; + zoomflag = true; + EXPECT_EQ(RowsCoveredByPanel(), 4); +} + +TEST(Scrool_rt, calc_tiles_covered_by_panel_960_zoom) +{ + gnScreenWidth = 960; + zoomflag = false; + EXPECT_EQ(RowsCoveredByPanel(), 2); +} diff --git a/test/stores_test.cpp b/test/stores_test.cpp index fa588e6eb..dbc25b948 100644 --- a/test/stores_test.cpp +++ b/test/stores_test.cpp @@ -1,74 +1,74 @@ -#include - -#include "stores.h" - -using namespace devilution; - -namespace { - -TEST(Stores, AddStoreHoldRepair_magic) -{ - ItemStruct *item; - - item = &storehold[0]; - - item->_iMaxDur = 60; - item->_iDurability = item->_iMaxDur; - item->_iMagical = ITEM_QUALITY_MAGIC; - item->_iIdentified = true; - item->_ivalue = 2000; - item->_iIvalue = 19000; - - for (int i = 1; i < item->_iMaxDur; i++) { - item->_ivalue = 2000; - item->_iIvalue = 19000; - item->_iDurability = i; - storenumh = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); - EXPECT_EQ(95 * (item->_iMaxDur - i) / 2, item->_ivalue); - } - - item->_iDurability = 59; - storenumh = 0; - item->_ivalue = 500; - item->_iIvalue = 30; // To cheap to repair - AddStoreHoldRepair(item, 0); - EXPECT_EQ(0, storenumh); - EXPECT_EQ(30, item->_iIvalue); - EXPECT_EQ(500, item->_ivalue); -} - -TEST(Stores, AddStoreHoldRepair_normal) -{ - ItemStruct *item; - - item = &storehold[0]; - - item->_iMaxDur = 20; - item->_iDurability = item->_iMaxDur; - item->_iMagical = ITEM_QUALITY_NORMAL; - item->_iIdentified = true; - item->_ivalue = 2000; - item->_iIvalue = item->_ivalue; - - for (int i = 1; i < item->_iMaxDur; i++) { - item->_ivalue = 2000; - item->_iIvalue = item->_ivalue; - item->_iDurability = i; - storenumh = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); - EXPECT_EQ(50 * (item->_iMaxDur - i), item->_ivalue); - } - - item->_iDurability = 19; - storenumh = 0; - item->_ivalue = 10; // less then 1 per dur - item->_iIvalue = item->_ivalue; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); - EXPECT_EQ(1, item->_ivalue); - EXPECT_EQ(1, item->_iIvalue); -} -} // namespace +#include + +#include "stores.h" + +using namespace devilution; + +namespace { + +TEST(Stores, AddStoreHoldRepair_magic) +{ + ItemStruct *item; + + item = &storehold[0]; + + item->_iMaxDur = 60; + item->_iDurability = item->_iMaxDur; + item->_iMagical = ITEM_QUALITY_MAGIC; + item->_iIdentified = true; + item->_ivalue = 2000; + item->_iIvalue = 19000; + + for (int i = 1; i < item->_iMaxDur; i++) { + item->_ivalue = 2000; + item->_iIvalue = 19000; + item->_iDurability = i; + storenumh = 0; + AddStoreHoldRepair(item, 0); + EXPECT_EQ(1, storenumh); + EXPECT_EQ(95 * (item->_iMaxDur - i) / 2, item->_ivalue); + } + + item->_iDurability = 59; + storenumh = 0; + item->_ivalue = 500; + item->_iIvalue = 30; // To cheap to repair + AddStoreHoldRepair(item, 0); + EXPECT_EQ(0, storenumh); + EXPECT_EQ(30, item->_iIvalue); + EXPECT_EQ(500, item->_ivalue); +} + +TEST(Stores, AddStoreHoldRepair_normal) +{ + ItemStruct *item; + + item = &storehold[0]; + + item->_iMaxDur = 20; + item->_iDurability = item->_iMaxDur; + item->_iMagical = ITEM_QUALITY_NORMAL; + item->_iIdentified = true; + item->_ivalue = 2000; + item->_iIvalue = item->_ivalue; + + for (int i = 1; i < item->_iMaxDur; i++) { + item->_ivalue = 2000; + item->_iIvalue = item->_ivalue; + item->_iDurability = i; + storenumh = 0; + AddStoreHoldRepair(item, 0); + EXPECT_EQ(1, storenumh); + EXPECT_EQ(50 * (item->_iMaxDur - i), item->_ivalue); + } + + item->_iDurability = 19; + storenumh = 0; + item->_ivalue = 10; // less then 1 per dur + item->_iIvalue = item->_ivalue; + AddStoreHoldRepair(item, 0); + EXPECT_EQ(1, storenumh); + EXPECT_EQ(1, item->_ivalue); + EXPECT_EQ(1, item->_iIvalue); +} +} // namespace