diff --git a/README.md b/README.md index e4e6e3440..8678031f2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,8 @@ ![Linux-CI_fmod_steam](https://github.com/TurningWheel/Barony/workflows/Linux-CI_fmod_steam/badge.svg) ![Linux-CI_fmod_steam_eos](https://github.com/TurningWheel/Barony/workflows/Linux-CI_fmod_steam_eos/badge.svg) -# Update - 4th Feb 2021 +# Update - 3rd October 2023 -The current 'master' branch contains in-development features for our latest update. From here we're branching to 'develop' and continuing on work for splitscreen/gamepad/new UI. - -If you'd like to compile the code for your own modding or casual uses, we recommend forking from the current stable V3.3.7 build on Steam/Epic/GOG - check the Releases section on Github for the commit. -[https://github.com/TurningWheel/Barony/releases/tag/v3.3.7](https://github.com/TurningWheel/Barony/releases/tag/v3.3.7). - -For bugfixes + PRs, open them against 'master'. To build the code from 'master' you'll need some additional assets to get the game running, 'tmp-master-assets.zip' found at [https://github.com/TurningWheel/Barony/releases/tag/V3.3.7-dev](https://github.com/TurningWheel/Barony/releases/tag/V3.3.7-dev) and place them in the root Barony directory. +The current 'develop' branch contains in-development features for our latest update. For bugfixes + PRs, open them against 'master'. # Compilation Instructions diff --git a/lang/en.txt b/lang/en.txt index f822ae810..a022ef5c2 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -4202,7 +4202,7 @@ any gyrobot findings.# 3824 DEPRECATED# 3825 DEPRECATED# 3826 DEPRECATED# -3827 DEPRECATED# +3827 Incubus# 3828 DEPRECATED# 3829 DEPRECATED# 3830 DEPRECATED# @@ -5908,7 +5908,8 @@ Mod# Workshop# 5881 Open Workshop# -5882 Browse community created mods and 'subscribe' to\ndownload mods for use. +5882 Browse community created mods and 'subscribe' to +download mods for use. Subscribing while Barony is running may require closing the game in order for Steam to begin downloading. @@ -6204,5 +6205,6 @@ intro - God Rest Ye Merry Gentlemen by NaturesEye# 6049 View Wheel# 6050 SPECTATE# 6051 SPAWN AS GHOST# +6052 Your party has been wiped out...# 6100 end# diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index d71517b25..15e37abc1 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -629,12 +629,12 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa if ( !strcmp(stat->name, "") ) { updateEnemyBar(parent, triggered, getMonsterLocalizedName(stat->type).c_str(), stat->HP, stat->MAXHP, - false, DamageGib::DMG_TODO); + false, DamageGib::DMG_DEFAULT); } else { updateEnemyBar(parent, triggered, stat->name, stat->HP, stat->MAXHP, - false, DamageGib::DMG_TODO); + false, DamageGib::DMG_DEFAULT); } Entity* gib = spawnGib(triggered); serverSpawnGibForClient(gib); diff --git a/src/actplayer.cpp b/src/actplayer.cpp index f759fa008..d83736e12 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -7539,6 +7539,46 @@ void actPlayer(Entity* my) { hit.entity->doorHealth = 0; } + //else if ( hit.entity->behavior == &actDoorFrame && + // hit.entity->flags[INVISIBLE] ) + //{ + // // code that almost fixes door frame collision + // if ( hit.entity->yaw >= -0.1 && hit.entity->yaw <= 0.1 ) + // { + // // east/west doorway + // if ( my->y < floor(hit.entity->y / 16) * 16 + 8 ) + // { + // // slide south + // PLAYER_VELX = 0; + // PLAYER_VELY = .25; + // } + // else + // { + // // slide north + // PLAYER_VELX = 0; + // PLAYER_VELY = -.25; + // } + // } + // else + // { + // // north/south doorway + // if ( my->x < floor(hit.entity->x / 16) * 16 + 8 ) + // { + // // slide east + // PLAYER_VELX = .25; + // PLAYER_VELY = 0; + // } + // else + // { + // // slide west + // PLAYER_VELX = -.25; + // PLAYER_VELY = 0; + // } + // } + // my->x += PLAYER_VELX; + // my->y += PLAYER_VELY; + // dist = sqrt(PLAYER_VELX * PLAYER_VELX + PLAYER_VELY * PLAYER_VELY); + //} } } else diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 470236f71..b0d5f1abc 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -1167,12 +1167,12 @@ void actThrown(Entity* my) if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, DamageGib::DMG_DEFAULT); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, DamageGib::DMG_DEFAULT); } } diff --git a/src/engine/audio/defines.cpp b/src/engine/audio/defines.cpp index 3b7749482..56b41a71c 100644 --- a/src/engine/audio/defines.cpp +++ b/src/engine/audio/defines.cpp @@ -53,8 +53,8 @@ FMOD::Sound* caveslairmusic = nullptr; FMOD::Sound* bramscastlemusic = nullptr; FMOD::Sound* hamletmusic = nullptr; FMOD::Sound* tutorialmusic = nullptr; -FMOD::Sound* introstorymusic = nullptr; FMOD::Sound* gameovermusic = nullptr; +FMOD::Sound* introstorymusic = nullptr; bool levelmusicplaying = false; FMOD::Channel* music_channel = nullptr; diff --git a/src/engine/audio/sound.cpp b/src/engine/audio/sound.cpp index 60ba08dcd..b27706ad4 100644 --- a/src/engine/audio/sound.cpp +++ b/src/engine/audio/sound.cpp @@ -537,8 +537,8 @@ OPENAL_BUFFER* caveslairmusic = NULL; OPENAL_BUFFER* bramscastlemusic = NULL; OPENAL_BUFFER* hamletmusic = NULL; OPENAL_BUFFER* tutorialmusic = nullptr; -OPENAL_BUFFER* introstorymusic = nullptr; OPENAL_BUFFER* gameovermusic = nullptr; +OPENAL_BUFFER* introstorymusic = nullptr; bool levelmusicplaying = false; OPENAL_SOUND* music_channel = nullptr; @@ -1160,6 +1160,7 @@ bool physfsSearchMusicToUpdate() themeMusic.push_back("music/hamlet.ogg"); themeMusic.push_back("music/tutorial.ogg"); themeMusic.push_back("sound/Death.ogg"); + themeMusic.push_back("sound/ui/StoryMusicV3.ogg"); for ( std::vector::iterator it = themeMusic.begin(); it != themeMusic.end(); ++it ) { @@ -1281,6 +1282,7 @@ void physfsReloadMusic(bool &introMusicChanged, bool reloadAll) //TODO: This sho themeMusic.push_back("music/hamlet.ogg"); themeMusic.push_back("music/tutorial.ogg"); themeMusic.push_back("sound/Death.ogg"); + themeMusic.push_back("sound/ui/StoryMusicV3.ogg"); int index = 0; #ifdef USE_OPENAL @@ -1583,6 +1585,20 @@ void physfsReloadMusic(bool &introMusicChanged, bool reloadAll) //TODO: This sho fmod_result = fmod_system->createStream(musicDir.c_str(), FMOD_DEFAULT, nullptr, &gameovermusic); } break; + case 20: + if ( introstorymusic ) + { + introstorymusic->release(); + } + if ( musicPreload ) + { + fmod_result = fmod_system->createSound(musicDir.c_str(), FMOD_DEFAULT, nullptr, &introstorymusic); + } + else + { + fmod_result = fmod_system->createStream(musicDir.c_str(), FMOD_DEFAULT, nullptr, &introstorymusic); + } + break; default: break; } diff --git a/src/eos.cpp b/src/eos.cpp index 4536db531..92afc0343 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -1240,7 +1240,9 @@ bool EOSFuncs::initPlatform(bool enableLogging) PlatformOptions.RTCOptions = nullptr; PlatformOptions.IntegratedPlatformOptionsContainerHandle = nullptr; // must be null initialized +#ifndef LINUX PlatformOptions.SystemSpecificOptions = nullptr; // must be null initialized +#endif PlatformHandle = EOS_Platform_Create(&PlatformOptions); PlatformOptions.bIsServer = EOS_TRUE; diff --git a/src/files.cpp b/src/files.cpp index 442718ebe..a74a499b1 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -3177,7 +3177,20 @@ void generatePolyModels(int start, int end, bool forceCacheRebuild) if ( useModelCache && !forceCacheRebuild ) { #ifndef NINTENDO - std::string cache_path = std::string(outputdir) + "/models.cache"; + std::string cache_path; + if (isCurrentHoliday()) { + const auto holiday = getCurrentHoliday(); + switch (holiday) { + case HolidayTheme::THEME_NONE: + cache_path = std::string(outputdir) + "/models.cache"; + break; + default: + cache_path = "models.cache"; + break; + } + } else { + cache_path = std::string(outputdir) + "/models.cache"; + } #else std::string cache_path = "models.cache"; #endif diff --git a/src/init_game.cpp b/src/init_game.cpp index ad149b1da..b01592075 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -558,8 +558,8 @@ void deinitGame() bramscastlemusic->release(); hamletmusic->release(); tutorialmusic->release(); - introstorymusic->release(); gameovermusic->release(); + introstorymusic->release(); for ( int c = 0; c < NUMMINESMUSIC; c++ ) { diff --git a/src/input.cpp b/src/input.cpp index 777e540c0..94639d68d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -490,10 +490,10 @@ std::string Input::getGlyphPathForInput(const char* input, bool pressed, Control {"StickRightY+", {"Stick_Switch_R_Down_00.png", "Stick_Switch_R_Down_Pressed_00.png"}}, {"LeftTrigger", {"G_Switch_ZL00.png", "G_Switch_ZL_Press00.png"}}, {"RightTrigger", {"G_Switch_ZR00.png", "G_Switch_ZR_Press00.png"}}, - {"DpadX-", {"G_Direct_Left_Press00.png", "G_Direct_00.png"}}, - {"DpadX+", {"G_Direct_Right_Press00.png", "G_Direct_00.png"}}, - {"DpadY-", {"G_Direct_Up_Press00.png", "G_Direct_00.png"}}, - {"DpadY+", {"G_Direct_Down_Press00.png", "G_Direct_00.png"}}, + {"DpadX-", {"G_Switch_Direct_Left_Press00.png", "G_Switch_Direct_00.png"}}, + {"DpadX+", {"G_Switch_Direct_Right_Press00.png", "G_Switch_Direct_00.png"}}, + {"DpadY-", {"G_Switch_Direct_Up_Press00.png", "G_Switch_Direct_00.png"}}, + {"DpadY+", {"G_Switch_Direct_Down_Press00.png", "G_Switch_Direct_00.png"}}, }; // look for glyph in table diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index a67bb8835..adca14aa1 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -944,7 +944,11 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. // Only degrade the equipment if Friendly Fire is ON or if it is (OFF && target is an enemy) bool bShouldEquipmentDegrade = false; - if ( (svFlags & SV_FLAG_FRIENDLYFIRE) ) + if ( parent && parent->behavior == &actDeathGhost ) + { + bShouldEquipmentDegrade = false; + } + else if ( (svFlags & SV_FLAG_FRIENDLYFIRE) ) { // Friendly Fire is ON, equipment should always degrade, as hit will register bShouldEquipmentDegrade = true; @@ -1243,11 +1247,35 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. // check for magic resistance... // resistance stacks diminishingly int resistance = 0; + DamageGib dmgGib = DMG_DEFAULT; + real_t damageMultiplier = 1.0; if ( hit.entity ) { resistance = Entity::getMagicResistance(hit.entity->getStats()); - - // TODO - magic impact weak/strong messages? + if ( (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer) && hitstats ) + { + damageMultiplier = Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + if ( damageMultiplier <= 0.75 ) + { + dmgGib = DMG_WEAKEST; + } + else if ( damageMultiplier <= 0.85 ) + { + dmgGib = DMG_WEAKER; + } + else if ( damageMultiplier >= 1.25 ) + { + dmgGib = resistance == 0 ? DMG_STRONGEST : DMG_WEAKER; + } + else if ( damageMultiplier >= 1.15 ) + { + dmgGib = resistance == 0 ? DMG_STRONGER : DMG_WEAKER; + } + else if ( resistance > 0 ) + { + dmgGib = DMG_WEAKEST; + } + } } real_t spellbookDamageBonus = (my->actmagicSpellbookBonus / 100.f); @@ -1274,7 +1302,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int damage = element->damage; damage += (spellbookDamageBonus * damage); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. @@ -1292,12 +1320,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( hitstats->HP <= 0 && parent) @@ -1406,7 +1434,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. @@ -1425,12 +1453,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( hitstats->HP <= 0 && parent) @@ -1621,7 +1649,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } damage = damage - local_rng.rand() % ((damage / 8) + 1); } - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; if ( parent ) { Stat* casterStats = parent->getStats(); @@ -1672,12 +1700,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( oldHP > 0 && hitstats->HP <= 0 ) { @@ -1940,7 +1968,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; int oldHP = hitstats->HP; - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); Entity* gib = spawnGib(hit.entity); @@ -1956,12 +1984,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( parent ) { @@ -2166,7 +2194,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; int oldHP = hitstats->HP; - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); @@ -2180,12 +2208,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( oldHP > 0 && hitstats->HP <= 0 && parent) { @@ -2721,7 +2749,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int damage = element->damage; damage += (spellbookDamageBonus * damage); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; Stat* casterStats = nullptr; if ( parent ) { @@ -2795,12 +2823,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( hitstats->HP <= 0 && parent ) diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index f677859da..aa9975fd4 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -260,11 +260,36 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re resistance += 2; hasamulet = true; } + + DamageGib dmgGib = DMG_DEFAULT; + real_t damageMultiplier = Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + if ( damageMultiplier <= 0.75 ) + { + dmgGib = DMG_WEAKEST; + } + else if ( damageMultiplier <= 0.85 ) + { + dmgGib = DMG_WEAKER; + } + else if ( damageMultiplier >= 1.25 ) + { + dmgGib = resistance == 0 ? DMG_STRONGEST : DMG_WEAKER; + } + else if ( damageMultiplier >= 1.15 ) + { + dmgGib = resistance == 0 ? DMG_STRONGER : DMG_WEAKER; + } + else if ( resistance > 0 ) + { + dmgGib = DMG_WEAKEST; + } + int oldHP = hitstats->HP; damage /= (1 + (int)resistance); - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; hit.entity->modHP(-damage); + // write the obituary if ( parent ) { @@ -318,12 +343,12 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( oldHP > 0 && hitstats->HP <= 0 && parent ) @@ -417,8 +442,32 @@ void spellEffectPoison(Entity& my, spellElement_t& element, Entity* parent, int resistance += 2; hasamulet = true; } + + DamageGib dmgGib = DMG_DEFAULT; + real_t damageMultiplier = Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + if ( damageMultiplier <= 0.75 ) + { + dmgGib = DMG_WEAKEST; + } + else if ( damageMultiplier <= 0.85 ) + { + dmgGib = DMG_WEAKER; + } + else if ( damageMultiplier >= 1.25 ) + { + dmgGib = resistance == 0 ? DMG_STRONGEST : DMG_WEAKER; + } + else if ( damageMultiplier >= 1.15 ) + { + dmgGib = resistance == 0 ? DMG_STRONGER : DMG_WEAKER; + } + else if ( resistance > 0 ) + { + dmgGib = DMG_WEAKEST; + } + damage /= (1 + (int)resistance); - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; hit.entity->modHP(-damage); // write the obituary @@ -458,12 +507,12 @@ void spellEffectPoison(Entity& my, spellElement_t& element, Entity* parent, int if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } if ( hitstats->HP <= 0 && parent ) @@ -871,11 +920,34 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i return; } + DamageGib dmgGib = DMG_DEFAULT; + real_t damageMultiplier = Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + if ( damageMultiplier <= 0.75 ) + { + dmgGib = DMG_WEAKEST; + } + else if ( damageMultiplier <= 0.85 ) + { + dmgGib = DMG_WEAKER; + } + else if ( damageMultiplier >= 1.25 ) + { + dmgGib = resistance == 0 ? DMG_STRONGEST : DMG_WEAKER; + } + else if ( damageMultiplier >= 1.15 ) + { + dmgGib = resistance == 0 ? DMG_STRONGER : DMG_WEAKER; + } + else if ( resistance > 0 ) + { + dmgGib = DMG_WEAKEST; + } + int damage = element.damage; damage += damage * ((my.actmagicSpellbookBonus / 100.f) + getBonusFromCasterOfSpellElement(parent, nullptr, &element)); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; damage /= (1 + (int)resistance); - damage *= Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_MAGIC); + damage *= damageMultiplier; if ( parent ) { @@ -912,12 +984,12 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i if ( !strcmp(hitstats->name, "") ) { updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } else { updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, - false, DamageGib::DMG_TODO); + false, dmgGib); } Uint32 color = makeColorRGB(255, 0, 0); @@ -1003,15 +1075,6 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i damage /= (1 + (int)resistance); hit.entity->colliderHandleDamageMagic(damage, my, parent); - if ( my.actmagicProjectileArc > 0 ) - { - spawnMagicTower(parent, my.x, my.y, SPELL_DRAIN_SOUL, nullptr); - } - if ( !(my.actmagicIsOrbiting == 2) ) - { - my.removeLightField(); - list_RemoveNode(my.mynode); - } return; } else if ( hit.entity->behavior == &actChest ) @@ -1020,15 +1083,6 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i damage += (my.actmagicSpellbookBonus * damage); damage /= (1 + (int)resistance); hit.entity->chestHandleDamageMagic(damage, my, parent); - if ( my.actmagicProjectileArc > 0 ) - { - spawnMagicTower(parent, my.x, my.y, SPELL_DRAIN_SOUL, nullptr); - } - if ( !(my.actmagicIsOrbiting == 2) ) - { - my.removeLightField(); - list_RemoveNode(my.mynode); - } return; } else if ( hit.entity->behavior == &actFurniture ) @@ -1074,10 +1128,6 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i } } playSoundEntity(hit.entity, 28, 128); - if ( my.actmagicProjectileArc > 0 ) - { - spawnMagicTower(parent, my.x, my.y, SPELL_DRAIN_SOUL, nullptr); - } } } } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index a9c4465e1..72a49d0fc 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -595,6 +595,8 @@ void IRCHandler_t::handleMessage(std::string& msg) } #endif // !NINTENDO +Uint32 ItemTooltips_t::itemsJsonHashRead = 0; + void ItemTooltips_t::readItemsFromFile() { printlog("loading items...\n"); @@ -686,7 +688,8 @@ void ItemTooltips_t::readItemsFromFile() //itemValueTable.clear(); //itemValueTableByCategory.clear(); - + Uint32 shift = 0; + Uint32 hash = 0; for ( int i = 0; i < NUMITEMS && i < itemsRead; ++i ) { assert(i == tmpItems[i].itemId); @@ -827,6 +830,9 @@ void ItemTooltips_t::readItemsFromFile() items[i].item_slot = ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM; } + hash += (Uint32)((Uint32)items[i].weight << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)items[i].value << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)items[i].level << (shift % 32)); ++shift; /*{ auto pair = std::make_pair(items[i].value, i); auto lower = std::lower_bound(itemValueTable.begin(), itemValueTable.end(), pair, @@ -846,6 +852,16 @@ void ItemTooltips_t::readItemsFromFile() }*/ } + itemsJsonHashRead = hash; + if ( itemsJsonHashRead != kItemsJsonHash ) + { + printlog("[JSON]: Notice: items.json unknown hash, achievements are disabled: %d", itemsJsonHashRead); + } + else + { + printlog("[JSON]: items.json hash verified successfully."); + } + spellItems.clear(); int spellsRead = 0; @@ -8148,6 +8164,10 @@ void Mods::verifyAchievements(const char* fullpath, bool ignoreBaseFolder) { disableSteamAchievements = true; } + if ( ItemTooltips_t::itemsJsonHashRead != ItemTooltips_t::kItemsJsonHash ) + { + disableSteamAchievements = true; + } } bool Mods::isPathInMountedFiles(std::string findStr) diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 5313090f3..27de66fd3 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2715,6 +2715,8 @@ class ItemTooltips_t void setColorFaintText(Uint32 color) { faintTextColor = color; } }; void readItemsFromFile(); + static const Uint32 kItemsJsonHash = 255104943; + static Uint32 itemsJsonHashRead; void readItemLocalizationsFromFile(bool forceLoadBaseDirectory = false); void readTooltipsFromFile(bool forceLoadBaseDirectory = false); std::vector tmpItems; diff --git a/src/player.hpp b/src/player.hpp index 60437dd2d..d1c541426 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -1656,6 +1656,8 @@ class Player static int actionPromptIconSize; static int actionPromptIconOpacity; static int actionPromptIconBackingOpacity; + real_t animDeadPrompt = 0.0; + bool animDeadPromptDisplay = false; int offsetHUDAboveHotbarHeight = 0; void updateEnemyBar(Frame* whichFrame); void updateEnemyBar2(Frame* whichFrame, void* enemyHPDetails); diff --git a/src/scores.cpp b/src/scores.cpp index 5048f4d16..f0cd3603f 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -3202,11 +3202,18 @@ SaveGameInfo getSaveGameInfo(bool singleplayer, int saveIndex) hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday; } if (info.players.size() > info.player_num) { - auto& stats = info.players[info.player_num].stats; - hash += stats.STR + stats.LVL + stats.DEX * stats.INT; - hash += stats.CON * stats.PER + std::min(stats.GOLD, 5000) - stats.CON; - hash += stats.HP - stats.MP; - hash += info.dungeon_lvl; + if ( info.game_version < 410 ) + { + auto& stats = info.players[info.player_num].stats; + hash += stats.STR + stats.LVL + stats.DEX * stats.INT; + hash += stats.CON * stats.PER + std::min(stats.GOLD, 5000) - stats.CON; + hash += stats.HP - stats.MP; + hash += info.dungeon_lvl; + } + else + { + info.computeHash(info.player_num, hash); + } } if (hash != info.hash) { info.hash = 0; @@ -3404,6 +3411,11 @@ void updatePlayerConductsInMainLoop() conductGameChallenges[CONDUCT_CHEATS_ENABLED] = 1; } } + if ( ItemTooltips_t::itemsJsonHashRead != ItemTooltips_t::kItemsJsonHash ) + { + conductGameChallenges[CONDUCT_MODDED] = 1; + Mods::disableSteamAchievements = true; + } if ( !conductGameChallenges[CONDUCT_MODDED_NO_ACHIEVEMENTS] ) { if ( Mods::disableSteamAchievements @@ -5280,6 +5292,139 @@ SteamGlobalStatIndexes getIndexForDeathType(int type) return STEAM_GSTAT_INVALID; } +void SaveGameInfo::computeHash(const int playernum, Uint32& hash) +{ + if ( players.size() <= playernum ) + { + return; + } + + Uint32 shift = 0; + hash += (Uint32)((Uint32)gamekey << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)mapseed << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)gametimer << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)svflags << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)multiplayer_type << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)dungeon_lvl << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)level_track << (shift % 32)); ++shift; + + auto& player = players[playernum]; + hash += (Uint32)((Uint32)player.char_class << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)player.race << (shift % 32)); ++shift; + + for ( auto k : player.kills ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + + hash += (Uint32)((Uint32)conductPenniless << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)conductFoodless << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)conductVegetarian << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)conductIlliterate << (shift % 32)); ++shift; + for ( int i = 0; i < NUM_CONDUCT_CHALLENGES; ++i ) + { + hash += (Uint32)((Uint32)player.additionalConducts[i] << (shift % 32)); ++shift; + } + for ( int i = 0; i < NUM_GAMEPLAY_STATISTICS; ++i ) + { + hash += (Uint32)((Uint32)player.gameStatistics[i] << (shift % 32)); ++shift; + } + for ( int i = 0; i < NUM_HOTBAR_SLOTS; ++i ) + { + hash += (Uint32)((Uint32)player.hotbar[i] << (shift % 32)); ++shift; + for ( int j = 0; j < NUM_HOTBAR_ALTERNATES; ++j ) + { + hash += (Uint32)((Uint32)player.hotbar_alternate[j][i] << (shift % 32)); ++shift; + } + } + for ( auto k : player.spells ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + + std::vector statsArr; + statsArr.push_back(&players[playernum].stats); + for ( auto& s : players[playernum].followers ) + { + statsArr.push_back(&s); + } + + for ( auto stats : statsArr ) + { + hash += (Uint32)((Uint32)stats->type << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->sex << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->appearance << (shift % 32)); ++shift; + + hash += (Uint32)((Uint32)stats->HP << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->maxHP << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->MP << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->maxMP << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->STR << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->DEX << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->CON << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->INT << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->PER << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->CHR << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->EXP << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->LVL << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->GOLD << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->HUNGER << (shift % 32)); ++shift; + + for ( auto k : stats->PROFICIENCIES ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + for ( auto k : stats->EFFECTS ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + for ( auto k : stats->EFFECTS_TIMERS ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + for ( auto k : stats->MISC_FLAGS ) + { + hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; + } + for ( auto& pair : stats->player_equipment ) + { + hash += (Uint32)((Uint32)pair.second << (shift % 32)); ++shift; + } + for ( auto& pair : stats->npc_equipment ) + { + pair.second.computeHash(hash, shift); + } + for ( auto& item : stats->inventory ) + { + item.computeHash(hash, shift); + } + for ( auto& bag : stats->player_lootbags ) + { + hash += (Uint32)((Uint32)bag.first << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)bag.second.spawn_x << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)bag.second.spawn_y << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)bag.second.looted << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)bag.second.spawnedOnGround << (shift % 32)); ++shift; + for ( auto& item : bag.second.items ) + { + item.computeHash(hash, shift); + } + } + } +} + +void SaveGameInfo::Player::stat_t::item_t::computeHash(Uint32& hash, Uint32& shift) +{ + hash += (Uint32)((Uint32)type << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)status << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)appearance << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)beatitude << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)count << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)identified << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)x << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)y << (shift % 32)); ++shift; +} + int saveGame(int saveIndex) { if (gameModeManager.getMode() != GameModeManager_t::GameModes::GAME_MODE_DEFAULT) { return 1; // can't save tutorial games @@ -5300,10 +5445,13 @@ int saveGame(int saveIndex) { // savefile hash info.hash = tm->tm_hour + tm->tm_mday * tm->tm_year + tm->tm_wday + tm->tm_yday; - info.hash += stats[clientnum]->STR + stats[clientnum]->LVL + stats[clientnum]->DEX * stats[clientnum]->INT; - info.hash += stats[clientnum]->CON * stats[clientnum]->PER + std::min(stats[clientnum]->GOLD, 5000) - stats[clientnum]->CON; - info.hash += stats[clientnum]->HP - stats[clientnum]->MP; - info.hash += currentlevel; + if ( info.game_version < 410 ) + { + info.hash += stats[clientnum]->STR + stats[clientnum]->LVL + stats[clientnum]->DEX * stats[clientnum]->INT; + info.hash += stats[clientnum]->CON * stats[clientnum]->PER + std::min(stats[clientnum]->GOLD, 5000) - stats[clientnum]->CON; + info.hash += stats[clientnum]->HP - stats[clientnum]->MP; + info.hash += currentlevel; + } // game info info.gamename = stats[clientnum]->name; @@ -5682,6 +5830,11 @@ int saveGame(int saveIndex) { static ConsoleVariable cvar_saveText("/save_text_format", true); + if ( info.game_version >= 410 ) + { + info.computeHash(info.player_num, info.hash); + } + char path[PATH_MAX] = ""; std::string savefile = setSaveGameFileName(multiplayer == SINGLE, SaveFileType::JSON, saveIndex); completePath(path, savefile.c_str(), outputdir); diff --git a/src/scores.hpp b/src/scores.hpp index 8b67471c8..8209eae7c 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -427,6 +427,7 @@ struct SaveGameInfo { fp->property("y", y); return true; } + void computeHash(Uint32& hash, Uint32& shift); }; struct lootbag_t @@ -590,6 +591,8 @@ struct SaveGameInfo { fp->property("map_messages", map_messages); return true; } + + void computeHash(const int playernum, Uint32& hash); }; int saveGame(int saveIndex = savegameCurrentFileIndex); diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index d21efd3cd..62688f00c 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -78,7 +78,7 @@ Stat::Stat(Sint32 sprite) : this->RANDOM_MAXMP = 0; this->RANDOM_MP = 0; int c; - for ( c = 0; c < std::max(NUMPROFICIENCIES, NUMEFFECTS); c++ ) + for ( c = 0; c < std::max(NUMPROFICIENCIES, NUMEFFECTS); c++ ) { if ( c < NUMPROFICIENCIES ) { diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 96915008a..1e7dfa6ae 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -10182,6 +10182,71 @@ static void checkControllerState(int player) { controllerFrame->setHollow(true); } +void HUDDrawGameEndHint(const int player, SDL_Rect rect) +{ + if ( multiplayer == CLIENT || multiplayer == SERVER ) + { + bool everyonedead = true; + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( players[i] ) + { + if ( multiplayer == SERVER && (!client_disconnected[i] && players[i]->entity) ) + { + everyonedead = false; + } + else if ( multiplayer == CLIENT && players[i]->entity ) + { + everyonedead = false; + } + } + } + + if ( players[player]->bControlEnabled ) + { + players[player]->hud.animDeadPromptDisplay = true; + } + + if ( everyonedead && players[player]->hud.animDeadPromptDisplay ) + { + static ConsoleVariable cvar_anim_dead_prompt_speed("/anim_dead_prompt_speed", 0.003); + players[player]->hud.animDeadPrompt += *cvar_anim_dead_prompt_speed; + if ( players[player]->hud.animDeadPrompt >= 1.0 ) + { + players[player]->hud.animDeadPrompt = 0.0; + } + rect.x += rect.w / 2; + rect.y += 8 - 1; + if ( players[player]->hud.xpFrame && !players[player]->hud.xpFrame->isDisabled() ) + { + rect.y += players[player]->hud.xpFrame->getSize().y; + rect.y += players[player]->hud.xpFrame->getSize().h; + } + if ( auto textGet = Text::get(Language::get(6052), smallfont_outline, makeColorRGB(255, 255, 255), 0) ) + { + Uint8 r, g, b, a; + getColor(hudColors.characterSheetRed, &r, &g, &b, nullptr); + real_t opacity = 0.5 + .4 * (1.0 * cos(players[player]->hud.animDeadPrompt * 2 * PI) + 1.0); + Uint32 color = makeColor(r, g, b, std::max(0.25, std::min(opacity, 1.0)) * 255); + rect.x -= textGet->getWidth() / 2; + textGet->drawColor(SDL_Rect{ 0, 0, 0, 0 }, SDL_Rect{ rect.x, rect.y, 0, 0 }, + SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }, + color); + } + } + else + { + players[player]->hud.animDeadPromptDisplay = false; + players[player]->hud.animDeadPrompt = 0.0; + } + } + else + { + players[player]->hud.animDeadPromptDisplay = false; + players[player]->hud.animDeadPrompt = 0.0; + } +} + void Player::HUD_t::processHUD() { const SDL_Rect hudSize{ @@ -10198,6 +10263,9 @@ void Player::HUD_t::processHUD() hudFrame->setHollow(true); hudFrame->setBorder(0); hudFrame->setOwner(player.playernum); + hudFrame->setDrawCallback([](const Widget& widget, SDL_Rect rect) { + HUDDrawGameEndHint(widget.getOwner(), rect); + }); } if ( !minotaurSharedDisplay && player.playernum == 0 ) @@ -32354,7 +32422,13 @@ void sliderSkillsheetUpdateSelectorOnHighlight(const int player, Slider* slider) } } +#ifdef NINTENDO +// this causes nintendo to crash unless it is false!! +static ConsoleVariable cvar_skillsheet_optimise("/skillsheet_optimise", false); +#else static ConsoleVariable cvar_skillsheet_optimise("/skillsheet_optimise", true); +#endif + void Player::SkillSheet_t::processSkillSheet() { //DebugTimers.addTimePoint("skill 1", "start"); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 6f01adf7f..b66f2b84f 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -3058,7 +3058,8 @@ namespace MainMenu { file->property("extra_life_enabled", extra_life_enabled); file->property("cheats_enabled", cheats_enabled); file->property("skipintro", skipintro); - file->property("use_model_cache", useModelCache); + bool no = false; + file->property("use_model_cache", no); file->property("debug_keys_enabled", enableDebugKeys); file->property("port_number", port_number); file->propertyVersion("show_lobby_code", version >= 12, show_lobby_code); @@ -11682,7 +11683,7 @@ namespace MainMenu { stats[index]->playerRace = RACE_INCUBUS; auto race = card->findButton("race"); if (race) { - race->setText(Language::get(3827)); + race->setText(Language::get(5375)); } auto incubus = subframe ? subframe->findButton("Incubus") : nullptr; if (incubus) { @@ -14648,9 +14649,6 @@ namespace MainMenu { auto card = static_cast(button.getParent()); - // select a random sex - stats[index]->sex = (sex_t)(RNG.getU8() % 2); - // select a random race // there are 9 legal races that the player can select from the start. if (enabledDLCPack1 && enabledDLCPack2) { @@ -14676,16 +14674,28 @@ namespace MainMenu { stats[index]->appearance = 0; } - // update sex buttons after race selection: - // we might have chosen a succubus or incubus + // select a random sex (unless you're a succubus or an incubus) + if (stats[index]->playerRace == RACE_SUCCUBUS) { + stats[index]->sex = sex_t::FEMALE; + } + else if (stats[index]->playerRace == RACE_INCUBUS) { + stats[index]->sex = sex_t::MALE; + } + else { + stats[index]->sex = (sex_t)(RNG.getU8() % 2); + } + + // update sex buttons auto bottom = card->findFrame("bottom"); if (bottom) { auto male_button = bottom->findButton("male"); - auto female_button = bottom->findButton("female"); - if (male_button && female_button) { + if (male_button) { male_button->setPressed(stats[index]->sex == MALE); male_button->setColor(stats[index]->sex == MALE ? makeColorRGB(255, 255, 255) : makeColorRGB(127, 127, 127)); male_button->setHighlightColor(stats[index]->sex == MALE ? makeColorRGB(255, 255, 255) : makeColorRGB(127, 127, 127)); + } + auto female_button = bottom->findButton("female"); + if (female_button) { female_button->setPressed(stats[index]->sex == FEMALE); female_button->setColor(stats[index]->sex == FEMALE ? makeColorRGB(255, 255, 255) : makeColorRGB(127, 127, 127)); female_button->setHighlightColor(stats[index]->sex == FEMALE ? makeColorRGB(255, 255, 255) : makeColorRGB(127, 127, 127)); @@ -22756,7 +22766,7 @@ namespace MainMenu { void(*banner_funcs[])(Button&) = { [](Button&) { // banner #1 - openURLTryWithOverlay("https://www.baronygame.com/blog/life-after-death-announcement"); + openURLTryWithOverlay("https://www.baronygame.com/blog/410-update-summary"); }, [](Button&) { // banner #2 openDLCPrompt(enabledDLCPack1 ? 1 : 0);