FixedPoint: Implement comparison with int

This commit is contained in:
Gleb Mazovetskiy
2026-10-11 21:55:08 +01:00
parent c361e764cc
commit 7fdf19da3a
14 changed files with 121 additions and 25 deletions
+2 -2
View File
@@ -543,7 +543,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
}
}
// Disallow player from accessing escape menu during the frames before the death message appears
if (vkey == SDLK_ESCAPE && MyPlayer->_pHitPoints > Fixed26_6::fromInt(0)) {
if (vkey == SDLK_ESCAPE && MyPlayer->_pHitPoints > 0) {
if (!PressEscKey()) {
LastPlayerAction = PlayerActionType::None;
gamemenu_on();
@@ -3188,7 +3188,7 @@ void LoadGameLevelSyncPlayerEntry(lvl_entry lvldir)
{
for (Player &player : Players) {
if (player.plractive && player.isOnActiveLevel() && (!player._pLvlChanging || &player == MyPlayer)) {
if (player._pHitPoints > Fixed26_6::fromInt(0)) {
if (player._pHitPoints > 0) {
if (lvldir != ENTRY_LOAD)
SyncInitPlrPos(player);
} else {
+1 -1
View File
@@ -62,7 +62,7 @@ std::string DebugCmdChangeHealth(int change)
Fixed26_6 newHealth = myPlayer._pHitPoints + Fixed26_6::fromInt(change);
SetPlayerHitPoints(myPlayer, newHealth);
if (newHealth <= Fixed26_6::fromInt(0))
if (newHealth <= 0)
SyncPlrKill(myPlayer, DeathReason::MonsterOrTrap);
return StrCat("Changed life by ", change);
+2 -2
View File
@@ -3688,7 +3688,7 @@ void ProcessStoneCurse(Missile &missile)
{
missile.duration--;
Monster &monster = Monsters[missile.var2];
if (monster.hitPoints == Fixed26_6::fromInt(0) && missile._miAnimType != MissileGraphicID::StoneCurseShatter) {
if (monster.hitPoints == 0 && missile._miAnimType != MissileGraphicID::StoneCurseShatter) {
missile.setDefaultFrameGroup();
missile._miDrawFlag = true;
missile.setAnimation(MissileGraphicID::StoneCurseShatter);
@@ -3701,7 +3701,7 @@ void ProcessStoneCurse(Missile &missile)
if (missile.duration == 0) {
missile._miDelFlag = true;
if (monster.hitPoints > Fixed26_6::fromInt(0)) {
if (monster.hitPoints > 0) {
monster.mode = static_cast<MonsterMode>(missile.var1);
monster.animInfo.isPetrified = false;
} else {
+5 -5
View File
@@ -1562,7 +1562,7 @@ bool MonsterDelay(Monster &monster)
void MonsterPetrified(Monster &monster)
{
if (monster.hitPoints <= Fixed26_6::fromInt(0)) {
if (monster.hitPoints <= 0) {
dMonster[monster.position.tile.x][monster.position.tile.y] = 0;
MarkWalkabilityChanged();
monster.isInvalid = true;
@@ -4060,7 +4060,7 @@ void M_StartKill(Monster &monster, const Player &player)
void M_SyncStartKill(Monster &monster, Point position, const Player &player)
{
if (monster.hitPoints == Fixed26_6::fromInt(0) || monster.mode == MonsterMode::Death) {
if (monster.hitPoints == 0 || monster.mode == MonsterMode::Death) {
return;
}
@@ -4682,7 +4682,7 @@ Monster *FindGolemForPlayer(const Player &player)
continue;
if (monster.goalVar3 != player.getId())
continue;
if (monster.hitPoints == Fixed26_6::fromInt(0))
if (monster.hitPoints == 0)
continue;
return &monster;
}
@@ -4820,14 +4820,14 @@ void SpawnGolem(const Player &player, Point position, uint8_t spellLevel)
// 1. Prefer MonsterIndex = PlayerIndex for vanilla compatibility
if (player.getId() < ReservedMonsterSlotsForGolems) {
Monster &reservedGolem = Monsters[player.getId()];
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == Fixed26_6::fromInt(0))
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0)
golem = &reservedGolem;
}
// 2. Use reserved slots, so additional Monsters can spawn
if (golem == nullptr) {
for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) {
Monster &reservedGolem = Monsters[i];
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == Fixed26_6::fromInt(0)) {
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0) {
golem = &reservedGolem;
break;
}
+1 -1
View File
@@ -18,7 +18,7 @@ namespace {
bool IsEnemyValid(size_t enemyId, bool checkMonsterTable)
{
if (enemyId < MaxMonsters)
return !checkMonsterTable || Monsters[enemyId].hitPoints > Fixed26_6::fromInt(0);
return !checkMonsterTable || Monsters[enemyId].hitPoints > 0;
const size_t playerId = enemyId - MaxMonsters;
return playerId < Players.size() && Players[playerId].plractive;
}
+3 -3
View File
@@ -1027,7 +1027,7 @@ void DeltaLeaveSync(uint8_t bLevel)
for (size_t i = 0; i < ActiveMonsterCount; i++) {
const unsigned ma = ActiveMonsters[i];
Monster &monster = Monsters[ma];
if (monster.hitPoints == Fixed26_6::fromInt(0))
if (monster.hitPoints == 0)
continue;
DMonsterStr &delta = deltaLevel.monster[ma];
delta.position = monster.position.tile;
@@ -1930,7 +1930,7 @@ size_t OnResurrect(const TCmdParam1 &message, Player &caster)
SpawnResurrectBeam(caster, target);
if (&target == MyPlayer && target._pHitPoints <= Fixed26_6::fromInt(0)) {
if (&target == MyPlayer && target._pHitPoints <= 0) {
NetSendCmd(true, CMD_PLRALIVE);
}
@@ -2053,7 +2053,7 @@ size_t OnMonstDamage(const TCmdMonDamage &message, Player &player)
if (player.isOnActiveLevel() && leveltype != DTYPE_TOWN && monsterIdx < MaxMonsters) {
Monster &monster = Monsters[monsterIdx];
monster.tag(player);
if (monster.hitPoints > Fixed26_6::fromInt(0)) {
if (monster.hitPoints > 0) {
monster.hitPoints -= Fixed26_6::fromRaw(Swap32LE(message.dwDam));
if (monster.hitPoints.whole() < 1)
monster.hitPoints = Fixed26_6::fromInt(1);
+1 -1
View File
@@ -3145,7 +3145,7 @@ void OperateBookcase(Object &bookcase, bool sendmsg, bool sendLootMsg)
if (zhar.mode == MonsterMode::Stand // prevents playing the "angry" message for the second time if zhar got aggroed by losing vision and talking again
&& zhar.uniqueType == UniqueMonsterType::Zhar
&& zhar.activeForTicks == UINT8_MAX
&& zhar.hitPoints > Fixed26_6::fromInt(0)) {
&& zhar.hitPoints > 0) {
zhar.talkMsg = TEXT_ZHAR2;
M_StartStand(zhar, zhar.direction); // BUGFIX: first parameter in call to M_StartStand should be MAX_PLRS, not 0. (fixed)
zhar.goal = MonsterGoal::Attack;
+3 -3
View File
@@ -185,7 +185,7 @@ void DrawPartyMemberInfoPanel(const Surface &out)
const PlayerSpriteData &spriteData = GetPlayerSpriteDataForClass(player._pClass);
Point offset = (player.isOnLevel(0)) ? spriteData.partyOffsetTown : spriteData.partyOffsetDungeon;
if (player._pHitPoints <= Fixed26_6::fromInt(0) && IsPlayerUnarmed(player))
if (player._pHitPoints <= 0 && IsPlayerUnarmed(player))
offset = spriteData.partyOffsetDead;
// Calculate the players portait position
@@ -218,7 +218,7 @@ void DrawPartyMemberInfoPanel(const Surface &out)
}
// Check to see if the player is dead and if so we draw a half transparent red rect over the portrait
if (player._pHitPoints <= Fixed26_6::fromInt(0)) {
if (player._pHitPoints <= 0) {
DrawHalfTransparentRectTo(
frameSubregion,
0, 0,
@@ -230,7 +230,7 @@ void DrawPartyMemberInfoPanel(const Surface &out)
// Add to the position before continuing to the next item
pos.y += PortraitFrameSize.height;
if (player._pMaxMana > Fixed26_6::fromInt(0)) {
if (player._pMaxMana > 0) {
// Get the players remaining mana
const int manaTicks = ((player._pMana.raw() * PortraitFrameSize.width) + (player._pMaxMana.raw() / 2)) / player._pMaxMana.raw();
const uint8_t manaBarColor = PAL8_BLUE + 3;
+3 -3
View File
@@ -2822,7 +2822,7 @@ void ApplyPlrDamage(DamageType damageType, Player &player, Fixed26_6 dam, int mi
if (&player == MyPlayer && !player.hasNoLife()) {
lua::OnPlayerTakeDamage(&player, totalDamage.raw(), static_cast<int>(damageType));
}
if (totalDamage > Fixed26_6::fromInt(0) && player.pManaShield && HasNoneOf(player._pIFlags, ItemSpecialEffect::NoMana)) {
if (totalDamage > 0 && player.pManaShield && HasNoneOf(player._pIFlags, ItemSpecialEffect::NoMana)) {
const uint8_t manaShieldLevel = player._pSplLvl[static_cast<int8_t>(SpellID::ManaShield)];
if (manaShieldLevel > 0) {
totalDamage += totalDamage / -player.GetManaShieldDamageReduction();
@@ -2845,7 +2845,7 @@ void ApplyPlrDamage(DamageType damageType, Player &player, Fixed26_6 dam, int mi
}
}
if (totalDamage == Fixed26_6::fromInt(0))
if (totalDamage == 0)
return;
RedrawComponent(PanelDrawComponent::Health);
@@ -3459,7 +3459,7 @@ void PlayDungMsgs()
} else if (!setlevel && currlevel == 16 && !myPlayer._pLvlVisited[16] && (myPlayer.pDungMsgs & DungMsgDiablo) == 0) {
for (auto &monster : Monsters) {
if (monster.type().type != MT_DIABLO) continue;
if (monster.hitPoints > Fixed26_6::fromInt(0)) {
if (monster.hitPoints > 0) {
sfxdelay = 40;
sfxdnum = SfxID::DiabloGreeting;
myPlayer.pDungMsgs |= DungMsgDiablo;
+2 -2
View File
@@ -682,7 +682,7 @@ public:
*/
int UpdateHitPointPercentage()
{
if (_pMaxHP <= Fixed26_6::fromInt(0)) { // divide by zero guard
if (_pMaxHP <= 0) { // divide by zero guard
_pHPPer = 0;
} else {
// Maximum achievable HP is approximately 1200. Diablo uses fixed point integers where the last 6 bits are
@@ -696,7 +696,7 @@ public:
int UpdateManaPercentage()
{
if (_pMaxMana <= Fixed26_6::fromInt(0)) {
if (_pMaxMana <= 0) {
_pManaPer = 0;
} else {
_pManaPer = std::clamp<int>(_pMana.raw() * 81 / _pMaxMana.raw(), 0, 81);
+1 -1
View File
@@ -100,7 +100,7 @@ void DrawMonsterHealthBar(const Surface &out)
if (monster.hitPoints > monster.maxHitPoints) {
multiplier = monster.hitPoints.raw() / monster.maxHitPoints.raw();
currLife = monster.hitPoints - monster.maxHitPoints * multiplier;
if (currLife == Fixed26_6::fromInt(0) && multiplier > 0) {
if (currLife == 0 && multiplier > 0) {
multiplier--;
currLife = monster.maxHitPoints;
}
+1 -1
View File
@@ -157,7 +157,7 @@ void SyncPlrInv(TSyncHeader *pHdr)
void SyncMonster(bool isOwner, const TSyncMonster &monsterSync)
{
Monster &monster = Monsters[monsterSync._mndx];
if (monster.hitPoints <= Fixed26_6::fromInt(0) || monster.mode == MonsterMode::Death) {
if (monster.hitPoints <= 0 || monster.mode == MonsterMode::Death) {
return;
}
+75
View File
@@ -204,6 +204,81 @@ template <typename StorageT, unsigned FractionalBits>
return FixedPoint<decltype(raw), FractionalBits>::fromRaw(raw);
}
/**
* @brief Compares against a plain `int`, treated as a whole number of units (as if by `fromInt`).
*/
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator==(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return a == FixedPoint<StorageT, FractionalBits>::fromInt(whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator==(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return a == whole;
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator!=(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return !(a == whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator!=(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return !(a == whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator<(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return a < FixedPoint<StorageT, FractionalBits>::fromInt(whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator<(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return a > whole;
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator<=(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return a <= FixedPoint<StorageT, FractionalBits>::fromInt(whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator<=(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return a >= whole;
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator>(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return a > FixedPoint<StorageT, FractionalBits>::fromInt(whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator>(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return a < whole;
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator>=(FixedPoint<StorageT, FractionalBits> a, int whole)
{
return a >= FixedPoint<StorageT, FractionalBits>::fromInt(whole);
}
template <typename StorageT, unsigned FractionalBits>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool operator>=(int whole, FixedPoint<StorageT, FractionalBits> a)
{
return a <= whole;
}
using Fixed10_6 = FixedPoint<int16_t, 6>;
using Fixed26_6 = FixedPoint<int32_t, 6>;
+21
View File
@@ -146,5 +146,26 @@ TEST(FixedPointTest, CompoundAssignmentNarrowsBackToTheLeftHandSideType)
EXPECT_EQ(value, Fixed10_6::fromInt(3));
}
TEST(FixedPointTest, ComparisonWithIntTreatsItAsWholeUnits)
{
const Fixed10_6 value = Fixed10_6::fromInt(3);
EXPECT_TRUE(value == 3);
EXPECT_TRUE(3 == value);
EXPECT_TRUE(value != 4);
EXPECT_TRUE(4 != value);
EXPECT_TRUE(value < 4);
EXPECT_TRUE(2 < value);
EXPECT_TRUE(value <= 3);
EXPECT_TRUE(3 <= value);
EXPECT_TRUE(value > 2);
EXPECT_TRUE(4 > value);
EXPECT_TRUE(value >= 3);
EXPECT_TRUE(3 >= value);
const Fixed10_6 fractional = Fixed10_6::fromRaw((3 << 6) + 1);
EXPECT_FALSE(fractional == 3); // has a fractional part, so it's not exactly 3
EXPECT_TRUE(fractional > 3);
}
} // namespace
} // namespace devilution