diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 67e6f83ec..9adf25770 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -1120,7 +1120,7 @@ void SavePlayer(SaveHelper &file, const Player &player) file.WriteLE(player.AnimInfo.numberOfFrames); file.WriteLE(player.AnimInfo.currentFrame + 1); // write _pAnimWidth for vanilla compatibility - int animWidth = player.AnimInfo.celSprite ? player.AnimInfo.celSprite->Width() : 96; + const int animWidth = player.getSpriteWidth(); file.WriteLE(animWidth); // write _pAnimWidth2 for vanilla compatibility file.WriteLE(CalculateWidth2(animWidth)); diff --git a/Source/player.cpp b/Source/player.cpp index 161f0c17d..b70fe204b 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -1771,6 +1771,67 @@ void CheckCheatStats(Player &player) } } +HeroClass GetPlayerSpriteClass(HeroClass cls) +{ + if (cls == HeroClass::Bard && !hfbard_mpq) + return HeroClass::Rogue; + if (cls == HeroClass::Barbarian && !hfbarb_mpq) + return HeroClass::Warrior; + return cls; +} + +PlayerWeaponGraphic GetPlayerWeaponGraphic(player_graphic graphic, PlayerWeaponGraphic weaponGraphic) +{ + if (leveltype == DTYPE_TOWN && IsAnyOf(graphic, player_graphic::Lightning, player_graphic::Fire, player_graphic::Magic)) { + // If the hero doesn't hold the weapon in town then we should use the unarmed animation for casting + switch (weaponGraphic) { + case PlayerWeaponGraphic::Mace: + case PlayerWeaponGraphic::Sword: + return PlayerWeaponGraphic::Unarmed; + case PlayerWeaponGraphic::SwordShield: + case PlayerWeaponGraphic::MaceShield: + return PlayerWeaponGraphic::UnarmedShield; + default: + break; + } + } + return weaponGraphic; +} + +uint16_t GetPlayerSpriteWidth(HeroClass cls, player_graphic graphic, PlayerWeaponGraphic weaponGraphic) +{ + switch (graphic) { + case player_graphic::Stand: + case player_graphic::Walk: + if (cls == HeroClass::Monk) + return 112; + break; + case player_graphic::Attack: + if (cls == HeroClass::Monk) + return 130; + else if (weaponGraphic != PlayerWeaponGraphic::Bow || !(cls == HeroClass::Warrior || cls == HeroClass::Barbarian)) + return 128; + break; + case player_graphic::Hit: + case player_graphic::Block: + if (cls == HeroClass::Monk) + return 98; + break; + case player_graphic::Lightning: + case player_graphic::Fire: + case player_graphic::Magic: + if (cls == HeroClass::Monk) + return 114; + else if (cls == HeroClass::Sorcerer) + return 128; + break; + case player_graphic::Death: + return (cls == HeroClass::Monk) ? 160 : 128; + break; + } + return 96; +} + } // namespace void Player::CalcScrolls() @@ -1978,6 +2039,54 @@ void Player::ReadySpellFromEquipment(inv_body_loc bodyLocation) } } +player_graphic Player::getGraphic() const +{ + switch (_pmode) { + case PM_STAND: + case PM_NEWLVL: + case PM_QUIT: + return player_graphic::Stand; + case PM_WALK_NORTHWARDS: + case PM_WALK_SOUTHWARDS: + case PM_WALK_SIDEWAYS: + return player_graphic::Walk; + case PM_ATTACK: + case PM_RATTACK: + return player_graphic::Attack; + case PM_BLOCK: + return player_graphic::Block; + case PM_SPELL: + // We don't have the spell data for other players. + if (this == MyPlayer) { + switch (spelldata[_pSpell].sType) { + case STYPE_FIRE: + return player_graphic::Fire; + case STYPE_LIGHTNING: + return player_graphic::Lightning; + case STYPE_MAGIC: + return player_graphic::Magic; + } + } + return player_graphic::Fire; + case PM_GOTHIT: + return player_graphic::Hit; + case PM_DEATH: + return player_graphic::Death; + default: + app_fatal("SyncPlrAnim"); + } +} + +uint16_t Player::getSpriteWidth() const +{ + if (!HeadlessMode) + return AnimInfo.celSprite->Width(); + const player_graphic graphic = getGraphic(); + const HeroClass cls = GetPlayerSpriteClass(_pClass); + const PlayerWeaponGraphic weaponGraphic = GetPlayerWeaponGraphic(graphic, static_cast(_pgfxnum & 0xF)); + return GetPlayerSpriteWidth(cls, graphic, weaponGraphic); +} + void Player::UpdatePreviewCelSprite(_cmd_id cmdId, Point point, uint16_t wParam1, uint16_t wParam2) { // if game is not running don't show a preview @@ -2142,18 +2251,10 @@ void LoadPlrGFX(Player &player, player_graphic graphic) if (animationData.RawData != nullptr) return; - HeroClass c = player._pClass; - if (c == HeroClass::Bard && !hfbard_mpq) { - c = HeroClass::Rogue; - } else if (c == HeroClass::Barbarian && !hfbarb_mpq) { - c = HeroClass::Warrior; - } - - auto animWeaponId = static_cast(player._pgfxnum & 0xF); - int animationWidth = 96; - bool useUnarmedAnimationInTown = false; + const HeroClass cls = GetPlayerSpriteClass(player._pClass); + const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast(player._pgfxnum & 0xF)); - const char *cs = ClassPathTbl[static_cast(c)]; + const char *path = ClassPathTbl[static_cast(cls)]; const char *szCel; switch (graphic) { @@ -2161,61 +2262,35 @@ void LoadPlrGFX(Player &player, player_graphic graphic) szCel = "AS"; if (leveltype == DTYPE_TOWN) szCel = "ST"; - if (c == HeroClass::Monk) - animationWidth = 112; break; case player_graphic::Walk: szCel = "AW"; if (leveltype == DTYPE_TOWN) szCel = "WL"; - if (c == HeroClass::Monk) - animationWidth = 112; break; case player_graphic::Attack: if (leveltype == DTYPE_TOWN) return; szCel = "AT"; - if (c == HeroClass::Monk) - animationWidth = 130; - else if (animWeaponId != PlayerWeaponGraphic::Bow || !(c == HeroClass::Warrior || c == HeroClass::Barbarian)) - animationWidth = 128; break; case player_graphic::Hit: if (leveltype == DTYPE_TOWN) return; szCel = "HT"; - if (c == HeroClass::Monk) - animationWidth = 98; break; case player_graphic::Lightning: szCel = "LM"; - useUnarmedAnimationInTown = true; - if (c == HeroClass::Monk) - animationWidth = 114; - else if (c == HeroClass::Sorcerer) - animationWidth = 128; break; case player_graphic::Fire: szCel = "FM"; - useUnarmedAnimationInTown = true; - if (c == HeroClass::Monk) - animationWidth = 114; - else if (c == HeroClass::Sorcerer) - animationWidth = 128; break; case player_graphic::Magic: szCel = "QM"; - useUnarmedAnimationInTown = true; - if (c == HeroClass::Monk) - animationWidth = 114; - else if (c == HeroClass::Sorcerer) - animationWidth = 128; break; case player_graphic::Death: if (animWeaponId != PlayerWeaponGraphic::Unarmed) return; szCel = "DT"; - animationWidth = (c == HeroClass::Monk) ? 160 : 128; break; case player_graphic::Block: if (leveltype == DTYPE_TOWN) @@ -2223,32 +2298,18 @@ void LoadPlrGFX(Player &player, player_graphic graphic) if (!player._pBlockFlag) return; szCel = "BL"; - if (c == HeroClass::Monk) - animationWidth = 98; break; default: app_fatal("PLR:2"); } - if (leveltype == DTYPE_TOWN && useUnarmedAnimationInTown) { - // If the hero doesn't hold the weapon in town then we should use the unarmed animation for casting - switch (animWeaponId) { - case PlayerWeaponGraphic::Mace: - case PlayerWeaponGraphic::Sword: - animWeaponId = PlayerWeaponGraphic::Unarmed; - break; - case PlayerWeaponGraphic::SwordShield: - case PlayerWeaponGraphic::MaceShield: - animWeaponId = PlayerWeaponGraphic::UnarmedShield; - break; - default: - break; - } - } + if (HeadlessMode) + return; - char prefix[3] = { CharChar[static_cast(c)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; + char prefix[3] = { CharChar[static_cast(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; char pszName[256]; - *fmt::format_to(pszName, FMT_COMPILE(R"(PlrGFX\{0}\{1}\{1}{2}.CL2)"), cs, string_view(prefix, 3), szCel) = 0; + *fmt::format_to(pszName, FMT_COMPILE(R"(PlrGFX\{0}\{1}\{1}{2}.CL2)"), path, string_view(prefix, 3), szCel) = 0; + const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId); SetPlayerGPtrs(pszName, animationData.RawData, animationData.CelSpritesForDirections, animationWidth); } @@ -3440,51 +3501,7 @@ void CheckPlrSpell(bool isShiftHeld, spell_id spellID, spell_type spellType) void SyncPlrAnim(Player &player) { - player_graphic graphic; - switch (player._pmode) { - case PM_STAND: - case PM_NEWLVL: - case PM_QUIT: - graphic = player_graphic::Stand; - break; - case PM_WALK_NORTHWARDS: - case PM_WALK_SOUTHWARDS: - case PM_WALK_SIDEWAYS: - graphic = player_graphic::Walk; - break; - case PM_ATTACK: - case PM_RATTACK: - graphic = player_graphic::Attack; - break; - case PM_BLOCK: - graphic = player_graphic::Block; - break; - case PM_SPELL: - graphic = player_graphic::Fire; - if (&player == MyPlayer) { - switch (spelldata[player._pSpell].sType) { - case STYPE_FIRE: - graphic = player_graphic::Fire; - break; - case STYPE_LIGHTNING: - graphic = player_graphic::Lightning; - break; - case STYPE_MAGIC: - graphic = player_graphic::Magic; - break; - } - } - break; - case PM_GOTHIT: - graphic = player_graphic::Hit; - break; - case PM_DEATH: - graphic = player_graphic::Death; - break; - default: - app_fatal("SyncPlrAnim"); - } - + const player_graphic graphic = player.getGraphic(); player.AnimInfo.celSprite = player.AnimationData[static_cast(graphic)].GetCelSpritesForDirection(player._pdir); // Ensure ScrollInfo is initialized correctly ScrollViewPort(player, WalkSettings[static_cast(player._pdir)].scrollDir); diff --git a/Source/player.h b/Source/player.h index 484f39e95..0cd09d681 100644 --- a/Source/player.h +++ b/Source/player.h @@ -698,6 +698,10 @@ struct Player { return false; } + [[nodiscard]] player_graphic getGraphic() const; + + [[nodiscard]] uint16_t getSpriteWidth() const; + /** * @brief Updates previewCelSprite according to new requested command * @param cmdId What command is requested