Browse Source

Floating numbers (#5639)

Co-authored-by: qndel <stefan551@o2.pl>
Co-authored-by: Stephen C. Wills <staphen@gmail.com>
pull/5698/head
Anders Jenbo 3 years ago committed by GitHub
parent
commit
c99f7cf644
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CMake/Assets.cmake
  2. BIN
      Packaging/resources/assets/fonts/orange.trn
  3. BIN
      Packaging/resources/assets/fonts/yellow.trn
  4. 1
      Source/CMakeLists.txt
  5. 2
      Source/DiabloUI/dialogs.cpp
  6. 27
      Source/DiabloUI/ui_flags.hpp
  7. 2
      Source/diablo.cpp
  8. 125
      Source/engine/render/scrollrt.cpp
  9. 13
      Source/engine/render/text_render.cpp
  10. 3
      Source/engine/render/text_render.hpp
  11. 2
      Source/gamemenu.cpp
  12. 73
      Source/missiles.cpp
  13. 13
      Source/monster.cpp
  14. 3
      Source/monster.h
  15. 5
      Source/msg.cpp
  16. 3
      Source/msg.h
  17. 2
      Source/objects.cpp
  18. 2
      Source/options.cpp
  19. 2
      Source/options.h
  20. 14
      Source/player.cpp
  21. 2
      Source/player.h
  22. 5
      Source/portal.cpp
  23. 198
      Source/qol/floatingnumbers.cpp
  24. 20
      Source/qol/floatingnumbers.h
  25. 4
      Source/spells.cpp

3
CMake/Assets.cmake

@ -157,10 +157,11 @@ set(devilutionx_assets
fonts/goldui.trn
fonts/grayuis.trn
fonts/grayui.trn
fonts/orange.trn
fonts/red.trn
fonts/whitegold.trn
fonts/white.trn
fonts/yellowdialog.trn
fonts/yellow.trn
gendata/cutportlw.clx
gendata/cutportrw.clx
gendata/cutstartw.clx

BIN
Packaging/resources/assets/fonts/yellowdialog.trn → Packaging/resources/assets/fonts/orange.trn

Binary file not shown.

BIN
Packaging/resources/assets/fonts/yellow.trn

Binary file not shown.

1
Source/CMakeLists.txt

@ -140,6 +140,7 @@ set(libdevilutionx_SRCS
qol/autopickup.cpp
qol/chatlog.cpp
qol/floatingnumbers.cpp
qol/itemlabels.cpp
qol/monhealthbar.cpp
qol/stash.cpp

2
Source/DiabloUI/dialogs.cpp

@ -86,7 +86,7 @@ bool Init(string_view caption, string_view text, bool error, bool renderBehind)
vecOkDialog.push_back(std::make_unique<UiImageClx>(*dialogSprite, rect1));
SDL_Rect rect2 = MakeSdlRect(uiPosition.x + 147, uiPosition.y + 110, textWidth, 20);
vecOkDialog.push_back(std::make_unique<UiText>(caption, rect2, UiFlags::AlignCenter | UiFlags::ColorDialogYellow));
vecOkDialog.push_back(std::make_unique<UiText>(caption, rect2, UiFlags::AlignCenter | UiFlags::ColorYellow));
SDL_Rect rect3 = MakeSdlRect(uiPosition.x + 147, uiPosition.y + 141, textWidth, 190);
vecOkDialog.push_back(std::make_unique<UiText>(wrappedText, rect3, UiFlags::AlignCenter | UiFlags::ColorDialogWhite));

27
Source/DiabloUI/ui_flags.hpp

@ -22,31 +22,32 @@ enum class UiFlags : uint32_t {
ColorUiGoldDark = 1 << 8,
ColorUiSilverDark = 1 << 9,
ColorDialogWhite = 1 << 10,
ColorDialogYellow = 1 << 11,
ColorYellow = 1 << 11,
ColorGold = 1 << 12,
ColorBlack = 1 << 13,
ColorWhite = 1 << 14,
ColorWhitegold = 1 << 15,
ColorRed = 1 << 16,
ColorBlue = 1 << 17,
ColorButtonface = 1 << 18,
ColorButtonpushed = 1 << 19,
ColorOrange = 1 << 18,
ColorButtonface = 1 << 19,
ColorButtonpushed = 1 << 20,
AlignCenter = 1 << 20,
AlignRight = 1 << 21,
VerticalCenter = 1 << 22,
AlignCenter = 1 << 21,
AlignRight = 1 << 22,
VerticalCenter = 1 << 23,
KerningFitSpacing = 1 << 23,
KerningFitSpacing = 1 << 24,
ElementDisabled = 1 << 24,
ElementHidden = 1 << 25,
ElementDisabled = 1 << 25,
ElementHidden = 1 << 26,
PentaCursor = 1 << 26,
TextCursor = 1 << 27,
Outlined = 1 << 28,
PentaCursor = 1 << 27,
TextCursor = 1 << 28,
Outlined = 1 << 29,
/** @brief Ensures that the if current element is active that the next element is also visible. */
NeedsNextElement = 1 << 29,
NeedsNextElement = 1 << 30,
// clang-format on
};
use_enum_as_flags(UiFlags);

2
Source/diablo.cpp

@ -64,6 +64,7 @@
#include "pfile.h"
#include "plrmsg.h"
#include "qol/chatlog.h"
#include "qol/floatingnumbers.h"
#include "qol/itemlabels.h"
#include "qol/monhealthbar.h"
#include "qol/stash.h"
@ -2656,6 +2657,7 @@ void DisableInputEventHandler(const SDL_Event &event, uint16_t modState)
void LoadGameLevel(bool firstflag, lvl_entry lvldir)
{
_music_id neededTrack = GetLevelMusic(leveltype);
ClearFloatingNumbers();
if (neededTrack != sgnMusicTrack)
music_stop();

125
Source/engine/render/scrollrt.cpp

@ -34,6 +34,7 @@
#include "panels/charpanel.hpp"
#include "plrmsg.h"
#include "qol/chatlog.h"
#include "qol/floatingnumbers.h"
#include "qol/itemlabels.h"
#include "qol/monhealthbar.h"
#include "qol/stash.h"
@ -984,101 +985,94 @@ Displacement tileShift;
int tileColums;
int tileRows;
void CalcFirstTilePosition(Point &position, Displacement &offset)
{
// Adjust by player offset and tile grid alignment
Player &myPlayer = *MyPlayer;
offset = tileOffset;
if (myPlayer.isWalking())
offset += GetOffsetForWalking(myPlayer.AnimInfo, myPlayer._pdir, true);
position += tileShift;
// Skip rendering parts covered by the panels
if (CanPanelsCoverView() && (IsLeftPanelOpen() || IsRightPanelOpen())) {
int multiplier = (*sgOptions.Graphics.zoom) ? 1 : 2;
position += Displacement(Direction::East) * multiplier;
offset.deltaX += -TILE_WIDTH * multiplier / 2 / 2;
if (IsLeftPanelOpen() && !*sgOptions.Graphics.zoom) {
offset.deltaX += SidePanelSize.width;
// SidePanelSize.width accounted for in Zoom()
}
}
// Draw areas moving in and out of the screen
if (myPlayer.isWalking()) {
switch (myPlayer._pdir) {
case Direction::North:
case Direction::NorthEast:
offset.deltaY -= TILE_HEIGHT;
position += Direction::North;
break;
case Direction::SouthWest:
case Direction::West:
offset.deltaX -= TILE_WIDTH;
position += Direction::West;
break;
case Direction::NorthWest:
offset.deltaX -= TILE_WIDTH / 2;
offset.deltaY -= TILE_HEIGHT / 2;
position += Direction::NorthWest;
default:
break;
}
}
}
/**
* @brief Configure render and process screen rows
* @param fullOut Buffer to render to
* @param position Center of view in dPiece coordinate
* @param position First tile of view in dPiece coordinate
* @param offset Amount to offset the rendering in screen space
*/
void DrawGame(const Surface &fullOut, Point position)
void DrawGame(const Surface &fullOut, Point position, Displacement offset)
{
// Limit rendering to the view area
const Surface &out = !*sgOptions.Graphics.zoom
? fullOut.subregionY(0, gnViewportHeight)
: fullOut.subregionY(0, (gnViewportHeight + 1) / 2);
// Adjust by player offset and tile grid alignment
Player &myPlayer = *MyPlayer;
Displacement offset = {};
if (myPlayer.isWalking())
offset = GetOffsetForWalking(myPlayer.AnimInfo, myPlayer._pdir, true);
int sx = offset.deltaX + tileOffset.deltaX;
int sy = offset.deltaY + tileOffset.deltaY;
int columns = tileColums;
int rows = tileRows;
position += tileShift;
// Skip rendering parts covered by the panels
if (CanPanelsCoverView()) {
if (!*sgOptions.Graphics.zoom) {
if (IsLeftPanelOpen()) {
position += Displacement(Direction::East) * 2;
columns -= 4;
sx += SidePanelSize.width - TILE_WIDTH / 2;
}
if (IsRightPanelOpen()) {
position += Displacement(Direction::East) * 2;
columns -= 4;
sx += -TILE_WIDTH / 2;
}
} else {
if (IsLeftPanelOpen()) {
position += Direction::East;
columns -= 2;
sx += -TILE_WIDTH / 2 / 2; // SPANEL_WIDTH accounted for in Zoom()
}
if (IsRightPanelOpen()) {
position += Direction::East;
columns -= 2;
sx += -TILE_WIDTH / 2 / 2;
}
}
if (CanPanelsCoverView() && (IsLeftPanelOpen() || IsRightPanelOpen())) {
columns -= (*sgOptions.Graphics.zoom) ? 2 : 4;
}
UpdateMissilesRendererData();
// Draw areas moving in and out of the screen
if (myPlayer.isWalking()) {
switch (myPlayer._pdir) {
if (MyPlayer->isWalking()) {
switch (MyPlayer->_pdir) {
case Direction::NoDirection:
break;
case Direction::North:
sy -= TILE_HEIGHT;
position += Direction::North;
case Direction::South:
rows += 2;
break;
case Direction::NorthEast:
sy -= TILE_HEIGHT;
position += Direction::North;
columns++;
rows += 2;
break;
case Direction::East:
case Direction::West:
columns++;
break;
case Direction::SouthEast:
columns++;
rows++;
break;
case Direction::South:
rows += 2;
break;
case Direction::SouthWest:
sx -= TILE_WIDTH;
position += Direction::West;
columns++;
rows++;
break;
case Direction::West:
sx -= TILE_WIDTH;
position += Direction::West;
columns++;
break;
case Direction::NorthWest:
sx -= TILE_WIDTH / 2;
sy -= TILE_HEIGHT / 2;
position += Direction::NorthWest;
columns++;
rows++;
break;
@ -1089,8 +1083,8 @@ void DrawGame(const Surface &fullOut, Point position)
DunRenderStats.clear();
#endif
DrawFloor(out, position, { sx, sy }, rows, columns);
DrawTileContent(out, position, { sx, sy }, rows, columns);
DrawFloor(out, position, Point {} + offset, rows, columns);
DrawTileContent(out, position, Point {} + offset, rows, columns);
if (*sgOptions.Graphics.zoom) {
Zoom(fullOut.subregionY(0, gnViewportHeight));
@ -1126,7 +1120,9 @@ void DrawView(const Surface &out, Point startPosition)
#ifdef _DEBUG
DebugCoordsMap.clear();
#endif
DrawGame(out, startPosition);
Displacement offset = {};
CalcFirstTilePosition(startPosition, offset);
DrawGame(out, startPosition, offset);
if (AutomapActive) {
DrawAutomap(out.subregionY(0, gnViewportHeight));
}
@ -1195,6 +1191,7 @@ void DrawView(const Surface &out, Point startPosition)
#endif
DrawMonsterHealthBar(out);
DrawItemNameLabels(out);
DrawFloatingNumbers(out, startPosition, offset);
if (stextflag != STORE_NONE && !qtextflag)
DrawSText(out);

13
Source/engine/render/text_render.cpp

@ -47,14 +47,14 @@ constexpr std::array<int, 6> LineHeights = { 12, 26, 38, 42, 50, 22 };
constexpr int SmallFontTallLineHeight = 16;
std::array<int, 6> BaseLineOffset = { -3, -2, -3, -6, -7, 3 };
std::array<const char *, 14> ColorTranslations = {
std::array<const char *, 15> ColorTranslations = {
"fonts\\goldui.trn",
"fonts\\grayui.trn",
"fonts\\golduis.trn",
"fonts\\grayuis.trn",
nullptr,
"fonts\\yellowdialog.trn",
"fonts\\yellow.trn",
nullptr,
"fonts\\black.trn",
@ -63,12 +63,13 @@ std::array<const char *, 14> ColorTranslations = {
"fonts\\whitegold.trn",
"fonts\\red.trn",
"fonts\\blue.trn",
"fonts\\orange.trn",
"fonts\\buttonface.trn",
"fonts\\buttonpushed.trn",
};
std::array<std::optional<std::array<uint8_t, 256>>, 14> ColorTranslationsData;
std::array<std::optional<std::array<uint8_t, 256>>, 15> ColorTranslationsData;
GameFontTables GetSizeFromFlags(UiFlags flags)
{
@ -92,6 +93,8 @@ text_color GetColorFromFlags(UiFlags flags)
return ColorWhite;
else if (HasAnyOf(flags, UiFlags::ColorBlue))
return ColorBlue;
else if (HasAnyOf(flags, UiFlags::ColorOrange))
return ColorOrange;
else if (HasAnyOf(flags, UiFlags::ColorRed))
return ColorRed;
else if (HasAnyOf(flags, UiFlags::ColorBlack))
@ -108,8 +111,8 @@ text_color GetColorFromFlags(UiFlags flags)
return ColorUiSilverDark;
else if (HasAnyOf(flags, UiFlags::ColorDialogWhite))
return ColorDialogWhite;
else if (HasAnyOf(flags, UiFlags::ColorDialogYellow))
return ColorDialogYellow;
else if (HasAnyOf(flags, UiFlags::ColorYellow))
return ColorYellow;
else if (HasAnyOf(flags, UiFlags::ColorButtonface))
return ColorButtonface;
else if (HasAnyOf(flags, UiFlags::ColorButtonpushed))

3
Source/engine/render/text_render.hpp

@ -37,7 +37,7 @@ enum text_color : uint8_t {
ColorUiSilverDark,
ColorDialogWhite,
ColorDialogYellow,
ColorYellow,
ColorGold,
ColorBlack,
@ -46,6 +46,7 @@ enum text_color : uint8_t {
ColorWhitegold,
ColorRed,
ColorBlue,
ColorOrange,
ColorButtonface,
ColorButtonpushed,

2
Source/gamemenu.cpp

@ -15,6 +15,7 @@
#include "loadsave.h"
#include "options.h"
#include "pfile.h"
#include "qol/floatingnumbers.h"
#include "utils/language.h"
namespace devilution {
@ -292,6 +293,7 @@ void gamemenu_load_game(bool /*bActivate*/)
{
EventHandler saveProc = SetEventHandler(DisableInputEventHandler);
gamemenu_off();
ClearFloatingNumbers();
NewCursor(CURSOR_NONE);
InitDiabloMsg(EMSG_LOADING);
RedrawEverything();

73
Source/missiles.cpp

@ -199,7 +199,8 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss
int hit = GenerateRnd(100);
int hper = 0;
const Player &player = Players[pnum];
if (MissilesData[static_cast<int8_t>(t)].mType == 0) {
const MissileData &missileData = MissilesData[static_cast<int8_t>(t)];
if (missileData.mType == 0) {
hper = player.GetRangedPiercingToHit();
hper -= player.CalculateArmorPierce(monster.armorClass, false);
hper -= (dist * dist) / 2;
@ -229,7 +230,7 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss
dam = mindam + GenerateRnd(maxdam - mindam + 1);
}
if (MissilesData[static_cast<int8_t>(t)].mType == 0 && MissilesData[static_cast<int8_t>(t)].damageType == DamageType::Physical) {
if (missileData.mType == 0 && missileData.damageType == DamageType::Physical) {
dam = player._pIBonusDamMod + dam * player._pIBonusDam / 100 + dam;
if (player._pClass == HeroClass::Rogue)
dam += player._pDamageMod;
@ -245,7 +246,7 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss
dam >>= 2;
if (&player == MyPlayer)
ApplyMonsterDamage(monster, dam);
ApplyMonsterDamage(missileData.damageType, monster, dam);
if (monster.hitPoints >> 6 <= 0) {
M_StartKill(monster, player);
@ -253,7 +254,7 @@ bool MonsterMHit(int pnum, int monsterId, int mindam, int maxdam, int dist, Miss
monster.tag(player);
PlayEffect(monster, MonsterSound::Hit);
} else {
if (monster.mode != MonsterMode::Petrified && MissilesData[static_cast<int8_t>(t)].mType == 0 && HasAnyOf(player._pIFlags, ItemSpecialEffect::Knockback))
if (monster.mode != MonsterMode::Petrified && missileData.mType == 0 && HasAnyOf(player._pIFlags, ItemSpecialEffect::Knockback))
M_GetKnockback(monster);
if (monster.type().type != MT_GOLEM)
M_StartHit(monster, player, dam);
@ -287,12 +288,14 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist,
return false;
}
if (HasAnyOf(target._pSpellFlags, SpellFlag::Etherealize) && MissilesData[static_cast<int8_t>(mtype)].mType == 0) {
const MissileData &missileData = MissilesData[static_cast<int8_t>(mtype)];
if (HasAnyOf(target._pSpellFlags, SpellFlag::Etherealize) && missileData.mType == 0) {
return false;
}
int8_t resper;
switch (MissilesData[static_cast<int8_t>(mtype)].damageType) {
switch (missileData.damageType) {
case DamageType::Fire:
resper = target._pFireResist;
break;
@ -311,7 +314,7 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist,
int hper = GenerateRnd(100);
int hit;
if (MissilesData[static_cast<int8_t>(mtype)].mType == 0) {
if (missileData.mType == 0) {
hit = player.GetRangedToHit()
- (dist * dist / 2)
- target.GetArmor();
@ -340,17 +343,17 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist,
dam = target._pHitPoints / 3;
} else {
dam = mindam + GenerateRnd(maxdam - mindam + 1);
if (MissilesData[static_cast<int8_t>(mtype)].mType == 0 && MissilesData[static_cast<int8_t>(mtype)].damageType == DamageType::Physical)
if (missileData.mType == 0 && missileData.damageType == DamageType::Physical)
dam += player._pIBonusDamMod + player._pDamageMod + dam * player._pIBonusDam / 100;
if (!shift)
dam <<= 6;
}
if (MissilesData[static_cast<int8_t>(mtype)].mType != 0)
if (missileData.mType != 0)
dam /= 2;
if (resper > 0) {
dam -= (dam * resper) / 100;
if (&player == MyPlayer)
NetSendCmdDamage(true, p, dam);
NetSendCmdDamage(true, p, dam, missileData.damageType);
target.Say(HeroSpeech::ArghClang);
return true;
}
@ -360,7 +363,7 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist,
*blocked = true;
} else {
if (&player == MyPlayer)
NetSendCmdDamage(true, p, dam);
NetSendCmdDamage(true, p, dam, missileData.damageType);
StartPlrHit(target, dam, false);
}
@ -456,8 +459,9 @@ void CheckMissileCol(Missile &missile, int minDamage, int maxDamage, bool isDama
missile._miHitFlag = false;
}
if (missile._mirange == 0 && MissilesData[static_cast<int8_t>(missile._mitype)].miSFX != -1)
PlaySfxLoc(MissilesData[static_cast<int8_t>(missile._mitype)].miSFX, missile.position.tile);
const MissileData &missileData = MissilesData[static_cast<int8_t>(missile._mitype)];
if (missile._mirange == 0 && missileData.miSFX != -1)
PlaySfxLoc(missileData.miSFX, missile.position.tile);
}
bool MoveMissile(Missile &missile, tl::function_ref<bool(Point)> checkTile, bool ifCheckTileFailsDontMoveToTile = false)
@ -932,7 +936,7 @@ bool MonsterTrapHit(int monsterId, int mindam, int maxdam, int dist, MissileID t
dam <<= 6;
if (resist)
dam /= 4;
ApplyMonsterDamage(monster, dam);
ApplyMonsterDamage(MissilesData[static_cast<int8_t>(t)].damageType, monster, dam);
#ifdef _DEBUG
if (DebugGodMode)
monster.hitPoints = 0;
@ -961,7 +965,9 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil
return false;
}
if (HasAnyOf(player._pSpellFlags, SpellFlag::Etherealize) && MissilesData[static_cast<int8_t>(mtype)].mType == 0) {
const MissileData &missileData = MissilesData[static_cast<int8_t>(mtype)];
if (HasAnyOf(player._pSpellFlags, SpellFlag::Etherealize) && missileData.mType == 0) {
return false;
}
@ -971,7 +977,7 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil
hit = 1000;
#endif
int hper = 40;
if (MissilesData[static_cast<int8_t>(mtype)].mType == 0) {
if (missileData.mType == 0) {
int tac = player.GetArmor();
if (monster != nullptr) {
hper = monster->toHit
@ -1010,7 +1016,7 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil
blkper = clamp(blkper, 0, 100);
int8_t resper;
switch (MissilesData[static_cast<int8_t>(mtype)].damageType) {
switch (missileData.damageType) {
case DamageType::Fire:
resper = player._pFireResist;
break;
@ -1064,7 +1070,7 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil
if (resper > 0) {
dam -= dam * resper / 100;
if (&player == MyPlayer) {
ApplyPlrDamage(player, 0, 0, dam, earflag);
ApplyPlrDamage(missileData.damageType, player, 0, 0, dam, earflag);
}
if (player._pHitPoints >> 6 > 0) {
@ -1074,7 +1080,7 @@ bool PlayerMHit(int pnum, Monster *monster, int dist, int mind, int maxd, Missil
}
if (&player == MyPlayer) {
ApplyPlrDamage(player, 0, 0, dam, earflag);
ApplyPlrDamage(missileData.damageType, player, 0, 0, dam, earflag);
}
if (player._pHitPoints >> 6 > 0) {
@ -1113,7 +1119,7 @@ void InitMissiles()
if (missile.sourcePlayer() == MyPlayer) {
int missingHP = myPlayer._pMaxHP - myPlayer._pHitPoints;
CalcPlrItemVals(myPlayer, true);
ApplyPlrDamage(myPlayer, 0, 1, missingHP + missile.var2);
ApplyPlrDamage(DamageType::Physical, myPlayer, 0, 1, missingHP + missile.var2);
}
}
}
@ -2622,7 +2628,7 @@ Missile *AddMissile(Point src, Point dst, Direction midir, MissileID mitype, mie
Missiles.emplace_back(Missile {});
auto &missile = Missiles.back();
const auto &missileData = MissilesData[static_cast<int8_t>(mitype)];
const MissileData &missileData = MissilesData[static_cast<int8_t>(mitype)];
missile._mitype = mitype;
missile._micaster = micaster;
@ -2688,16 +2694,17 @@ void MI_LArrow(Missile &missile)
mind = GenerateRnd(10) + 1 + currlevel;
maxd = GenerateRnd(10) + 1 + currlevel * 2;
}
DamageType rst = MissilesData[static_cast<int8_t>(missile._mitype)].damageType;
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = DamageType::Physical;
MissileData &missileData = MissilesData[static_cast<int8_t>(missile._mitype)];
DamageType rst = missileData.damageType;
missileData.damageType = DamageType::Physical;
MoveMissileAndCheckMissileCol(missile, mind, maxd, true, false);
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = rst;
missileData.damageType = rst;
if (missile._mirange == 0) {
missile._mimfnum = 0;
missile._mirange = missile._miAnimLen - 1;
missile.position.StopMissile();
rst = MissilesData[static_cast<int8_t>(missile._mitype)].damageType;
rst = missileData.damageType;
int eMind;
int eMaxd;
@ -2735,9 +2742,9 @@ void MI_LArrow(Missile &missile)
break;
}
SetMissAnim(missile, eAnim);
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = eRst;
missileData.damageType = eRst;
CheckMissileCol(missile, eMind, eMaxd, false, missile.position.tile, true);
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = rst;
missileData.damageType = rst;
} else {
if (missile.position.tile != Point { missile.var1, missile.var2 }) {
missile.var1 = missile.position.tile.x;
@ -3498,16 +3505,17 @@ void MI_Weapexp(Missile &missile)
const Player &player = Players[missile._misource];
int mind;
int maxd;
MissileData &missileData = MissilesData[static_cast<int8_t>(missile._mitype)];
if (missile.var2 == 1) {
// BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives.
mind = player._pIFMinDam;
maxd = player._pIFMaxDam;
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = DamageType::Fire;
missileData.damageType = DamageType::Fire;
} else {
// BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives.
mind = player._pILMinDam;
maxd = player._pILMaxDam;
MissilesData[static_cast<int8_t>(missile._mitype)].damageType = DamageType::Lightning;
missileData.damageType = DamageType::Lightning;
}
CheckMissileCol(missile, mind, maxd, false, missile.position.tile, false);
if (missile.var1 == 0) {
@ -3803,7 +3811,7 @@ void MI_Blodboil(Missile &missile)
}
CalcPlrItemVals(player, true);
ApplyPlrDamage(player, 0, 1, hpdif);
ApplyPlrDamage(DamageType::Physical, player, 0, 1, hpdif);
RedrawEverything();
player.Say(HeroSpeech::HeavyBreathing);
}
@ -4096,8 +4104,9 @@ void ProcessMissiles()
MissilePreFlag = false;
for (auto &missile : Missiles) {
if (MissilesData[static_cast<int8_t>(missile._mitype)].mProc != nullptr)
MissilesData[static_cast<int8_t>(missile._mitype)].mProc(missile);
const MissileData &missileData = MissilesData[static_cast<int8_t>(missile._mitype)];
if (missileData.mProc != nullptr)
missileData.mProc(missile);
if (missile._miAnimFlags == MissileDataFlags::NotAnimated)
continue;

13
Source/monster.cpp

@ -33,6 +33,7 @@
#include "missiles.h"
#include "movie.h"
#include "options.h"
#include "qol/floatingnumbers.h"
#include "spelldat.h"
#include "storm/storm_net.hpp"
#include "towners.h"
@ -1086,7 +1087,7 @@ void MonsterAttackMonster(Monster &attacker, Monster &target, int hper, int mind
return;
int dam = (mind + GenerateRnd(maxd - mind + 1)) << 6;
ApplyMonsterDamage(target, dam);
ApplyMonsterDamage(DamageType::Physical, target, dam);
if (attacker.isPlayerMinion()) {
int playerId = attacker.getId();
@ -1113,7 +1114,7 @@ int CheckReflect(Monster &monster, Player &player, int dam)
NetSendCmdParam1(true, CMD_SETREFLECT, 0);
// reflects 20-30% damage
int mdam = dam * RandomIntBetween(20, 30, true) / 100;
ApplyMonsterDamage(monster, mdam);
ApplyMonsterDamage(DamageType::Physical, monster, mdam);
if (monster.hitPoints >> 6 <= 0)
M_StartKill(monster, player);
else
@ -1197,13 +1198,13 @@ void MonsterAttackPlayer(Monster &monster, Player &player, int hit, int minDam,
int reflectedDamage = CheckReflect(monster, player, dam);
dam = std::max(dam - reflectedDamage, 0);
}
ApplyPlrDamage(player, 0, 0, dam);
ApplyPlrDamage(DamageType::Physical, player, 0, 0, dam);
}
// Reflect can also kill a monster, so make sure the monster is still alive
if (HasAnyOf(player._pIFlags, ItemSpecialEffect::Thorns) && monster.mode != MonsterMode::Death) {
int mdam = (GenerateRnd(3) + 1) << 6;
ApplyMonsterDamage(monster, mdam);
ApplyMonsterDamage(DamageType::Physical, monster, mdam);
if (monster.hitPoints >> 6 <= 0)
M_StartKill(monster, player);
else
@ -3568,8 +3569,10 @@ void AddDoppelganger(Monster &monster)
}
}
void ApplyMonsterDamage(Monster &monster, int damage)
void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage)
{
AddFloatingNumber(damageType, monster, damage);
monster.hitPoints -= damage;
if (monster.hitPoints >> 6 <= 0) {

3
Source/monster.h

@ -21,6 +21,7 @@
#include "engine/sound.h"
#include "engine/world_tile.hpp"
#include "init.h"
#include "misdat.h"
#include "monstdat.h"
#include "spelldat.h"
#include "textdat.h"
@ -456,7 +457,7 @@ void InitMonsters();
void SetMapMonsters(const uint16_t *dunData, Point startPosition);
Monster *AddMonster(Point position, Direction dir, size_t mtype, bool inMap);
void AddDoppelganger(Monster &monster);
void ApplyMonsterDamage(Monster &monster, int damage);
void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage);
bool M_Talker(const Monster &monster);
void M_StartStand(Monster &monster, Direction md);
void M_ClearSquares(const Monster &monster);

5
Source/msg.cpp

@ -1993,7 +1993,7 @@ size_t OnPlayerDamage(const TCmd *pCmd, Player &player)
Player &target = Players[message.bPlr];
if (&target == MyPlayer && leveltype != DTYPE_TOWN && gbBufferMsgs != 1) {
if (player.isOnActiveLevel() && damage <= 192000 && target._pHitPoints >> 6 > 0) {
ApplyPlrDamage(target, 0, 0, damage, 1);
ApplyPlrDamage(message.damageType, target, 0, 0, damage, 1);
}
}
@ -3232,13 +3232,14 @@ void NetSendCmdChBeltItem(bool bHiPri, int beltIndex)
NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd));
}
void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam)
void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam, DamageType damageType)
{
TCmdDamage cmd;
cmd.bCmd = CMD_PLRDAMAGE;
cmd.bPlr = bPlr;
cmd.dwDam = dwDam;
cmd.damageType = damageType;
if (bHiPri)
NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd));
else

3
Source/msg.h

@ -643,6 +643,7 @@ struct TCmdDamage {
_cmd_id bCmd;
uint8_t bPlr;
uint32_t dwDam;
DamageType damageType;
};
struct TCmdMonDamage {
@ -774,7 +775,7 @@ void NetSendCmdChItem(bool bHiPri, uint8_t bLoc);
void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc);
void NetSendCmdChInvItem(bool bHiPri, int invGridIndex);
void NetSendCmdChBeltItem(bool bHiPri, int invGridIndex);
void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam);
void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam, DamageType damageType);
void NetSendCmdMonDmg(bool bHiPri, uint16_t wMon, uint32_t dwDam);
void NetSendCmdString(uint32_t pmask, const char *pszStr);
void delta_close_portal(int pnum);

2
Source/objects.cpp

@ -1722,7 +1722,7 @@ void UpdateBurningCrossDamage(Object &cross)
if (myPlayer.position.tile != cross.position + Displacement { 0, -1 })
return;
ApplyPlrDamage(myPlayer, 0, 0, damage[leveltype - 1]);
ApplyPlrDamage(DamageType::Fire, myPlayer, 0, 0, damage[leveltype - 1]);
if (myPlayer._pHitPoints >> 6 > 0) {
myPlayer.Say(HeroSpeech::Argh);
}

2
Source/options.cpp

@ -1025,6 +1025,7 @@ 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, "Enable floating numbers", N_("Enables floating numbers on gaining XP / dealing damage etc."), false)
{
grabInput.SetValueChangedCallback(OptionGrabInputChanged);
experienceBar.SetValueChangedCallback(OptionExperienceBarChanged);
@ -1065,6 +1066,7 @@ std::vector<OptionEntryBase *> GameplayOptions::GetEntries()
&numFullManaPotionPickup,
&numRejuPotionPickup,
&numFullRejuPotionPickup,
&enableFloatingNumbers,
};
}

2
Source/options.h

@ -583,6 +583,8 @@ struct GameplayOptions : OptionCategoryBase {
OptionEntryInt<int> numRejuPotionPickup;
/** @brief Number of Full Rejuvenating potions to pick up automatically */
OptionEntryInt<int> numFullRejuPotionPickup;
/** @brief Enable floating numbers. */
OptionEntryBoolean enableFloatingNumbers;
};
struct ControllerOptions : OptionCategoryBase {

14
Source/player.cpp

@ -37,6 +37,7 @@
#include "options.h"
#include "player.h"
#include "qol/autopickup.h"
#include "qol/floatingnumbers.h"
#include "qol/stash.h"
#include "spells.h"
#include "stores.h"
@ -824,7 +825,7 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false)
if (HasAnyOf(player.pDamAcFlags, ItemSpecialEffectHf::Peril)) {
dam2 += player._pIGetHit << 6;
if (dam2 >= 0) {
ApplyPlrDamage(player, 0, 1, dam2);
ApplyPlrDamage(DamageType::Physical, player, 0, 1, dam2);
}
dam *= 2;
}
@ -833,7 +834,7 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false)
dam = monster.hitPoints; /* ensure monster is killed with one hit */
}
#endif
ApplyMonsterDamage(monster, dam);
ApplyMonsterDamage(DamageType::Physical, monster, dam);
}
int skdam = 0;
@ -951,7 +952,7 @@ bool PlrHitPlr(Player &attacker, Player &target)
RedrawComponent(PanelDrawComponent::Health);
}
if (&attacker == MyPlayer) {
NetSendCmdDamage(true, target.getId(), skdam);
NetSendCmdDamage(true, target.getId(), skdam, DamageType::Physical);
}
StartPlrHit(target, skdam, false);
@ -3061,9 +3062,12 @@ void StripTopGold(Player &player)
player._pGold = CalculateGold(player);
}
void ApplyPlrDamage(Player &player, int dam, int minHP /*= 0*/, int frac /*= 0*/, int earflag /*= 0*/)
void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP /*= 0*/, int frac /*= 0*/, int earflag /*= 0*/)
{
int totalDamage = (dam << 6) + frac;
if (&player == MyPlayer) {
AddFloatingNumber(damageType, player, totalDamage);
}
if (totalDamage > 0 && player.pManaShield) {
int8_t manaShieldLevel = player._pSplLvl[SPL_MANASHIELD];
if (manaShieldLevel > 0) {
@ -3273,7 +3277,7 @@ void ProcessPlayers()
if (&player == MyPlayer) {
if (HasAnyOf(player._pIFlags, ItemSpecialEffect::DrainLife) && leveltype != DTYPE_TOWN) {
ApplyPlrDamage(player, 0, 0, 4);
ApplyPlrDamage(DamageType::Physical, player, 0, 0, 4);
}
if (HasAnyOf(player._pIFlags, ItemSpecialEffect::NoMana) && player._pManaBase > 0) {
player._pManaBase -= player._pMana;

2
Source/player.h

@ -791,7 +791,7 @@ void NextPlrLevel(Player &player);
#endif
void AddPlrExperience(Player &player, int lvl, int exp);
void AddPlrMonstExper(int lvl, int exp, char pmask);
void ApplyPlrDamage(Player &player, int dam, int minHP = 0, int frac = 0, int earflag = 0);
void ApplyPlrDamage(DamageType damageType, Player &player, int dam, int minHP = 0, int frac = 0, int earflag = 0);
void InitPlayer(Player &player, bool FirstTime);
void InitMultiView();
void PlrClrTrans(Point position);

5
Source/portal.cpp

@ -49,7 +49,8 @@ void SetPortalStats(int i, bool o, int x, int y, int lvl, dungeon_type lvltype,
void AddWarpMissile(int i, Point position, bool sync)
{
MissilesData[static_cast<int8_t>(MissileID::TownPortal)].mlSFX = SFX_NONE;
MissileData &missileData = MissilesData[static_cast<int8_t>(MissileID::TownPortal)];
missileData.mlSFX = SFX_NONE;
auto *missile = AddMissile({ 0, 0 }, position, Direction::South, MissileID::TownPortal, TARGET_MONSTERS, i, 0, 0);
if (missile != nullptr) {
@ -61,7 +62,7 @@ void AddWarpMissile(int i, Point position, bool sync)
missile->_mlid = AddLight(missile->position.tile, 15);
}
MissilesData[static_cast<int8_t>(MissileID::TownPortal)].mlSFX = LS_SENTINEL;
missileData.mlSFX = LS_SENTINEL;
}
void SyncPortals()

198
Source/qol/floatingnumbers.cpp

@ -0,0 +1,198 @@
#include "floatingnumbers.h"
#include <ctime>
#include <deque>
#include <fmt/format.h>
#include <string>
#include "engine/render/text_render.hpp"
#include "options.h"
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<FloatingNumber> 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)
{
num.text = fmt::format("{:d}", num.value >> 6);
if (num.value > 0 && num.value < 64) {
num.text = fmt::format("{:.2f}", num.value / 64.0);
}
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 = goodAngles[rand() % 3];
if (damageToPlayer)
endOffset = -endOffset;
for (auto &num : FloatingQueue) {
if (num.reverseDirection == damageToPlayer && num.type == type && num.index == index && (SDL_GetTicks() - static_cast<int>(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)
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)
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)
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

20
Source/qol/floatingnumbers.h

@ -0,0 +1,20 @@
/**
* @file floatingnumbers.h
*
* Adds floating numbers QoL feature
*/
#pragma once
#include "engine/point.hpp"
#include "misdat.h"
#include "monster.h"
#include "player.h"
namespace devilution {
void AddFloatingNumber(DamageType damageType, const Monster &monster, int damage);
void AddFloatingNumber(DamageType damageType, const Player &player, int damage);
void DrawFloatingNumbers(const Surface &out, Point viewPosition, Displacement offset);
void ClearFloatingNumbers();
} // namespace devilution

4
Source/spells.cpp

@ -196,10 +196,10 @@ void ConsumeSpell(Player &player, spell_id sn)
break;
}
if (sn == SPL_FLARE) {
ApplyPlrDamage(player, 5);
ApplyPlrDamage(DamageType::Physical, player, 5);
}
if (sn == SPL_BONESPIRIT) {
ApplyPlrDamage(player, 6);
ApplyPlrDamage(DamageType::Physical, player, 6);
}
}

Loading…
Cancel
Save