diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 5385e5bb2..fb58bf831 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -159,6 +159,8 @@ set(devilutionx_assets lua/devilutionx/events.lua lua/inspect.lua lua/mods/clock/init.lua + "lua/mods/Floating Numbers - Damage/init.lua" + "lua/mods/Floating Numbers - XP/init.lua" lua/repl_prelude.lua plrgfx/warrior/whu/whufm.trn plrgfx/warrior/whu/whulm.trn diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index e69e80266..482e9f187 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -117,6 +117,7 @@ set(libdevilutionx_SRCS lua/modules/dev/quests.cpp lua/modules/dev/search.cpp lua/modules/dev/towners.cpp + lua/modules/floatingnumbers.cpp lua/modules/i18n.cpp lua/modules/items.cpp lua/modules/log.cpp @@ -653,6 +654,7 @@ target_link_dependencies(libdevilutionx_player DevilutionX::SDL fmt::fmt magic_enum::magic_enum + sol2::sol2 tl unordered_dense::unordered_dense libdevilutionx_game_mode diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 6e85670c0..fbbcd5f1b 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -1816,7 +1816,7 @@ void DrawAndBlit() const Rectangle &mainPanel = GetMainPanel(); - if (gnScreenWidth > mainPanel.size.width || IsRedrawEverything() || *GetOptions().Gameplay.enableFloatingNumbers != FloatingNumbers::Off) { + if (gnScreenWidth > mainPanel.size.width || IsRedrawEverything()) { drawHealth = true; drawMana = true; drawControlButtons = true; diff --git a/Source/lua/lua_global.cpp b/Source/lua/lua_global.cpp index 97043a31d..3453d1ac1 100644 --- a/Source/lua/lua_global.cpp +++ b/Source/lua/lua_global.cpp @@ -13,6 +13,7 @@ #include "effects.h" #include "engine/assets.hpp" #include "lua/modules/audio.hpp" +#include "lua/modules/floatingnumbers.hpp" #include "lua/modules/hellfire.hpp" #include "lua/modules/i18n.hpp" #include "lua/modules/items.hpp" @@ -21,7 +22,9 @@ #include "lua/modules/player.hpp" #include "lua/modules/render.hpp" #include "lua/modules/towners.hpp" +#include "monster.h" #include "options.h" +#include "player.h" #include "plrmsg.h" #include "utils/console.h" #include "utils/log.hpp" @@ -283,6 +286,7 @@ void LuaInitialize() "devilutionx.render", LuaRenderModule(lua), "devilutionx.towners", LuaTownersModule(lua), "devilutionx.hellfire", LuaHellfireModule(lua), + "devilutionx.floatingnumbers", LuaFloatingNumbersModule(lua), "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, // This package is loaded without a sandbox: "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); @@ -305,7 +309,8 @@ void LuaShutdown() CurrentLuaState = std::nullopt; } -void LuaEvent(std::string_view name) +template +void CallLuaEvent(std::string_view name, Args &&...args) { if (!CurrentLuaState.has_value()) { return; @@ -317,7 +322,27 @@ void LuaEvent(std::string_view name) return; } const sol::protected_function fn = trigger->as(); - SafeCallResult(fn(), /*optional=*/true); + SafeCallResult(fn(std::forward(args)...), /*optional=*/true); +} + +void LuaEvent(std::string_view name) +{ + CallLuaEvent(name); +} + +void LuaEvent(std::string_view name, const Player *player, int arg1, int arg2) +{ + CallLuaEvent(name, player, arg1, arg2); +} + +void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2) +{ + CallLuaEvent(name, monster, arg1, arg2); +} + +void LuaEvent(std::string_view name, const Player *player, uint32_t arg1) +{ + CallLuaEvent(name, player, arg1); } sol::state &GetLuaState() diff --git a/Source/lua/lua_global.hpp b/Source/lua/lua_global.hpp index 80fbb1211..252075335 100644 --- a/Source/lua/lua_global.hpp +++ b/Source/lua/lua_global.hpp @@ -8,10 +8,16 @@ namespace devilution { +struct Player; +struct Monster; + void LuaInitialize(); void LuaReloadActiveMods(); void LuaShutdown(); void LuaEvent(std::string_view name); +void LuaEvent(std::string_view name, const Player *player, int arg1, int arg2); +void LuaEvent(std::string_view name, const Monster *monster, int arg1, int arg2); +void LuaEvent(std::string_view name, const Player *player, uint32_t arg1); sol::state &GetLuaState(); sol::environment CreateLuaSandbox(); sol::object SafeCallResult(sol::protected_function_result result, bool optional); diff --git a/Source/lua/modules/floatingnumbers.cpp b/Source/lua/modules/floatingnumbers.cpp new file mode 100644 index 000000000..d4caa233b --- /dev/null +++ b/Source/lua/modules/floatingnumbers.cpp @@ -0,0 +1,51 @@ +#include "lua/modules/floatingnumbers.hpp" + +#include + +#ifdef USE_SDL3 +#include +#else +#include +#endif + +#include "DiabloUI/ui_flags.hpp" +#include "engine/point.hpp" +#include "lua/metadoc.hpp" +#include "qol/floatingnumbers.h" + +namespace devilution { + +sol::table LuaFloatingNumbersModule(sol::state_view &lua) +{ + sol::table table = lua.create_table(); + + LuaSetDocFn(table, "add", "(text: string, pos: Point, style: UiFlags, id: integer = 0, reverseDirection: boolean = false)", + "Add a floating number", + [](const std::string &text, Point pos, UiFlags style, std::optional id, std::optional reverseDirection) { + AddFloatingNumber(pos, { 0, 0 }, text, style, id.value_or(0), reverseDirection.value_or(false)); + }); + + LuaSetDocFn(table, "get_ticks", "() -> integer", "Returns the number of milliseconds since the game started.", + []() { return static_cast(SDL_GetTicks()); }); + + auto flags = lua.create_table(); + flags["DarkRed"] = UiFlags::ColorUiSilver; + flags["Yellow"] = UiFlags::ColorYellow; + flags["Gold"] = UiFlags::ColorGold; + flags["Black"] = UiFlags::ColorBlack; + flags["White"] = UiFlags::ColorWhite; + flags["WhiteGold"] = UiFlags::ColorWhitegold; + flags["Red"] = UiFlags::ColorRed; + flags["Blue"] = UiFlags::ColorBlue; + flags["Orange"] = UiFlags::ColorOrange; + + flags["Small"] = UiFlags::FontSize12; + flags["Medium"] = UiFlags::FontSize24; + flags["Large"] = UiFlags::FontSize30; + + table["Flags"] = flags; + + return table; +} + +} // namespace devilution diff --git a/Source/lua/modules/floatingnumbers.hpp b/Source/lua/modules/floatingnumbers.hpp new file mode 100644 index 000000000..008c3c3ee --- /dev/null +++ b/Source/lua/modules/floatingnumbers.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace devilution { + +sol::table LuaFloatingNumbersModule(sol::state_view &lua); + +} // namespace devilution diff --git a/Source/lua/modules/monsters.cpp b/Source/lua/modules/monsters.cpp index cc7b2f02e..f648d500d 100644 --- a/Source/lua/modules/monsters.cpp +++ b/Source/lua/modules/monsters.cpp @@ -6,7 +6,9 @@ #include #include "data/file.hpp" +#include "engine/point.hpp" #include "lua/metadoc.hpp" +#include "monster.h" #include "tables/monstdat.h" #include "utils/language.h" #include "utils/str_split.hpp" @@ -27,10 +29,26 @@ void AddUniqueMonsterDataFromTsv(const std::string_view path) LoadUniqueMonstDatFromFile(dataFile, path); } +void InitMonsterUserType(sol::state_view &lua) +{ + sol::usertype monsterType = lua.new_usertype(sol::no_constructor); + LuaSetDocReadonlyProperty(monsterType, "position", "Point", + "Monster's current position (readonly)", + [](const Monster &monster) { + return Point { monster.position.tile }; + }); + LuaSetDocReadonlyProperty(monsterType, "id", "integer", + "Monster's unique ID (readonly)", + [](const Monster &monster) { + return static_cast(reinterpret_cast(&monster)); + }); +} + } // namespace sol::table LuaMonstersModule(sol::state_view &lua) { + InitMonsterUserType(lua); sol::table table = lua.create_table(); LuaSetDocFn(table, "addMonsterDataFromTsv", "(path: string)", AddMonsterDataFromTsv); LuaSetDocFn(table, "addUniqueMonsterDataFromTsv", "(path: string)", AddUniqueMonsterDataFromTsv); diff --git a/Source/lua/modules/player.cpp b/Source/lua/modules/player.cpp index c9f8d44c6..58b3ea359 100644 --- a/Source/lua/modules/player.cpp +++ b/Source/lua/modules/player.cpp @@ -20,6 +20,16 @@ void InitPlayerUserType(sol::state_view &lua) LuaSetDocReadonlyProperty(playerType, "name", "string", "Player's name (readonly)", &Player::name); + LuaSetDocReadonlyProperty(playerType, "id", "integer", + "Player's unique ID (readonly)", + [](const Player &player) { + return static_cast(reinterpret_cast(&player)); + }); + LuaSetDocReadonlyProperty(playerType, "position", "Point", + "Player's current position (readonly)", + [](const Player &player) -> Point { + return Point { player.position.tile }; + }); LuaSetDocFn(playerType, "addExperience", "(experience: integer, monsterLevel: integer = nil)", "Adds experience to this player based on the current game mode", [](Player &player, uint32_t experience, std::optional monsterLevel) { diff --git a/Source/monster.cpp b/Source/monster.cpp index dbe869a64..f0f9e2ad6 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -69,6 +69,7 @@ #include "levels/tile_properties.hpp" #include "levels/trigs.h" #include "lighting.h" +#include "lua/lua_global.hpp" #include "minitext.h" #include "missiles.h" #include "movie.h" @@ -77,7 +78,6 @@ #include "objects.h" #include "options.h" #include "player.h" -#include "qol/floatingnumbers.h" #include "quests.h" #include "sound_effect_enums.h" #include "storm/storm_net.hpp" @@ -3778,7 +3778,7 @@ void AddDoppelganger(Monster &monster) void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage) { - AddFloatingNumber(damageType, monster, damage); + LuaEvent("OnMonsterTakeDamage", &monster, damage, static_cast(damageType)); monster.hitPoints -= damage; diff --git a/Source/options.cpp b/Source/options.cpp index 8f1940d33..86cc0f5b6 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -72,7 +72,7 @@ namespace { void DiscoverMods() { // Add mods available by default: - std::unordered_set modNames = { "clock" }; + std::unordered_set modNames = { "clock", "Floating Numbers - Damage", "Floating Numbers - XP" }; if (HaveHellfire()) { modNames.insert("Hellfire"); @@ -870,12 +870,6 @@ GameplayOptions::GameplayOptions() , numFullManaPotionPickup("Full Mana Potion Pickup", OptionEntryFlags::None, N_("Full Mana Potion Pickup"), N_("Number of Full Mana potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) , numRejuPotionPickup("Rejuvenation Potion Pickup", OptionEntryFlags::None, N_("Rejuvenation Potion Pickup"), N_("Number of Rejuvenation potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) , numFullRejuPotionPickup("Full Rejuvenation Potion Pickup", OptionEntryFlags::None, N_("Full Rejuvenation Potion Pickup"), N_("Number of Full Rejuvenation potions to pick up automatically."), 0, { 0, 1, 2, 4, 8, 16 }) - , enableFloatingNumbers("Enable floating numbers", OptionEntryFlags::None, N_("Enable floating numbers"), N_("Enables floating numbers on gaining XP / dealing damage etc."), FloatingNumbers::Off, - { - { FloatingNumbers::Off, N_("Off") }, - { FloatingNumbers::Random, N_("Random Angles") }, - { FloatingNumbers::Vertical, N_("Vertical Only") }, - }) , skipLoadingScreenThresholdMs("Skip loading screen threshold, ms", OptionEntryFlags::Invisible, "", "", 0) { } @@ -902,7 +896,6 @@ std::vector GameplayOptions::GetEntries() &floatingInfoBox, &showMonsterType, &showItemLabels, - &enableFloatingNumbers, &autoRefillBelt, &autoEquipWeapons, &autoEquipArmor, diff --git a/Source/options.h b/Source/options.h index 2b0b68a8c..3b8847054 100644 --- a/Source/options.h +++ b/Source/options.h @@ -92,15 +92,6 @@ enum class Resampler : uint8_t { std::string_view ResamplerToString(Resampler resampler); std::optional ResamplerFromString(std::string_view resampler); -enum class FloatingNumbers : uint8_t { - /** @brief Show no floating numbers. */ - Off = 0, - /** @brief Show floating numbers at random angles. */ - Random = 1, - /** @brief Show floating numbers vertically only. */ - Vertical = 2, -}; - enum class OptionEntryType : uint8_t { Boolean, List, @@ -644,8 +635,6 @@ struct GameplayOptions : OptionCategoryBase { OptionEntryInt numRejuPotionPickup; /** @brief Number of Full Rejuvenating potions to pick up automatically */ OptionEntryInt numFullRejuPotionPickup; - /** @brief Enable floating numbers. */ - OptionEntryEnum enableFloatingNumbers; /** * @brief If loading takes less than this value, skips displaying the loading screen. diff --git a/Source/player.cpp b/Source/player.cpp index 045070092..b0e9a7222 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -41,6 +41,7 @@ #include "levels/trigs.h" #include "lighting.h" #include "loadsave.h" +#include "lua/lua_global.hpp" #include "minitext.h" #include "missiles.h" #include "monster.h" @@ -49,7 +50,6 @@ #include "options.h" #include "player.h" #include "qol/autopickup.h" -#include "qol/floatingnumbers.h" #include "qol/stash.h" #include "spells.h" #include "stores.h" @@ -2442,6 +2442,8 @@ void Player::_addExperience(uint32_t experience, int levelDelta) clampedExp = std::min({ clampedExp, /* level 1-5: */ getNextExperienceThreshold() / 20U, /* level 6-50: */ 200U * getCharacterLevel() }); } + LuaEvent("OnPlayerGainExperience", this, clampedExp); + const uint32_t maxExperience = GetNextExperienceThresholdForLevel(getMaxCharacterLevel()); // ensure we only add enough experience to reach the max experience cap so we don't overflow @@ -2822,7 +2824,7 @@ void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP /* { int totalDamage = (dam << 6) + frac; if (&player == MyPlayer && !player.hasNoLife()) { - AddFloatingNumber(damageType, player, totalDamage); + LuaEvent("OnPlayerTakeDamage", &player, totalDamage, static_cast(damageType)); } if (totalDamage > 0 && player.pManaShield && HasNoneOf(player._pIFlags, ItemSpecialEffect::NoMana)) { const uint8_t manaShieldLevel = player._pSplLvl[static_cast(SpellID::ManaShield)]; diff --git a/Source/qol/floatingnumbers.cpp b/Source/qol/floatingnumbers.cpp index a6a93964d..0f32bebc9 100644 --- a/Source/qol/floatingnumbers.cpp +++ b/Source/qol/floatingnumbers.cpp @@ -28,9 +28,7 @@ struct FloatingNumber { uint32_t time; uint32_t lastMerge; UiFlags style; - DamageType type; - int value; - size_t index; + int id; bool reverseDirection; }; @@ -47,142 +45,45 @@ void ClearExpiredNumbers() } } -GameFontTables GetGameFontSizeByDamage(int value) +GameFontTables GetGameFontSize(UiFlags flags) { - value >>= 6; - if (value >= 300) + if (HasAnyOf(flags, UiFlags::FontSize30)) return GameFont30; - if (value >= 100) + if (HasAnyOf(flags, UiFlags::FontSize24)) 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; - } -} +} // namespace -void AddFloatingNumber(Point pos, Displacement offset, DamageType type, int value, size_t index, bool damageToPlayer) +void AddFloatingNumber(Point pos, Displacement offset, std::string text, UiFlags style, int id, bool reverseDirection) { - // 45 deg angles to avoid jitter caused by px alignment - const Displacement goodAngles[] = { - { 0, -140 }, - { 100, -100 }, - { -100, -100 }, - }; - Displacement endOffset; - if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Random) { - endOffset = goodAngles[rand() % 3]; - } else if (*GetOptions().Gameplay.enableFloatingNumbers == FloatingNumbers::Vertical) { - endOffset = goodAngles[0]; - } - - if (damageToPlayer) - endOffset = -endOffset; + if (!reverseDirection) + endOffset = { 0, -140 }; + else + endOffset = { 0, 140 }; for (auto &num : FloatingQueue) { - if (num.reverseDirection == damageToPlayer && num.type == type && num.index == index && (SDL_GetTicks() - static_cast(num.lastMerge)) <= 100) { - num.value += value; + if (id != 0 && num.id == id && (SDL_GetTicks() - static_cast(num.lastMerge)) <= 100) { + num.text = text; num.lastMerge = SDL_GetTicks(); - UpdateFloatingData(num); + num.style = style; + num.startPos = pos; return; } } FloatingNumber num { - pos, offset, endOffset, "", + pos, offset, endOffset, text, static_cast(SDL_GetTicks() + 2500), static_cast(SDL_GetTicks()), - UiFlags::Outlined, type, value, index, damageToPlayer + style | UiFlags::Outlined, id, reverseDirection }; - UpdateFloatingData(num); FloatingQueue.push_back(num); } -} // namespace - -void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage) -{ - if (*GetOptions().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 (*GetOptions().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 (*GetOptions().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; @@ -193,7 +94,7 @@ void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement of Point screenPosition { worldOffset.deltaX, worldOffset.deltaY }; - const int lineWidth = GetLineWidth(floatingNum.text, GetGameFontSizeByDamage(floatingNum.value)); + const int lineWidth = GetLineWidth(floatingNum.text, GetGameFontSize(floatingNum.style)); screenPosition.x -= lineWidth / 2; const uint32_t timeLeft = floatingNum.time - SDL_GetTicks(); const float mul = 1 - (timeLeft / 2500.0f); diff --git a/Source/qol/floatingnumbers.h b/Source/qol/floatingnumbers.h index dff1f56a5..696cacedc 100644 --- a/Source/qol/floatingnumbers.h +++ b/Source/qol/floatingnumbers.h @@ -5,15 +5,16 @@ */ #pragma once +#include + +#include "DiabloUI/ui_flags.hpp" +#include "engine/displacement.hpp" #include "engine/point.hpp" -#include "monster.h" -#include "player.h" -#include "tables/misdat.h" +#include "engine/surface.hpp" namespace devilution { -void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage); -void AddFloatingNumber(DamageType damageType, const Player &player, int damage); +void AddFloatingNumber(Point pos, Displacement offset, std::string text, UiFlags style, int id = 0, bool reverseDirection = false); void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement offset); void ClearFloatingNumbers(); diff --git a/assets/lua/devilutionx/events.lua b/assets/lua/devilutionx/events.lua index 5f2a15723..de3f95331 100644 --- a/assets/lua/devilutionx/events.lua +++ b/assets/lua/devilutionx/events.lua @@ -25,9 +25,10 @@ local function CreateEvent() ---The arguments are forwarded to handlers. ---@param ... any trigger = function(...) - if arg ~= nil then + local args = {...} + if #args > 0 then for _, func in ipairs(functions) do - func(table.unpack(arg)) + func(table.unpack(args)) end else for _, func in ipairs(functions) do @@ -67,6 +68,18 @@ local events = { ---Called every frame at the end. GameDrawComplete = CreateEvent(), __doc_GameDrawComplete = "Called every frame at the end.", + + ---Called when a Monster takes damage. + OnMonsterTakeDamage = CreateEvent(), + __doc_OnMonsterTakeDamage = "Called when a Monster takes damage.", + + ---Called when Player takes damage. + OnPlayerTakeDamage = CreateEvent(), + __doc_OnPlayerTakeDamage = "Called when Player takes damage.", + + ---Called when Player gains experience. + OnPlayerGainExperience = CreateEvent(), + __doc_OnPlayerGainExperience = "Called when Player gains experience.", } ---Registers a custom event type with the given name. diff --git a/assets/lua/mods/Floating Numbers - Damage/init.lua b/assets/lua/mods/Floating Numbers - Damage/init.lua new file mode 100644 index 000000000..fe02b3f13 --- /dev/null +++ b/assets/lua/mods/Floating Numbers - Damage/init.lua @@ -0,0 +1,88 @@ +local floatingnumbers = require("devilutionx.floatingnumbers") +local events = require("devilutionx.events") +local player = require("devilutionx.player") + +local DAMAGE_TYPE = { + PHYSICAL = 0, + FIRE = 1, + LIGHTNING = 2, + MAGIC = 3, + ACID = 4, +} + +local function get_damage_style(damage_val, damage_type) + local style = 0 + + local v = damage_val + if v >= 64 * 300 then + style = style | floatingnumbers.Flags.Large + elseif v >= 64 * 100 then + style = style | floatingnumbers.Flags.Medium + else + style = style | floatingnumbers.Flags.Small + end + + local damage_type_styles = { + [DAMAGE_TYPE.PHYSICAL] = floatingnumbers.Flags.Gold, + [DAMAGE_TYPE.FIRE] = floatingnumbers.Flags.DarkRed, + [DAMAGE_TYPE.LIGHTNING] = floatingnumbers.Flags.Blue, + [DAMAGE_TYPE.MAGIC] = floatingnumbers.Flags.Orange, + [DAMAGE_TYPE.ACID] = floatingnumbers.Flags.Yellow, + } + + local type_style = damage_type_styles[damage_type] + if type_style then + style = style | type_style + end + + return style +end + +local function format_damage(damage_val) + if damage_val > 0 and damage_val < 64 then + return string.format("%.2f", damage_val / 64.0) + else + return tostring(math.floor(damage_val / 64)) + end +end + +local accumulated_damage = {} +local MERGE_WINDOW_MS = 100 + +events.OnMonsterTakeDamage.add(function(monster, damage, damage_type) + local id = monster.id + local now = floatingnumbers.get_ticks() + + local entry = accumulated_damage[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.damage = entry.damage + damage + else + entry = { damage = damage, time = now } + accumulated_damage[id] = entry + end + entry.time = now + + local text = format_damage(entry.damage) + local style = get_damage_style(entry.damage, damage_type) + floatingnumbers.add(text, monster.position, style, id, false) +end) + +events.OnPlayerTakeDamage.add(function(_player, damage, damage_type) + if _player == player.self() then + local id = _player.id + local now = floatingnumbers.get_ticks() + + local entry = accumulated_damage[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.damage = entry.damage + damage + else + entry = { damage = damage, time = now } + accumulated_damage[id] = entry + end + entry.time = now + + local text = format_damage(entry.damage) + local style = get_damage_style(entry.damage, damage_type) + floatingnumbers.add(text, _player.position, style, id, true) + end +end) diff --git a/assets/lua/mods/Floating Numbers - XP/init.lua b/assets/lua/mods/Floating Numbers - XP/init.lua new file mode 100644 index 000000000..b9777ef92 --- /dev/null +++ b/assets/lua/mods/Floating Numbers - XP/init.lua @@ -0,0 +1,25 @@ +local floatingnumbers = require("devilutionx.floatingnumbers") +local events = require("devilutionx.events") +local player = require("devilutionx.player") + +local accumulated_xp = {} +local MERGE_WINDOW_MS = 100 + +events.OnPlayerGainExperience.add(function(_player, experience) + if _player == player.self() then + local id = _player.id + local now = floatingnumbers.get_ticks() + + local entry = accumulated_xp[id] + if entry and (now - entry.time) < MERGE_WINDOW_MS then + entry.experience = entry.experience + experience + else + entry = { experience = experience, time = now } + accumulated_xp[id] = entry + end + entry.time = now + + local text = tostring(entry.experience) .. " XP" + floatingnumbers.add(text, _player.position, floatingnumbers.Flags.White, id, true) + end +end)