From c55242ac8c7e336def378b754fce8364ba15c0b9 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 20 May 2023 15:34:44 -0300 Subject: [PATCH] feat: hazard system (#1091) This modification introduces the implementation of the Hazard System. The Hazard System is an advanced game mechanic that offers players the opportunity to increase the difficulty and rewards of their hunts in Gnomprona. The system allows players to increase the hazard level of a hunt, which both increases the damage dealt by monsters and the amount of loot and experience rewards received. The hazard level is adjusted individually by players, but in a team, it's the lowest hazard level among the players that counts for the whole team. The benefits of increasing the hazard level include greater loot and experience, as well as a higher chance of spawning a Plunder Patriarch when killing a monster. However, with a higher hazard level, all damage dealt by monsters increases, as does the chance for monsters to land critical hits and dodge attacks. Furthermore, the chance of generating Primal Pods when killing a creature increases with a higher hazard level. Additionally, this modification also implements the mechanic associated with the "Primal Ordeal" quest, where players must defeat the boss "The Primal Menace" to increase hazard qualification. The recommended combat strategy includes focusing on the Primal Pack Beasts, and dealing with the threats of Primal Pods that can transform into the powerful Fungosaurus creature. --- config.lua.dist | 14 +++ .../scripts/lib/register_monster_type.lua | 17 +++ .../creaturescripts/others/login_events.lua | 4 +- .../scripts/hazard/hazard_system.lua | 110 ++++++++++++++++++ .../scripts/lib/register_monster_type.lua | 17 +++ data/events/scripts/monster.lua | 15 +++ src/creatures/monsters/monster.cpp | 14 +++ src/creatures/monsters/monster.h | 23 ++++ src/creatures/monsters/monsters.h | 6 + src/game/game.cpp | 51 ++++++++ src/game/game.h | 2 + src/items/functions/item/item_parse.hpp | 2 +- src/lua/functions/core/game/lua_enums.cpp | 2 + .../creatures/monster/monster_functions.cpp | 11 ++ .../creatures/monster/monster_functions.hpp | 4 + .../monster/monster_type_functions.cpp | 72 ++++++++++++ .../monster/monster_type_functions.hpp | 11 ++ .../creatures/player/player_functions.cpp | 28 +++++ .../creatures/player/player_functions.hpp | 7 ++ src/server/network/protocol/protocolgame.cpp | 34 +++++- src/server/network/protocol/protocolgame.h | 3 + src/utils/const.hpp | 3 + src/utils/utils_definitions.hpp | 1 + 23 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 data-otservbr-global/scripts/hazard/hazard_system.lua diff --git a/config.lua.dist b/config.lua.dist index 89d0473129b..33c1c70c1e4 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -123,6 +123,20 @@ boostedBossSlot = true boostedBossLootBonus = 250 boostedBossKillBonus = 3 +-- Hazard system +toogleHazardSystem = true +hazardCriticalInterval = 2000 +hazardCriticalMultiplier = 25 +hazardDamageMultiplier = 200 +hazardDodgeMultiplier = 85 +hazardPodsDropMultiplier = 87 +hazardPodsTimeToDamage = 2000 +hazardPodsTimeToSpawn = 4000 +hazardExpBonusMultiplier = 2 +hazardLootBonusMultiplier = 2 +hazardPodsDamage = 5 +hazardSpawnPlunderMultiplier = 25 + -- Familiar system -- NOTE: the time will be divided by 2 to get half the value, the familiar lasts 15 minutes by default and the cooldown of the spell is 30 minutes -- Only change it here if you know what you are doing or to make testing easier with familiars diff --git a/data-canary/scripts/lib/register_monster_type.lua b/data-canary/scripts/lib/register_monster_type.lua index 6e7437ad207..66143db6afe 100644 --- a/data-canary/scripts/lib/register_monster_type.lua +++ b/data-canary/scripts/lib/register_monster_type.lua @@ -506,6 +506,23 @@ registerMonsterType.defenses = function(mtype, mask) end end +registerMonsterType.hazard = function(mtype, mask) + if mask.hazard ~= nil then + if mask.hazard.criticalChance ~= nil then + mtype:hazardSystemCrit(mask.hazard.criticalChance) + end + if mask.hazard.canDodge ~= nil then + mtype:hazardSystemDodge(mask.hazard.canDodge) + end + if mask.hazard.canSpawnPod ~= nil then + mtype:hazardSystemSpawnPod(mask.hazard.canSpawnPod) + end + if mask.hazard.canDealMoreDamage ~= nil then + mtype:hazardSystemDamageBoost(mask.hazard.canDealMoreDamage) + end + end +end + local function loadcastSound(effect, incomingLua, mtype) -- Throw shoottype if effect == CONST_ANI_SPEAR or diff --git a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua index a630373caf9..ab5f3ba484b 100644 --- a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua +++ b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua @@ -116,7 +116,9 @@ function loginEvents.onLogin(player) -- Secret Library "SecretLibraryKill", -- The Dream Courts - "DreamCourtsKill" + "DreamCourtsKill", + -- Hazard System + "HazardSystemCombat" } for i = 1, #events do diff --git a/data-otservbr-global/scripts/hazard/hazard_system.lua b/data-otservbr-global/scripts/hazard/hazard_system.lua new file mode 100644 index 00000000000..a68dbb191b2 --- /dev/null +++ b/data-otservbr-global/scripts/hazard/hazard_system.lua @@ -0,0 +1,110 @@ +local hazardSystemStepPod = MoveEvent() + +function hazardSystemStepPod.onStepIn(creature, item, position, fromPosition) + if not configManager.getBoolean(configKeys.TOGGLE_HAZARDSYSTEM) then + item:remove() + return + end + + local player = creature:getPlayer() + if not player then + return + end + + local timer = item:getCustomAttribute("HazardSystem_PodTimer") + if timer then + local timeMs = os.time() * 1000 + timer = timeMs - timer + if timer >= configManager.getNumber(configKeys.HAZARD_PODS_TIME_TO_DAMAGE) and timer < configManager.getNumber(configKeys.HAZARD_PODS_TIME_TO_SPAWN) then + player:sendCancelMessage("You stepped too late on the primal pod and it explodes.") + player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) + local damage = math.ceil((player:getMaxHealth() * configManager.getNumber(configKeys.HAZARD_PODS_DAMAGE)) / 100) + local points = player:getHazardSystemPoints() + if points ~= 0 then + damage = math.ceil((damage * (100 + points)) / 100) + end + damage = damage + 500 + doTargetCombatHealth(0, player, COMBAT_LIFEDRAIN, -damage, -damage, CONST_ME_DRAWBLOOD) + end + end + + item:remove() + return true +end + +hazardSystemStepPod:id(ITEM_PRIMAL_POD) +hazardSystemStepPod:register() + +local SpawnHazardSystemFungosaurus = function(position) + local tile = Tile(position) + if tile then + local podItem = tile:getItemById(ITEM_PRIMAL_POD) + if podItem then + local monster = Game.createMonster("Fungosaurus", position, false, true) + if monster then + monster:say("The primal pod explode and wild emerges from it.") + end + podItem:remove() + end + end +end + +local hazardSystemSpawnPod = CreatureEvent("HazardSystemCombat") + +function hazardSystemSpawnPod.onKill(player, creature, lastHit) + if not configManager.getBoolean(configKeys.TOGGLE_HAZARDSYSTEM) then + return true + end + + local monster = creature:getMonster() + if not creature or not monster or not monster:isOnHazardSystem() then + return true + end + + local points = player:getHazardSystemPoints() + if points > 0 then + local party = player:getParty() + if party then + for _, member in ipairs(party:getMembers()) do + if member and member:getHazardSystemPoints() < points then + points = member:getHazardSystemPoints() + end + end + + local leader = party:getLeader() + if leader and leader:getHazardSystemPoints() < points then + points = leader:getHazardSystemPoints() + end + end + end + + if points == 0 then + return true + end + + local chanceTo = math.random(1, 10000) + if chanceTo <= (points * configManager.getNumber(configKeys.HAZARD_PODS_DROP_MULTIPLIER)) then + local closesestPosition = player:getClosestFreePosition(monster:getPosition(), 4, true) + local primalPod = Game.createItem(ITEM_PRIMAL_POD, 1, closesestPosition.x == 0 and monster:getPosition() or closesestPosition) + if primalPod then + primalPod:setCustomAttribute("HazardSystem_PodTimer", os.time() * 1000) + local podPos = primalPod:getPosition() + addEvent(SpawnHazardSystemFungosaurus, configManager.getNumber(configKeys.HAZARD_PODS_TIME_TO_SPAWN), podPos) + end + return true + end + + chanceTo = math.random(1, 10000) + if chanceTo <= (points * configManager.getNumber(configKeys.HAZARDSYSTEM_SPAWN_PLUNDER_MULTIPLIER)) then + local closesestPosition = player:getClosestFreePosition(monster:getPosition(), 4, true) + local monster = Game.createMonster("Plunder Patriarch", closesestPosition.x == 0 and monster:getPosition() or closesestPosition, false, true) + if monster then + monster:say("The Plunder Patriarch rises from the ashes.") + end + return true + end + + return true +end + +hazardSystemSpawnPod:register() diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua index f7217a44791..c222d733a65 100644 --- a/data-otservbr-global/scripts/lib/register_monster_type.lua +++ b/data-otservbr-global/scripts/lib/register_monster_type.lua @@ -530,6 +530,23 @@ registerMonsterType.defenses = function(mtype, mask) end end +registerMonsterType.hazard = function(mtype, mask) + if mask.hazard ~= nil then + if mask.hazard.criticalChance ~= nil then + mtype:hazardSystemCrit(mask.hazard.criticalChance) + end + if mask.hazard.canDodge ~= nil then + mtype:hazardSystemDodge(mask.hazard.canDodge) + end + if mask.hazard.canSpawnPod ~= nil then + mtype:hazardSystemSpawnPod(mask.hazard.canSpawnPod) + end + if mask.hazard.canDealMoreDamage ~= nil then + mtype:hazardSystemDamageBoost(mask.hazard.canDealMoreDamage) + end + end +end + local function loadcastSound(effect, incomingLua, mtype) -- Throw shoottype if effect == CONST_ANI_SPEAR or diff --git a/data/events/scripts/monster.lua b/data/events/scripts/monster.lua index 5c1a0c587a8..c14f3cf2252 100644 --- a/data/events/scripts/monster.lua +++ b/data/events/scripts/monster.lua @@ -34,6 +34,7 @@ function Monster:onDropLoot(corpse) if not player or player:getStamina() > 840 then local monsterLoot = mType:getLoot() local charmBonus = false + local hazardMsg = false if player and mType and mType:raceId() > 0 then local charm = player:getCharmMonsterType(CHARM_GUT) if charm and charm:raceId() == mType:raceId() then @@ -63,6 +64,17 @@ function Monster:onDropLoot(corpse) Spdlog.warn(string.format("[1][Monster:onDropLoot] - Could not add loot item to boosted monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) end end + if self:isOnHazardSystem() and player ~= nil then + local chanceTo = math.random(1, 100) + if chanceTo <= (2 * player:getHazardSystemPoints() * configManager.getNumber(configKeys.HAZARDSYSTEM_LOOT_BONUS_MULTIPLIER)) then + local podItem = corpse:createLootItem(monsterLoot[i], charmBonus, preyChanceBoost) + if not podItem then + Spdlog.warn(string.format("[Monster:onDropLoot] - Could not add loot item to hazard monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) + else + hazardMsg = true + end + end + end if not item then Spdlog.warn(string.format("[2][Monster:onDropLoot] - Could not add loot item to monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) end @@ -132,6 +144,9 @@ function Monster:onDropLoot(corpse) if charmBonus then text = text .. " (active charm bonus)" end + if hazardMsg then + text = text .. " (Hazard system)" + end local party = player:getParty() if party then party:broadcastPartyLoot(text) diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 01f2e68d03a..8fe252251d6 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -302,6 +302,14 @@ void Monster::removeFriend(Creature* creature) { } } +void Monster::handleHazardSystem(Creature &creature) const { + // Hazard system (Icon UI) + auto player = creature.getPlayer(); + if (player && isOnHazardSystem()) { + player->incrementeHazardSystemReference(); + } +} + void Monster::addTarget(Creature* creature, bool pushFront /* = false*/) { assert(creature != this); if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { @@ -313,6 +321,8 @@ void Monster::addTarget(Creature* creature, bool pushFront /* = false*/) { } if (!master && getFaction() != FACTION_DEFAULT && creature->getPlayer()) totalPlayersOnScreen++; + + handleHazardSystem(*creature); } } @@ -327,6 +337,8 @@ void Monster::removeTarget(Creature* creature) { totalPlayersOnScreen--; } + handleHazardSystem(*creature); + creature->decrementReferenceCounter(); targetList.erase(it); } @@ -348,6 +360,7 @@ void Monster::updateTargetList() { while (targetIterator != targetList.end()) { Creature* creature = *targetIterator; if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + handleHazardSystem(*creature); creature->decrementReferenceCounter(); targetIterator = targetList.erase(targetIterator); } else { @@ -374,6 +387,7 @@ void Monster::clearTargetList() { void Monster::clearFriendList() { for (Creature* creature : friendList) { + handleHazardSystem(*creature); creature->decrementReferenceCounter(); } friendList.clear(); diff --git a/src/creatures/monsters/monster.h b/src/creatures/monsters/monster.h index d00a2d74e4e..7704aff0ecf 100644 --- a/src/creatures/monsters/monster.h +++ b/src/creatures/monsters/monster.h @@ -256,6 +256,28 @@ class Monster final : public Creature { return mType->info.raceid; } + // Hazard system + bool isOnHazardSystem() const { + return mType->info.hazardSystemCritChance != 0 || mType->info.canSpawnPod || mType->info.canDodge || mType->info.canDamageBoost; + } + + bool getHazardSystemDodge() const { + return mType->info.canDodge; + } + + bool getHazardSystemSpawnPod() const { + return mType->info.canSpawnPod; + } + + bool getHazardSystemDamageBoost() const { + return mType->info.canDamageBoost; + } + + uint16_t getHazardSystemCritChance() const { + return mType->info.hazardSystemCritChance; + } + // Hazard end + void updateTargetList(); void clearTargetList(); void clearFriendList(); @@ -360,6 +382,7 @@ class Monster final : public Creature { void addFriend(Creature* creature); void removeFriend(Creature* creature); + void handleHazardSystem(Creature &creature) const; void addTarget(Creature* creature, bool pushFront = false); void removeTarget(Creature* creature); diff --git a/src/creatures/monsters/monsters.h b/src/creatures/monsters/monsters.h index c57c15c07d5..0e7b5724f52 100644 --- a/src/creatures/monsters/monsters.h +++ b/src/creatures/monsters/monsters.h @@ -83,6 +83,12 @@ class MonsterType { uint64_t experience = 0; + // Hazard system (0-10000), divide by 100 gives us % + uint16_t hazardSystemCritChance = 0; + bool canDamageBoost = false; + bool canSpawnPod = false; + bool canDodge = false; + uint32_t manaCost = 0; uint32_t yellChance = 0; uint32_t yellSpeedTicks = 0; diff --git a/src/game/game.cpp b/src/game/game.cpp index 957bddbee55..c2a4c29ad81 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5639,6 +5639,7 @@ bool Game::combatBlockHit(CombatDamage &damage, Creature* attacker, Creature* ta canHeal = true; } } + primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); damage.primary.value = -damage.primary.value; @@ -5674,6 +5675,7 @@ bool Game::combatBlockHit(CombatDamage &damage, Creature* attacker, Creature* ta canHeal = true; } } + secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field); damage.secondary.value = -damage.secondary.value; @@ -5790,6 +5792,44 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo } } +void Game::handleHazardSystemAttack(CombatDamage &damage, Player* player, const Monster* monster, bool isPlayerAttacker) { + if (damage.primary.value != 0 && monster->isOnHazardSystem()) { + if (isPlayerAttacker) { + player->parseAttackDealtHazardSystem(damage, monster); + } else { + player->parseAttackRecvHazardSystem(damage, monster); + } + } +} + +void Game::notifySpectators(const SpectatorHashSet &spectators, const Position &targetPos, Player* attackerPlayer, Monster* targetMonster) { + if (!spectators.empty()) { + for (Creature* spectator : spectators) { + if (!spectator) { + continue; + } + + const auto tmpPlayer = spectator->getPlayer(); + if (!tmpPlayer || tmpPlayer->getPosition().z != targetPos.z) { + continue; + } + + std::stringstream ss; + ss << ucfirst(targetMonster->getNameDescription()) << " has dodged"; + if (tmpPlayer == attackerPlayer) { + ss << " your attack."; + attackerPlayer->sendCancelMessage(ss.str()); + ss << " (Hazard)"; + attackerPlayer->sendTextMessage(MESSAGE_DAMAGE_OTHERS, ss.str()); + } else { + ss << " an attack by " << attackerPlayer->getName() << ". (Hazard)"; + tmpPlayer->sendTextMessage(MESSAGE_DAMAGE_OTHERS, ss.str()); + } + } + addMagicEffect(targetPos, CONST_ME_DODGE); + } +} + bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage &damage, bool isEvent /*= false*/) { using namespace std; const Position &targetPos = target->getPosition(); @@ -5954,6 +5994,17 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage SpectatorHashSet spectators; map.getSpectators(spectators, targetPos, true, true); + if (targetPlayer && attackerMonster) { + handleHazardSystemAttack(damage, targetPlayer, attackerMonster, false); + } else if (attackerPlayer && targetMonster) { + handleHazardSystemAttack(damage, attackerPlayer, targetMonster, true); + + if (damage.primary.value == 0 && damage.secondary.value == 0) { + notifySpectators(spectators, targetPos, attackerPlayer, targetMonster); + return true; + } + } + if (damage.fatal) { addMagicEffect(spectators, targetPos, CONST_ME_FATAL); } else if (damage.critical) { diff --git a/src/game/game.h b/src/game/game.h index 551f68d880f..86e3124225c 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -412,6 +412,8 @@ class Game { void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t &color, uint8_t &effect); + void handleHazardSystemAttack(CombatDamage &damage, Player* player, const Monster* monster, bool isPlayerAttacker); + void notifySpectators(const SpectatorHashSet &spectators, const Position &targetPos, Player* attackerPlayer, Monster* targetMonster); bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage &damage, bool isEvent = false); void applyCharmRune(const Monster* targetMonster, Player* attackerPlayer, Creature* target, const int32_t &realDamage) const; void applyManaLeech( diff --git a/src/items/functions/item/item_parse.hpp b/src/items/functions/item/item_parse.hpp index c34c1388cc4..16215bf712e 100644 --- a/src/items/functions/item/item_parse.hpp +++ b/src/items/functions/item/item_parse.hpp @@ -135,7 +135,7 @@ const phmap::flat_hash_map ItemParseAttribut { "walkstack", ITEM_PARSE_WALKSTACK }, { "blocking", ITEM_PARSE_BLOCK_SOLID }, { "allowdistread", ITEM_PARSE_ALLOWDISTREAD }, - { "imbuementslot", ITEM_PARSE_IMBUEMENT }, + { "imbuementslot", ITEM_PARSE_IMBUEMENT } }; const phmap::flat_hash_map ItemTypesMap = { diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index c43cfdae057..3b9fc312678 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -818,6 +818,8 @@ void LuaEnums::initItemIdEnums(lua_State* L) { registerEnum(L, ITEM_FORGE_SLIVER); registerEnum(L, ITEM_FORGE_CORE); + registerEnum(L, ITEM_PRIMAL_POD); + registerEnum(L, ItemID_t::HIRELING_LAMP); } diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index 79a75e0bf75..56bfdf222fd 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -513,3 +513,14 @@ int MonsterFunctions::luaMonsterGetName(lua_State* L) { pushString(L, monster->getName()); return 1; } + +int MonsterFunctions::luaMonsterIsOnHazardSystem(lua_State* L) { + // monster:isOnHazardSystem() + const Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->isOnHazardSystem()); + } else { + lua_pushnil(L); + } + return 1; +} diff --git a/src/lua/functions/creatures/monster/monster_functions.hpp b/src/lua/functions/creatures/monster/monster_functions.hpp index bc64adace04..461d0413cca 100644 --- a/src/lua/functions/creatures/monster/monster_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_functions.hpp @@ -57,6 +57,8 @@ class MonsterFunctions final : LuaScriptInterface { registerMethod(L, "Monster", "getName", MonsterFunctions::luaMonsterGetName); + registerMethod(L, "Monster", "isOnHazardSystem", MonsterFunctions::luaMonsterIsOnHazardSystem); + CharmFunctions::init(L); LootFunctions::init(L); MonsterSpellFunctions::init(L); @@ -110,6 +112,8 @@ class MonsterFunctions final : LuaScriptInterface { static int luaMonsterGetName(lua_State* L); + static int luaMonsterIsOnHazardSystem(lua_State* L); + friend class CreatureFunctions; }; diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 0c113480154..41e9cea2c85 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -1633,3 +1633,75 @@ int MonsterTypeFunctions::luaMonsterTypedeathSound(lua_State* L) { return 1; } + +int MonsterTypeFunctions::luaMonsterTypeHazardSystemCrit(lua_State* L) { + // get: monsterType:hazardSystemCrit() set: monsterType:hazardSystemCrit(chance) + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_TYPE_NOT_FOUND)); + return 0; + } + + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.hazardSystemCritChance); + } else { + monsterType->info.hazardSystemCritChance = std::min(10000, static_cast(getNumber(L, 2) * 100)); + pushBoolean(L, true); + } + return 1; +} + +int MonsterTypeFunctions::luaMonsterTypeHazardSystemDodge(lua_State* L) { + // get: monsterType:hazardSystemDodge() set: monsterType:hazardSystemDodge(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_TYPE_NOT_FOUND)); + return 0; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canDodge); + } else { + monsterType->info.canDodge = getBoolean(L, 2); + pushBoolean(L, true); + } + return 1; +} + +int MonsterTypeFunctions::luaMonsterTypeHazardSystemSpawnPod(lua_State* L) { + // get: monsterType:hazardSystemSpawnPod() set: monsterType:hazardSystemSpawnPod(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_TYPE_NOT_FOUND)); + return 0; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canSpawnPod); + } else { + monsterType->info.canSpawnPod = getBoolean(L, 2); + pushBoolean(L, true); + } + return 1; +} + +int MonsterTypeFunctions::luaMonsterTypeHazardSystemDamageBoost(lua_State* L) { + // get: monsterType:hazardSystemDamageBoost() set: monsterType:hazardSystemDamageBoost(bool) + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_TYPE_NOT_FOUND)); + return 0; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canDamageBoost); + } else { + monsterType->info.canDamageBoost = getBoolean(L, 2); + pushBoolean(L, true); + } + return 1; +} diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index 910a07a51ad..4e08da99c5e 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -138,6 +138,11 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "addSound", MonsterTypeFunctions::luaMonsterTypeAddSound); registerMethod(L, "MonsterType", "getSounds", MonsterTypeFunctions::luaMonsterTypeGetSounds); registerMethod(L, "MonsterType", "deathSound", MonsterTypeFunctions::luaMonsterTypedeathSound); + + registerMethod(L, "MonsterType", "hazardSystemCrit", MonsterTypeFunctions::luaMonsterTypeHazardSystemCrit); + registerMethod(L, "MonsterType", "hazardSystemDodge", MonsterTypeFunctions::luaMonsterTypeHazardSystemDodge); + registerMethod(L, "MonsterType", "hazardSystemSpawnPod", MonsterTypeFunctions::luaMonsterTypeHazardSystemSpawnPod); + registerMethod(L, "MonsterType", "hazardSystemDamageBoost", MonsterTypeFunctions::luaMonsterTypeHazardSystemDamageBoost); } private: @@ -260,6 +265,12 @@ class MonsterTypeFunctions final : LuaScriptInterface { static int luaMonsterTypeAddSound(lua_State* L); static int luaMonsterTypeGetSounds(lua_State* L); static int luaMonsterTypedeathSound(lua_State* L); + + // Hazard system + static int luaMonsterTypeHazardSystemCrit(lua_State* L); + static int luaMonsterTypeHazardSystemDodge(lua_State* L); + static int luaMonsterTypeHazardSystemSpawnPod(lua_State* L); + static int luaMonsterTypeHazardSystemDamageBoost(lua_State* L); }; #endif // SRC_LUA_FUNCTIONS_CREATURES_MONSTER_MONSTER_TYPE_FUNCTIONS_HPP_ diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 79f7ecb7e00..94e99988816 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3422,3 +3422,31 @@ int PlayerFunctions::luaPlayerRemoveGroupFlag(lua_State* L) { player->removeFlag(getNumber(L, 2)); return 1; } + +// Hazard system +int PlayerFunctions::luaPlayerAddHazardSystemPoints(lua_State* L) { + // player:addHazardSystemPoints(amount) + Player* player = getUserdata(L, 1); + if (!player) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + player->addHazardSystemPoints(getNumber(L, 2, 0)); + pushBoolean(L, true); + return 1; +} + +int PlayerFunctions::luaPlayerGetHazardSystemPoints(lua_State* L) { + // player:getHazardSystemPoints() + const auto player = getUserdata(L, 1); + if (!player) { + pushBoolean(L, false); + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + lua_pushnumber(L, player->getHazardSystemPoints()); + return 1; +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index db48ff65056..2f49fb6fc3e 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -305,6 +305,9 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "setGroupFlag", PlayerFunctions::luaPlayerSetGroupFlag); registerMethod(L, "Player", "removeGroupFlag", PlayerFunctions::luaPlayerRemoveGroupFlag); + registerMethod(L, "Player", "addHazardSystemPoints", PlayerFunctions::luaPlayerAddHazardSystemPoints); + registerMethod(L, "Player", "getHazardSystemPoints", PlayerFunctions::luaPlayerGetHazardSystemPoints); + GroupFunctions::init(L); GuildFunctions::init(L); MountFunctions::init(L); @@ -601,6 +604,10 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerSetGroupFlag(lua_State* L); static int luaPlayerRemoveGroupFlag(lua_State* L); + // Hazard system + static int luaPlayerAddHazardSystemPoints(lua_State* L); + static int luaPlayerGetHazardSystemPoints(lua_State* L); + friend class CreatureFunctions; }; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 5dfa8e3a206..7dfbd834fa2 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2878,13 +2878,22 @@ void ProtocolGame::sendCreatureIcon(const Creature* creature) { // Type 14 for this msg.addByte(14); // 0 = no icon, 1 = we'll send an icon - msg.addByte(icon != CREATUREICON_NONE); + auto creaturePlayer = creature->getPlayer(); + auto useHazard = g_configManager().getBoolean(TOGGLE_HAZARDSYSTEM); if (icon != CREATUREICON_NONE) { + msg.addByte(icon != CREATUREICON_NONE); // Has icon msg.addByte(icon); // Creature update msg.addByte(1); // Used for the life in the new quest msg.add(0); + } else if (useHazard && !oldProtocol && creaturePlayer && creaturePlayer->getHazardSystemReference() > 0 && creaturePlayer->getHazardSystemPoints() > 0) { + msg.addByte(0x01); // Has icon + msg.addByte(22); // Hazard icon + msg.addByte(0); + msg.add(creaturePlayer->getHazardSystemPoints()); + } else { + msg.addByte(0x00); // Has icon } writeToOutputBuffer(msg); } @@ -6476,6 +6485,7 @@ void ProtocolGame::AddCreature(NetworkMessage &msg, const Creature* creature, bo CreatureIcon_t icon; auto sendIcon = false; + auto useHazard = g_configManager().getBoolean(TOGGLE_HAZARDSYSTEM); if (!oldProtocol) { if (otherPlayer) { icon = creature->getIcon(); @@ -6485,6 +6495,10 @@ void ProtocolGame::AddCreature(NetworkMessage &msg, const Creature* creature, bo msg.addByte(icon); msg.addByte(1); msg.add(0); + } else if (useHazard && otherPlayer->getHazardSystemReference() > 0 && otherPlayer->getHazardSystemPoints() > 0) { + msg.addByte(22); // Hazard icon + msg.addByte(0); + msg.add(otherPlayer->getHazardSystemPoints()); } } else { if (auto monster = creature->getMonster(); @@ -7908,3 +7922,21 @@ void ProtocolGame::sendDoubleSoundEffect( msg.addByte(0x00); // Breaking the effects loop writeToOutputBuffer(msg); } + +void ProtocolGame::reloadHazardSystemIcon(uint16_t reference) { + if (oldProtocol || !g_configManager().getBoolean(TOGGLE_HAZARDSYSTEM)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x8B); + msg.add(player->getID()); + msg.addByte(14); + msg.addByte(reference != 0 ? 0x01 : 0x00); + if (reference != 0) { + msg.addByte(22); // Icon ID + msg.addByte(0); + msg.add(player->getHazardSystemPoints()); + } + writeToOutputBuffer(msg); +} diff --git a/src/server/network/protocol/protocolgame.h b/src/server/network/protocol/protocolgame.h index 8ed1483e3ae..4ce483d2638 100644 --- a/src/server/network/protocol/protocolgame.h +++ b/src/server/network/protocol/protocolgame.h @@ -486,6 +486,9 @@ class ProtocolGame final : public Protocol { void sendSingleSoundEffect(const Position &pos, SoundEffect_t id, SourceEffect_t source); void sendDoubleSoundEffect(const Position &pos, SoundEffect_t mainSoundId, SourceEffect_t mainSource, SoundEffect_t secondarySoundId, SourceEffect_t secondarySource); + + // Hazard system + void reloadHazardSystemIcon(uint16_t reference); }; #endif // SRC_SERVER_NETWORK_PROTOCOL_PROTOCOLGAME_H_ diff --git a/src/utils/const.hpp b/src/utils/const.hpp index 2c72f06ee39..6c88501d136 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -32,6 +32,9 @@ static constexpr int32_t STORAGEVALUE_PODIUM = 30020; static constexpr int32_t STORAGEVALUE_AUTO_LOOT = 30063; static constexpr int32_t STORAGEVALUE_DAILYREWARD = 14898; static constexpr int32_t STORAGEVALUE_BESTIARYKILLCOUNT = 61305000; // Can get up to 2000 storages! +// Hazard system storage +static constexpr int32_t STORAGEVALUE_HAZARDCOUNT = 112550; + // Reserved player storage key ranges; // [10000000 - 20000000]; static constexpr int32_t PSTRG_RESERVED_RANGE_START = 10000000; diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp index cbe51507dd6..c8f751efa97 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -665,6 +665,7 @@ enum ItemID_t : uint16_t { ITEM_FORGE_CORE = 37110, ITEM_EXALTATION_CHEST = 37561, ITEM_PODIUM_OF_VIGOUR = 38707, + ITEM_PRIMAL_POD = 39176, ITEM_NONE = 0 };