diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua new file mode 100644 index 00000000000..3de9bc41825 --- /dev/null +++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua @@ -0,0 +1,110 @@ +local mType = Game.createMonsterType("Doctor Marrow") +local monster = {} + +monster.description = "Doctor Marrow" +monster.experience = 0 +monster.outfit = { + lookType = 1611, + lookHead = 57, + lookBody = 0, + lookLegs = 0, + lookFeet = 95, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 1800 +monster.maxHealth = 1800 +monster.race = "blood" +monster.corpse = 18074 +monster.speed = 125 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + critChance = 10, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "You can't stop the future!", yell = false }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 }, +} + +monster.defenses = { + defense = 54, + armor = 59, + mitigation = 3.7, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onThink = function(monster, interval) end + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +mType.onDisappear = function(monster, creature) end + +mType.onMove = function(monster, creature, fromPosition, toPosition) end + +mType.onSay = function(monster, creature, type, message) end + +mType:register(monster) diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua index 68983418159..21a10d94aac 100644 --- a/data-otservbr-global/scripts/lib/register_monster_type.lua +++ b/data-otservbr-global/scripts/lib/register_monster_type.lua @@ -199,6 +199,9 @@ registerMonsterType.flags = function(mtype, mask) if mask.flags.canPushCreatures ~= nil then mtype:canPushCreatures(mask.flags.canPushCreatures) end + if mask.flags.critChance ~= nil then + mtype:critChance(mask.flags.critChance) + end if mask.flags.targetDistance then mtype:targetDistance(math.max(1, mask.flags.targetDistance)) end diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 8b650253567..75cec14c30f 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1139,41 +1139,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptrgetPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - // Charm low blow rune) - if (target && target->getMonster() && damage.primary.type != COMBAT_HEALING) { - uint16_t playerCharmRaceid = caster->getPlayer()->parseRacebyCharm(CHARM_LOW, false, 0); - if (playerCharmRaceid != 0) { - const auto mType = g_monsters().getMonsterType(target->getName()); - if (mType && playerCharmRaceid == mType->info.raceid) { - const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); - if (charm) { - chance += charm->percent; - g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); - } - } - } - } - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier()) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (target && caster && params.distanceEffect != CONST_ANI_NONE) { @@ -1194,27 +1160,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (damage.primary.type != COMBAT_HEALING && chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier() > 0) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatHealthFunc, &damage); } @@ -1231,15 +1177,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptrgetPosition(), params.impactEffect); } - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (caster && target && params.distanceEffect != CONST_ANI_NONE) { @@ -1260,15 +1198,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatManaFunc, &damage); } @@ -2080,3 +2010,64 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { creature->addCondition(conditionCopy); } } + +void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) { + if (damage.extension || !caster || damage.primary.type == COMBAT_HEALING) { + return; + } + + g_logger().debug("[Combat::applyExtensions] - Applying extensions for {} on {}. Initial damage: {}", caster->getName(), target ? target->getName() : "null", damage.primary.value); + + // Critical hit + uint16_t chance = 0; + int32_t multiplier = 50; + auto player = caster->getPlayer(); + auto monster = caster->getMonster(); + if (player) { + chance = player->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE); + multiplier = player->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE); + + if (target) { + uint16_t playerCharmRaceid = player->parseRacebyCharm(CHARM_LOW, false, 0); + if (playerCharmRaceid != 0) { + const auto mType = g_monsters().getMonsterType(target->getName()); + if (mType && playerCharmRaceid == mType->info.raceid) { + const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); + if (charm) { + chance += charm->percent; + g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); + } + } + } + } + } else if (monster) { + chance = monster->critChance(); + } + + multiplier += damage.criticalDamage; + multiplier = 1 + multiplier / 100; + chance += (uint16_t)damage.criticalChance; + + if (chance != 0 && uniform_random(1, 100) <= chance) { + damage.critical = true; + damage.primary.value *= multiplier; + damage.secondary.value *= multiplier; + } + + if (player) { + // Fatal hit (onslaught) + if (auto playerWeapon = player->getInventoryItem(CONST_SLOT_LEFT); + playerWeapon != nullptr && playerWeapon->getTier() > 0) { + double_t fatalChance = playerWeapon->getFatalChance(); + double_t randomChance = uniform_random(0, 10000) / 100; + if (fatalChance > 0 && randomChance < fatalChance) { + damage.fatal = true; + damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); + damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); + } + } + } else if (monster) { + damage.primary.value *= monster->getAttackMultiplier(); + damage.secondary.value *= monster->getAttackMultiplier(); + } +} diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 16062cb685d..2bb2d0a3589 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -260,6 +260,8 @@ class Combat { Combat(const Combat &) = delete; Combat &operator=(const Combat &) = delete; + static void applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); + static void doCombatHealth(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); static void doCombatHealth(std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms); diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index eef9aaa3830..0ce9432ea68 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -811,12 +811,6 @@ void Monster::doAttacking(uint32_t interval) { bool resetTicks = interval != 0; attackTicks += interval; - float forgeAttackBonus = 0; - if (monsterForgeClassification > ForgeClassifications_t::FORGE_NORMAL_MONSTER) { - uint16_t damageBase = 3; - forgeAttackBonus = static_cast(damageBase + 100) / 100.f; - } - const Position &myPos = getPosition(); const Position &targetPos = attackedCreature->getPosition(); @@ -834,20 +828,8 @@ void Monster::doAttacking(uint32_t interval) { updateLook = false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - minCombatValue = spellBlock.minCombatValue * multiplier; - maxCombatValue = spellBlock.maxCombatValue * multiplier; - - if (maxCombatValue <= 0 && forgeAttackBonus > 0) { - minCombatValue *= static_cast(forgeAttackBonus); - maxCombatValue *= static_cast(forgeAttackBonus); - } + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; if (spellBlock.spell == nullptr) { continue; @@ -1912,15 +1894,8 @@ bool Monster::getCombatValues(int32_t &min, int32_t &max) { return false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - min = minCombatValue * multiplier; - max = maxCombatValue * multiplier; + min = minCombatValue; + max = maxCombatValue; return true; } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 5f589af0e28..858b3ac8dd8 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -77,13 +77,13 @@ class Monster final : public Creature { return mType->info.race; } float getMitigation() const override { - return mType->info.mitigation; + return mType->info.mitigation * getDefenseMultiplier(); } int32_t getArmor() const override { - return mType->info.armor; + return mType->info.armor * getDefenseMultiplier(); } int32_t getDefense() const override { - return mType->info.defense; + return mType->info.defense * getDefenseMultiplier(); } Faction_t getFaction() const override { @@ -126,6 +126,9 @@ class Monster final : public Creature { bool canSeeInvisibility() const override { return isImmune(CONDITION_INVISIBLE); } + uint16_t critChance() const { + return mType->info.critChance; + } uint32_t getManaCost() const { return mType->info.manaCost; } @@ -325,6 +328,16 @@ class Monster final : public Creature { bool isImmune(ConditionType_t conditionType) const override; bool isImmune(CombatType_t combatType) const override; + float getAttackMultiplier() const { + float multiplier = mType->getAttackMultiplier(); + return multiplier * std::pow(1.03f, getForgeStack()); + } + + float getDefenseMultiplier() const { + float multiplier = mType->getDefenseMultiplier(); + return multiplier * std::pow(1.01f, getForgeStack()); + } + private: CreatureWeakHashMap friendList; CreatureIDList targetIDList; @@ -436,14 +449,4 @@ class Monster final : public Creature { void doRandomStep(Direction &nextDirection, bool &result); void onConditionStatusChange(const ConditionType_t &type); - - float getAttackMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.03f, getForgeStack()); - } - - float getDefenseMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.01f, getForgeStack()); - } }; diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index a13792fd297..b214b478e3b 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -128,6 +128,7 @@ class MonsterType { int32_t changeTargetChance = 0; int32_t defense = 0; int32_t armor = 0; + uint16_t critChance = 0; int32_t strategiesTargetNearest = 0; int32_t strategiesTargetHealth = 0; int32_t strategiesTargetDamage = 0; diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 1beea8de66b..ba77c49a90a 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -280,6 +280,20 @@ int MonsterTypeFunctions::luaMonsterTypeCanPushCreatures(lua_State* L) { return 1; } +int MonsterTypeFunctions::luaMonsterTypeCritChance(lua_State* L) { + // get: monsterType:critChance() set: monsterType:critChance(int) + const auto monsterType = getUserdataShared(L, 1); + if (monsterType) { + if (lua_gettop(L) == 2) { + monsterType->info.critChance = getNumber(L, 2); + } + lua_pushnumber(L, monsterType->info.critChance); + } else { + lua_pushnil(L); + } + return 1; +} + int32_t MonsterTypeFunctions::luaMonsterTypeName(lua_State* L) { // get: monsterType:name() set: monsterType:name(name) const auto monsterType = getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index d9c513d2bce..9d892219250 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -35,6 +35,8 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "canPushItems", MonsterTypeFunctions::luaMonsterTypeCanPushItems); registerMethod(L, "MonsterType", "canPushCreatures", MonsterTypeFunctions::luaMonsterTypeCanPushCreatures); + registerMethod(L, "MonsterType", "critChance", MonsterTypeFunctions::luaMonsterTypeCritChance); + registerMethod(L, "MonsterType", "name", MonsterTypeFunctions::luaMonsterTypeName); registerMethod(L, "MonsterType", "nameDescription", MonsterTypeFunctions::luaMonsterTypeNameDescription); @@ -260,10 +262,5 @@ 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); + static int luaMonsterTypeCritChance(lua_State* L); };