Skip to content

Commit

Permalink
feat: monster critical chance and multipliers refactor (#1679)
Browse files Browse the repository at this point in the history
  • Loading branch information
luan authored Oct 6, 2023
1 parent dbe353c commit e133007
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 122 deletions.
110 changes: 110 additions & 0 deletions data-otservbr-global/monster/bosses/doctor_marrow.lua
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions data-otservbr-global/scripts/lib/register_monster_type.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
139 changes: 65 additions & 74 deletions src/creatures/combat/combat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1139,41 +1139,7 @@ void Combat::doCombatHealth(std::shared_ptr<Creature> caster, std::shared_ptr<Cr
}
}

if (!damage.extension && caster && caster->getPlayer()) {
// 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<int32_t>(std::round(damage.primary.value * 0.6));
damage.secondary.value += static_cast<int32_t>(std::round(damage.secondary.value * 0.6));
}
}
}
applyExtensions(caster, target, damage, params);

if (canCombat) {
if (target && caster && params.distanceEffect != CONST_ANI_NONE) {
Expand All @@ -1194,27 +1160,7 @@ void Combat::doCombatHealth(std::shared_ptr<Creature> caster, std::shared_ptr<Cr
}

void Combat::doCombatHealth(std::shared_ptr<Creature> caster, const Position &position, const std::unique_ptr<AreaCombat> &area, CombatDamage &damage, const CombatParams &params) {
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<int32_t>(std::round(damage.primary.value * 0.6));
damage.secondary.value += static_cast<int32_t>(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);
}
Expand All @@ -1231,15 +1177,7 @@ void Combat::doCombatMana(std::shared_ptr<Creature> caster, std::shared_ptr<Crea
g_game().addMagicEffect(target->getPosition(), 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) {
Expand All @@ -1260,15 +1198,7 @@ void Combat::doCombatMana(std::shared_ptr<Creature> caster, std::shared_ptr<Crea
}

void Combat::doCombatMana(std::shared_ptr<Creature> caster, const Position &position, const std::unique_ptr<AreaCombat> &area, CombatDamage &damage, const CombatParams &params) {
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);
}
Expand Down Expand Up @@ -2080,3 +2010,64 @@ void MagicField::onStepInField(const std::shared_ptr<Creature> &creature) {
creature->addCondition(conditionCopy);
}
}

void Combat::applyExtensions(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, CombatDamage &damage, const CombatParams &params) {
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<int32_t>(std::round(damage.primary.value * 0.6));
damage.secondary.value += static_cast<int32_t>(std::round(damage.secondary.value * 0.6));
}
}
} else if (monster) {
damage.primary.value *= monster->getAttackMultiplier();
damage.secondary.value *= monster->getAttackMultiplier();
}
}
2 changes: 2 additions & 0 deletions src/creatures/combat/combat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ class Combat {
Combat(const Combat &) = delete;
Combat &operator=(const Combat &) = delete;

static void applyExtensions(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, CombatDamage &damage, const CombatParams &params);

static void doCombatHealth(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, CombatDamage &damage, const CombatParams &params);
static void doCombatHealth(std::shared_ptr<Creature> caster, const Position &position, const std::unique_ptr<AreaCombat> &area, CombatDamage &damage, const CombatParams &params);

Expand Down
33 changes: 4 additions & 29 deletions src/creatures/monsters/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(damageBase + 100) / 100.f;
}

const Position &myPos = getPosition();
const Position &targetPos = attackedCreature->getPosition();

Expand All @@ -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<int32_t>(forgeAttackBonus);
maxCombatValue *= static_cast<int32_t>(forgeAttackBonus);
}
minCombatValue = spellBlock.minCombatValue;
maxCombatValue = spellBlock.maxCombatValue;

if (spellBlock.spell == nullptr) {
continue;
Expand Down Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit e133007

Please sign in to comment.