#include "floatingnumbers.h" #include #include #include #include #include #include "engine/render/text_render.hpp" #include "options.h" #include "utils/str_cat.hpp" namespace devilution { namespace { struct FloatingNumber { Point startPos; Displacement startOffset; Displacement endOffset; std::string text; uint32_t time; uint32_t lastMerge; UiFlags style; DamageType type; int value; int index; bool reverseDirection; }; std::deque FloatingQueue; void ClearExpiredNumbers() { while (!FloatingQueue.empty()) { FloatingNumber &num = FloatingQueue.front(); if (num.time > SDL_GetTicks()) break; FloatingQueue.pop_front(); } } GameFontTables GetGameFontSizeByDamage(int value) { value >>= 6; if (value >= 300) return GameFont30; if (value >= 100) return GameFont24; return GameFont12; } UiFlags GetFontSizeByDamage(int value) { value >>= 6; if (value >= 300) return UiFlags::FontSize30; if (value >= 100) return UiFlags::FontSize24; return UiFlags::FontSize12; } void UpdateFloatingData(FloatingNumber &num) { if (num.value > 0 && num.value < 64) { num.text = fmt::format("{:.2f}", num.value / 64.0); } else { num.text = StrCat(num.value >> 6); } num.style &= ~(UiFlags::FontSize12 | UiFlags::FontSize24 | UiFlags::FontSize30); num.style |= GetFontSizeByDamage(num.value); switch (num.type) { case DamageType::Physical: num.style |= UiFlags::ColorGold; break; case DamageType::Fire: num.style |= UiFlags::ColorUiSilver; // UiSilver appears dark red ingame break; case DamageType::Lightning: num.style |= UiFlags::ColorBlue; break; case DamageType::Magic: num.style |= UiFlags::ColorOrange; break; case DamageType::Acid: num.style |= UiFlags::ColorYellow; break; } } void AddFloatingNumber(Point pos, Displacement offset, DamageType type, int value, int index, bool damageToPlayer) { // 45 deg angles to avoid jitter caused by px alignment Displacement goodAngles[] = { { 0, -140 }, { 100, -100 }, { -100, -100 }, }; Displacement endOffset; if (*sgOptions.Gameplay.enableFloatingNumbers == FloatingNumbers::Random) { endOffset = goodAngles[rand() % 3]; } else if (*sgOptions.Gameplay.enableFloatingNumbers == FloatingNumbers::Vertical) { endOffset = goodAngles[0]; } if (damageToPlayer) endOffset = -endOffset; for (auto &num : FloatingQueue) { if (num.reverseDirection == damageToPlayer && num.type == type && num.index == index && (SDL_GetTicks() - static_cast(num.lastMerge)) <= 100) { num.value += value; num.lastMerge = SDL_GetTicks(); UpdateFloatingData(num); return; } } FloatingNumber num { pos, offset, endOffset, "", SDL_GetTicks() + 2500, SDL_GetTicks(), UiFlags::Outlined, type, value, index, damageToPlayer }; UpdateFloatingData(num); FloatingQueue.push_back(num); } } // namespace void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage) { if (*sgOptions.Gameplay.enableFloatingNumbers == FloatingNumbers::Off) return; Displacement offset = {}; if (monster.isWalking()) { offset = GetOffsetForWalking(monster.animInfo, monster.direction); if (monster.mode == MonsterMode::MoveSideways) { if (monster.direction == Direction::West) offset -= Displacement { 64, 0 }; else offset += Displacement { 64, 0 }; } } if (monster.animInfo.sprites) { const ClxSprite sprite = monster.animInfo.currentSprite(); offset.deltaY -= sprite.height() / 2; } AddFloatingNumber(monster.position.tile, offset, damageType, damage, monster.getId(), false); } void AddFloatingNumber(DamageType damageType, const Player &player, int damage) { if (*sgOptions.Gameplay.enableFloatingNumbers == FloatingNumbers::Off) return; Displacement offset = {}; if (player.isWalking()) { offset = GetOffsetForWalking(player.AnimInfo, player._pdir); if (player._pmode == PM_WALK_SIDEWAYS) { if (player._pdir == Direction::West) offset -= Displacement { 64, 0 }; else offset += Displacement { 64, 0 }; } } AddFloatingNumber(player.position.tile, offset, damageType, damage, player.getId(), true); } void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement offset) { if (*sgOptions.Gameplay.enableFloatingNumbers == FloatingNumbers::Off) return; for (auto &floatingNum : FloatingQueue) { Displacement worldOffset = viewPosition - floatingNum.startPos; worldOffset = worldOffset.worldToScreen() + offset + Displacement { TILE_WIDTH / 2, -TILE_HEIGHT / 2 } + floatingNum.startOffset; if (*sgOptions.Graphics.zoom) { worldOffset *= 2; } Point screenPosition { worldOffset.deltaX, worldOffset.deltaY }; int lineWidth = GetLineWidth(floatingNum.text, GetGameFontSizeByDamage(floatingNum.value)); screenPosition.x -= lineWidth / 2; uint32_t timeLeft = floatingNum.time - SDL_GetTicks(); float mul = 1 - (timeLeft / 2500.0f); screenPosition += floatingNum.endOffset * mul; DrawString(out, floatingNum.text, Rectangle { screenPosition, { lineWidth, 0 } }, floatingNum.style); } ClearExpiredNumbers(); } void ClearFloatingNumbers() { srand(time(nullptr)); FloatingQueue.clear(); } } // namespace devilution