Skip to content

Commit

Permalink
feat: chain for weapons (sword, axe, club and wand/rod)
Browse files Browse the repository at this point in the history
  • Loading branch information
dudantas committed Feb 28, 2024
1 parent 9a08805 commit 81a32a6
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 54 deletions.
6 changes: 5 additions & 1 deletion config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,15 @@ multiplierSpeedOnFist = 5
maxSpeedOnFist = 500
disableLegacyRaids = false
disableMonsterArmor = false
combatChainDelay = 50
minElementalResistance = -200
maxElementalResistance = 200
maxDamageReflection = 200

-- Chain system
toggleChainSystem = true
combatChainDelay = 50
combatChainTargets = 5

-- Global server Save
-- NOTE: globalServerSaveNotifyDuration in minutes
globalServerSaveNotifyMessage = true
Expand Down
1 change: 1 addition & 0 deletions data/items/items.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22330,6 +22330,7 @@
<attribute key="unproperly" value="true"/>
<attribute key="weaponType" value="axe"/>
<attribute key="slot" value="hand"/>
<attribute key="chain" value="0.9"/>
</attribute>
</item>
<item id="8097" article="a" name="solar axe">
Expand Down
2 changes: 2 additions & 0 deletions src/config/config_enums.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum ConfigKey_t : uint16_t {
CLASSIC_ATTACK_SPEED,
CLEAN_PROTECTION_ZONES,
COMBAT_CHAIN_DELAY,
COMBAT_CHAIN_TARGETS,
COMPRESSION_LEVEL,
CONVERT_UNSAFE_SCRIPTS,
CORE_DIRECTORY,
Expand Down Expand Up @@ -273,6 +274,7 @@ enum ConfigKey_t : uint16_t {
TIBIADROME_CONCOCTION_DURATION,
TIBIADROME_CONCOCTION_TICK_TYPE,
TOGGLE_ATTACK_SPEED_ONFIST,
TOGGLE_CHAIN_SYSTEM,
TOGGLE_DOWNLOAD_MAP,
TOGGLE_FREE_QUEST,
TOGGLE_GOLD_POUCH_ALLOW_ANYTHING,
Expand Down
2 changes: 2 additions & 0 deletions src/config/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ bool ConfigManager::load() {
loadBoolConfig(L, TELEPORT_PLAYER_TO_VOCATION_ROOM, "teleportPlayerToVocationRoom", true);
loadBoolConfig(L, TELEPORT_SUMMONS, "teleportSummons", false);
loadBoolConfig(L, TOGGLE_ATTACK_SPEED_ONFIST, "toggleAttackSpeedOnFist", false);
loadBoolConfig(L, TOGGLE_CHAIN_SYSTEM, "toggleChainSystem", true);
loadBoolConfig(L, TOGGLE_DOWNLOAD_MAP, "toggleDownloadMap", false);
loadBoolConfig(L, TOGGLE_FREE_QUEST, "toggleFreeQuest", true);
loadBoolConfig(L, TOGGLE_GOLD_POUCH_ALLOW_ANYTHING, "toggleGoldPouchAllowAnything", false);
Expand Down Expand Up @@ -215,6 +216,7 @@ bool ConfigManager::load() {
loadIntConfig(L, BUY_BLESS_COMMAND_FEE, "buyBlessCommandFee", 0);
loadIntConfig(L, CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, "checkExpiredMarketOffersEachMinutes", 60);
loadIntConfig(L, COMBAT_CHAIN_DELAY, "combatChainDelay", 50);
loadIntConfig(L, COMBAT_CHAIN_TARGETS, "combatChainTargets", 5);
loadIntConfig(L, COMPRESSION_LEVEL, "packetCompressionLevel", 6);
loadIntConfig(L, CRITICALCHANCE, "criticalChance", 10);
loadIntConfig(L, DAY_KILLS_TO_RED, "dayKillsToRedSkull", 3);
Expand Down
128 changes: 94 additions & 34 deletions src/creatures/combat/combat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ bool Combat::setCallback(CallBackParam_t key) {

case CALLBACK_PARAM_CHAINVALUE: {
params.chainCallback = std::make_unique<ChainCallback>();
params.chainCallback->setFromLua(true);
return true;
}

Expand All @@ -521,6 +522,11 @@ bool Combat::setCallback(CallBackParam_t key) {
return false;
}

void Combat::setChainCallback(uint8_t chainTargets, uint8_t chainDistance, bool backtracking) {
params.chainCallback = std::make_unique<ChainCallback>(chainTargets, chainDistance, backtracking);
g_logger().debug("ChainCallback created: {}, with targets: {}, distance: {}, backtracking: {}", params.chainCallback != nullptr, chainTargets, chainDistance, backtracking);
}

CallBack* Combat::getCallback(CallBackParam_t key) {
switch (key) {
case CALLBACK_PARAM_LEVELMAGICVALUE:
Expand Down Expand Up @@ -930,6 +936,74 @@ void Combat::doChainEffect(const Position &origin, const Position &dest, uint8_t
}
}

void Combat::setupChain(const std::shared_ptr<Weapon> &weapon) {
if (!weapon) {
return;
}

static std::list<uint32_t> areaList = {
0, 0, 0, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 3, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 0, 0, 0,
};
auto area = std::make_unique<AreaCombat>();
area->setupArea(areaList, 7);
setArea(area);
g_logger().trace("Weapon: {}, element type: {}", Item::items[weapon->getID()].name, weapon->params.combatType);
setParam(COMBAT_PARAM_TYPE, weapon->params.combatType);
const auto &weaponType = weapon->getWeaponType();
if (weaponType != WEAPON_WAND) {
setParam(COMBAT_PARAM_BLOCKARMOR, true);
}

weapon->params.chainCallback = std::make_unique<ChainCallback>();

auto setCommonValues = [this, weapon](double formula, SoundEffect_t impactSound, uint32_t effect) {
double weaponSkillFormula = weapon->getChainSkillValue();
setPlayerCombatValues(COMBAT_FORMULA_SKILL, 0, 0, weaponSkillFormula ? weaponSkillFormula : formula, 0);
setParam(COMBAT_PARAM_IMPACTSOUND, impactSound);
setParam(COMBAT_PARAM_EFFECT, effect);
setParam(COMBAT_PARAM_BLOCKARMOR, true);
};

setChainCallback(g_configManager().getNumber(COMBAT_CHAIN_TARGETS, __FUNCTION__), 1, true);

switch (weaponType) {
case WEAPON_SWORD:
setCommonValues(1.1, MELEE_ATK_SWORD, CONST_ME_SLASH);
break;

case WEAPON_CLUB:
setCommonValues(0.7, MELEE_ATK_CLUB, CONST_ME_BLACK_BLOOD);
break;

case WEAPON_AXE:
setCommonValues(0.9, MELEE_ATK_AXE, CONST_ANI_WHIRLWINDAXE);
break;
}

if (weaponType == WEAPON_WAND) {
static const std::map<CombatType_t, std::pair<MagicEffectClasses, MagicEffectClasses>> elementEffects = {
{COMBAT_DEATHDAMAGE, {CONST_ME_MORTAREA, CONST_ME_BLACK_BLOOD}},
{COMBAT_ENERGYDAMAGE, {CONST_ME_ENERGYAREA, CONST_ME_PINK_ENERGY_SPARK}},
{COMBAT_FIREDAMAGE, {CONST_ME_FIREATTACK, CONST_ME_FIREATTACK}},
{COMBAT_ICEDAMAGE, {CONST_ME_ICEATTACK, CONST_ME_ICEATTACK}},
{COMBAT_EARTHDAMAGE, {CONST_ME_STONES, CONST_ME_POISONAREA}},
};

auto it = elementEffects.find(weapon->getElementType());
if (it != elementEffects.end()) {
setPlayerCombatValues(COMBAT_FORMULA_SKILL, 0, 0, 1.0, 0);
setParam(COMBAT_PARAM_EFFECT, it->second.first);
setParam(COMBAT_PARAM_CHAIN_EFFECT, it->second.second);
}
}
}

bool Combat::doCombatChain(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, bool aggressive) const {
metrics::method_latency measure(__METHOD_NAME__);
if (!params.chainCallback) {
Expand All @@ -939,7 +1013,7 @@ bool Combat::doCombatChain(std::shared_ptr<Creature> caster, std::shared_ptr<Cre
uint8_t maxTargets;
uint8_t chainDistance;
bool backtracking = false;
params.chainCallback->onChainCombat(caster, maxTargets, chainDistance, backtracking);
params.chainCallback->getChainValues(caster, maxTargets, chainDistance, backtracking);
auto targets = pickChainTargets(caster, params, chainDistance, maxTargets, backtracking, aggressive, target);

g_logger().debug("[{}] Chain targets: {}", __FUNCTION__, targets.size());
Expand Down Expand Up @@ -1476,42 +1550,16 @@ void ValueCallback::getMinMaxValues(std::shared_ptr<Player> player, CombatDamage
case COMBAT_FORMULA_SKILL: {
// onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor)
std::shared_ptr<Item> tool = player->getWeapon();
const WeaponShared_ptr weapon = g_weapons().getWeapon(tool);
std::shared_ptr<Item> item = nullptr;

const auto & weapon = g_weapons().getWeapon(tool);
int32_t attackSkill = 0;
float attackFactor = 0;
if (weapon) {
attackValue = tool->getAttack();
if (tool->getWeaponType() == WEAPON_AMMO) {
item = player->getWeapon(true);
if (item) {
attackValue += item->getAttack();
}
}

CombatType_t elementType = weapon->getElementType();
damage.secondary.type = elementType;

if (elementType != COMBAT_NONE) {
if (weapon) {
elementAttack = weapon->getElementDamageValue();
shouldCalculateSecondaryDamage = true;
attackValue += elementAttack;
}
} else {
shouldCalculateSecondaryDamage = false;
}

if (useCharges) {
auto charges = tool->getAttribute<uint16_t>(ItemAttribute_t::CHARGES);
if (charges != 0) {
g_game().transformItem(tool, tool->getID(), charges - 1);
}
}
shouldCalculateSecondaryDamage = weapon->calculateSkillFormula(player, attackSkill, attackValue, attackFactor, elementAttack, damage, useCharges);
}

lua_pushnumber(L, player->getWeaponSkill(item ? item : tool));
lua_pushnumber(L, attackSkill);
lua_pushnumber(L, attackValue);
lua_pushnumber(L, player->getAttackFactor());
lua_pushnumber(L, attackFactor);
parameters += 3;
break;
}
Expand Down Expand Up @@ -1635,7 +1683,19 @@ void TargetCallback::onTargetCombat(std::shared_ptr<Creature> creature, std::sha

//**********************************************************//

void ChainCallback::onChainCombat(std::shared_ptr<Creature> creature, uint8_t &maxTargets, uint8_t &chainDistance, bool &backtracking) const {
void ChainCallback::getChainValues(const std::shared_ptr<Creature> &creature, uint8_t &maxTargets, uint8_t &chainDistance, bool &backtracking) {
if (m_fromLua) {
onChainCombat(creature, maxTargets, chainDistance, backtracking);
return;
}

if (m_chainTargets && m_chainDistance) {
maxTargets = m_chainTargets;
chainDistance = m_chainDistance;
backtracking = m_backtracking;
}
}
void ChainCallback::onChainCombat(std::shared_ptr<Creature> creature, uint8_t &maxTargets, uint8_t &chainDistance, bool &backtracking) {
// onChainCombat(creature)
if (!scriptInterface->reserveScriptEnv()) {
g_logger().error("[ChainCallback::onTargetCombat - Creature {}] "
Expand Down
24 changes: 21 additions & 3 deletions src/creatures/combat/combat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Item;
class Spell;
class Player;
class MatrixArea;
class Weapon;

// for luascript callback
class ValueCallback final : public CallBack {
Expand Down Expand Up @@ -59,7 +60,22 @@ class TargetCallback final : public CallBack {

class ChainCallback final : public CallBack {
public:
void onChainCombat(std::shared_ptr<Creature> creature, uint8_t &chainTargets, uint8_t &chainDistance, bool &backtracking) const;
ChainCallback() = default;
ChainCallback(uint8_t &chainTargets, uint8_t &chainDistance, bool &backtracking) : m_chainDistance(chainDistance), m_chainTargets(chainTargets), m_backtracking(backtracking) { }

void getChainValues(const std::shared_ptr<Creature> &creature, uint8_t &maxTargets, uint8_t &chainDistance, bool &backtracking);
void setFromLua(bool fromLua) {
m_fromLua = fromLua;
}

private:
void onChainCombat(std::shared_ptr<Creature> creature, uint8_t &chainTargets, uint8_t &chainDistance, bool &backtracking);

uint8_t m_chainTargets = 0;
uint8_t m_chainDistance = 0;
bool m_backtracking = false;
bool m_fromLua = false;
bool m_loaded = false;
};

class ChainPickerCallback final : public CallBack {
Expand Down Expand Up @@ -293,6 +309,7 @@ class Combat {
bool doCombat(std::shared_ptr<Creature> caster, const Position &pos) const;

bool setCallback(CallBackParam_t key);
void setChainCallback(uint8_t chainTargets, uint8_t chainDistance, bool backtracking);
CallBack* getCallback(CallBackParam_t key);

bool setParam(CombatParam_t param, uint32_t value);
Expand Down Expand Up @@ -328,6 +345,9 @@ class Combat {
*/
void setRuneSpellName(const std::string &value);

void setupChain(const std::shared_ptr<Weapon> &weapon);
bool doCombatChain(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, bool aggressive) const;

private:
static void doChainEffect(const Position &origin, const Position &pos, uint8_t effect);
static std::vector<std::pair<Position, std::vector<uint32_t>>> pickChainTargets(std::shared_ptr<Creature> caster, const CombatParams &params, uint8_t chainDistance, uint8_t maxTargets, bool aggressive, bool backtracking, std::shared_ptr<Creature> initialTarget = nullptr);
Expand Down Expand Up @@ -376,8 +396,6 @@ class Combat {
int32_t getLevelFormula(std::shared_ptr<Player> player, const std::shared_ptr<Spell> wheelSpell, const CombatDamage &damage) const;
CombatDamage getCombatDamage(std::shared_ptr<Creature> creature, std::shared_ptr<Creature> target) const;

bool doCombatChain(std::shared_ptr<Creature> caster, std::shared_ptr<Creature> target, bool aggressive) const;

// configureable
CombatParams params;

Expand Down
26 changes: 18 additions & 8 deletions src/items/functions/item/item_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "items/weapons/weapons.hpp"
#include "lua/creature/movement.hpp"
#include "utils/pugicast.hpp"
#include "creatures/combat/combat.hpp"

void ItemParse::initParse(const std::string &tmpStrValue, pugi::xml_node attributeNode, pugi::xml_attribute valueAttribute, ItemType &itemType) {
// Parse all item attributes
Expand Down Expand Up @@ -1003,10 +1004,11 @@ void ItemParse::createAndRegisterScript(ItemType &itemType, pugi::xml_node attri
} else {
weapon = std::make_shared<WeaponMelee>(&g_weapons().getScriptInterface());
}

weapon->weaponType = weaponType;
itemType.weaponType = weapon->weaponType;
weapon->configureWeapon(itemType);
g_logger().debug("Created weapon with type '{}'", getWeaponName(weaponType));
g_logger().trace("Created weapon with type '{}'", getWeaponName(weaponType));
}
uint32_t fromDamage = 0;
uint32_t toDamage = 0;
Expand Down Expand Up @@ -1063,11 +1065,11 @@ void ItemParse::createAndRegisterScript(ItemType &itemType, pugi::xml_node attri
} else if (stringKey == "level") {
auto numberValue = subValueAttribute.as_uint();
if (moveevent) {
g_logger().debug("Added required moveevent level '{}'", numberValue);
g_logger().trace("Added required moveevent level '{}'", numberValue);
moveevent->setRequiredLevel(numberValue);
moveevent->setWieldInfo(WIELDINFO_LEVEL);
} else if (weapon) {
g_logger().debug("Added required weapon level '{}'", numberValue);
g_logger().trace("Added required weapon level '{}'", numberValue);
weapon->setRequiredLevel(numberValue);
weapon->setWieldInfo(WIELDINFO_LEVEL);
}
Expand Down Expand Up @@ -1162,16 +1164,24 @@ void ItemParse::createAndRegisterScript(ItemType &itemType, pugi::xml_node attri
} else {
g_logger().warn("[{}] - wandtype '{}' does not exist", __FUNCTION__, elementName);
}
} else if (stringKey == "chain" && weapon) {
auto value = subValueAttribute.as_double();
weapon->setChainSkillValue(value);
g_logger().trace("Found chain skill value '{}' for weapon: {}", value, itemType.name);
}
}

if (weapon) {
if (auto weaponWand = dynamic_pointer_cast<WeaponWand>(weapon)) {
g_logger().debug("Added weapon damage from '{}', to '{}'", fromDamage, toDamage);
g_logger().trace("Added weapon damage from '{}', to '{}'", fromDamage, toDamage);
weaponWand->setMinChange(fromDamage);
weaponWand->setMaxChange(toDamage);
}

const auto &weaponType = weapon->getWeaponType();
auto combat = weapon->getCombat();
combat->setupChain(weapon);

if (weapon->getWieldInfo() != 0) {
itemType.wieldInfo = weapon->getWieldInfo();
itemType.vocationString = weapon->getVocationString();
Expand All @@ -1195,7 +1205,7 @@ void ItemParse::parseUnscriptedItems(const std::string_view &tmpStrValue, pugi::
auto tokens = split(scriptName.data(), ';');
for (const auto &token : tokens) {
if (token == "moveevent") {
g_logger().debug("Registering moveevent for item id '{}', name '{}'", itemType.id, itemType.name);
g_logger().trace("Registering moveevent for item id '{}', name '{}'", itemType.id, itemType.name);
MoveEvent_t eventType = MOVE_EVENT_NONE;
for (auto subAttributeNode : attributeNode.children()) {
pugi::xml_attribute subKeyAttribute = subAttributeNode.attribute("key");
Expand All @@ -1212,7 +1222,7 @@ void ItemParse::parseUnscriptedItems(const std::string_view &tmpStrValue, pugi::
if (stringKey == "eventtype") {
const auto &eventTypeName = asLowerCaseString(subValueAttribute.as_string());
eventType = getMoveEventType(eventTypeName);
g_logger().debug("Found event type '{}'", eventTypeName);
g_logger().trace("Found event type '{}'", eventTypeName);
break;
}
}
Expand All @@ -1226,7 +1236,7 @@ void ItemParse::parseUnscriptedItems(const std::string_view &tmpStrValue, pugi::
}
} else if (token == "weapon") {
WeaponType_t weaponType;
g_logger().debug("Registering weapon for item id '{}', name '{}'", itemType.id, itemType.name);
g_logger().trace("Registering weapon for item id '{}', name '{}'", itemType.id, itemType.name);
for (auto subAttributeNode : attributeNode.children()) {
pugi::xml_attribute subKeyAttribute = subAttributeNode.attribute("key");
if (!subKeyAttribute) {
Expand All @@ -1241,7 +1251,7 @@ void ItemParse::parseUnscriptedItems(const std::string_view &tmpStrValue, pugi::
auto stringKey = asLowerCaseString(subKeyAttribute.as_string());
if (stringKey == "weapontype") {
weaponType = getWeaponType(subValueAttribute.as_string());
g_logger().debug("Found weapon type '{}''", subValueAttribute.as_string());
g_logger().trace("Found weapon type '{}''", subValueAttribute.as_string());
break;
}
}
Expand Down
Loading

0 comments on commit 81a32a6

Please sign in to comment.