diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml
index b681928..bc2c272 100644
--- a/data/XML/vocations.xml
+++ b/data/XML/vocations.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/data/talkactions/scripts/melee_damage.lua b/data/talkactions/scripts/melee_damage.lua
deleted file mode 100644
index 0b5bb47..0000000
--- a/data/talkactions/scripts/melee_damage.lua
+++ /dev/null
@@ -1,139 +0,0 @@
-local weaponSkillsConfig = {
- [WEAPON_SWORD] = SKILL_SWORD,
- [WEAPON_CLUB] = SKILL_CLUB,
- [WEAPON_AXE] = SKILL_AXE,
- [WEAPON_DISTANCE] = SKILL_DISTANCE
-}
-
-local function getWeapon(player)
- local itemLeft = player:getSlotItem(CONST_SLOT_LEFT)
- if itemLeft and itemLeft:getType():getWeaponType() ~= WEAPON_NONE and itemLeft:getType():getWeaponType() ~= WEAPON_SHIELD and itemLeft:getType():getWeaponType() ~= WEAPON_AMMO then
- return itemLeft
- end
-
- local itemRight = player:getSlotItem(CONST_SLOT_RIGHT)
- if itemRight and itemRight:getType():getWeaponType() ~= WEAPON_NONE and itemRight:getType():getWeaponType() ~= WEAPON_SHIELD and itemRight:getType():getWeaponType() ~= WEAPON_AMMO then
- return itemRight
- end
-end
-
-local function getDamageFormula(attack, attackSkill, fightMode)
- local damage = attack
- if fightMode == FIGHTMODE_ATTACK then
- damage = math.floor(damage + 2 * damage / 10)
- elseif fightMode == FIGHTMODE_DEFENSE then
- damage = math.floor(damage - 4 * damage / 10)
- end
-
- local formula = math.floor((5 * (attackSkill) + 50) * damage)
- return formula
-end
-
-local function getDamage(attack, attackSkill, fightMode, random1, random2)
- local formula = getDamageFormula(attack, attackSkill, fightMode)
- local randResult = math.floor(random1 % 100);
- local damage = -math.floor((math.ceil(formula * ((random2 % 100 + randResult) / 2) / 10000.)));
- return damage
-end
-
-local function getMinDamage(attack, attackSkill, fightMode)
- local formula = getDamageFormula(attack, attackSkill, fightMode)
- local damage = -math.floor((math.ceil(formula * (0 / 2) / 10000.)));
- return damage
-end
-
-local function getMaxDamage(attack, attackSkill, fightMode)
- local formula = getDamageFormula(attack, attackSkill, fightMode)
- local damage = -math.floor((math.ceil(formula * (198 / 2) / 10000.)));
- return damage
-end
-
-local function setVocationDamageIncrease(vocationId, damage)
- local knightCloseAttackDamageIncreasePercent = configManager.getNumber(configKeys.KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT)
- if knightCloseAttackDamageIncreasePercent ~= -1 then
- damage = math.floor(damage + damage * knightCloseAttackDamageIncreasePercent / 100);
- end
-
- return damage
-end
-
-local function getDefense(creature, random1, random2)
- local totalDefense = creature:getType():getDefense() + 1
- local defenseSkill = creature:getType():getSkill()
-
- local formula = math.floor((5 * (defenseSkill) + 50) * totalDefense)
- local randresult = math.floor(random1 % 100)
-
- return math.floor(formula * ((random2 % 100 + randresult) / 2) / 10000.)
-end
-
-local function rshift(x, by)
- return math.floor(x / 2 ^ by)
-end
-
-local function getArmor(creature, rand)
- local armor = creature:getType():getArmor()
- if armor > 1 then
- return rand % rshift(armor, 1) + rshift(armor, 1);
- end
-
- return armor
-end
-
-local function setDamageBlock(creature, damage, random1, random2, randomArmor)
- if bit.band(creature:getType():getCombatImmunities(), COMBAT_PHYSICALDAMAGE) == COMBAT_PHYSICALDAMAGE then
- return 0
- end
-
- damage = damage + getDefense(creature, random1, random2)
-
- if damage >= 0 then
- return 0
- end
-
- if damage < 0 then
- print (getArmor(creature, randomArmor))
- damage = damage + getArmor(creature, randomArmor)
- if damage >= 0 then
- return 0
- end
- end
-
- return damage
-end
-
-function onSay(player, words, param)
- local split = param:split(",")
- if split[3] == nil then
- --player:sendCancelMessage("Insufficient parameters [(exp,skill,loot,magic),percentage,hours].")
- --return false
- end
-
- local creature = Creature("Troll")
- if creature == nil then
- player:sendCancelMessage("The monster does not exist.")
- end
-
- local skillType = SKILL_FIST
- local attack = 7
- local weapon = getWeapon(player)
- if weapon ~= nil then
- attack = weapon:getType():getAttack()
- skillType = weaponSkillsConfig[weapon:getType():getWeaponType()]
- end
-
- local damage = setDamageBlock(creature, setVocationDamageIncrease(player:getVocation():getId(), getDamage(attack, player:getSkillLevel(skillType), FIGHTMODE_ATTACK, os.rand(), os.rand())), os.rand(), os.rand(), os.rand())
- local minDamage = setDamageBlock(creature, setVocationDamageIncrease(player:getVocation():getId(), getDamage(attack, player:getSkillLevel(skillType), FIGHTMODE_ATTACK, 0, 0)), 99, 99, rshift(creature:getType():getArmor(), 1) + 1)
- local maxDamage = setDamageBlock(creature, setVocationDamageIncrease(player:getVocation():getId(), getDamage(attack, player:getSkillLevel(skillType), FIGHTMODE_ATTACK, 99, 99)), 0, 0, 0)
-
- print ("TotalDamage:" .. damage)
- print ("MinDamage:" .. minDamage)
- print ("MaxDamage:" .. maxDamage)
- --print (weapon:getType():getAttack())
- --print (shield:getName())
- local message = ""
- message = message .. "Offensive Fighting\n"
- message = message .. "- Daily kills for red skull " .. 123 .. "\n"
- --player:showTextDialog(1998, message, false)
- return false
-end
\ No newline at end of file
diff --git a/data/talkactions/scripts/physical_damage.lua b/data/talkactions/scripts/physical_damage.lua
new file mode 100644
index 0000000..14dc775
--- /dev/null
+++ b/data/talkactions/scripts/physical_damage.lua
@@ -0,0 +1,284 @@
+local weaponSkillsConfig = {
+ [WEAPON_SWORD] = SKILL_SWORD,
+ [WEAPON_CLUB] = SKILL_CLUB,
+ [WEAPON_AXE] = SKILL_AXE,
+ [WEAPON_DISTANCE] = SKILL_DISTANCE
+}
+
+local skillNameConfig = {
+ [SKILL_SWORD] = "Sword Fighting",
+ [SKILL_CLUB] = "Club Fighting",
+ [SKILL_AXE] = "Axe Fighting",
+ [SKILL_DISTANCE] = "Distance Fighting",
+ [SKILL_FIST] = "Fist Fighting"
+}
+
+local function ltrim(s)
+ if s == nil then
+ return s
+ end
+
+ return s:match'^%s*(.*)'
+end
+
+local function getWeapon(player)
+ local itemLeft = player:getSlotItem(CONST_SLOT_LEFT)
+ if itemLeft and itemLeft:getType():getWeaponType() ~= WEAPON_NONE and itemLeft:getType():getWeaponType() ~= WEAPON_SHIELD and itemLeft:getType():getWeaponType() ~= WEAPON_AMMO and itemLeft:getType():getWeaponType() ~= WEAPON_WAND then
+ return itemLeft:getType()
+ end
+
+ local itemRight = player:getSlotItem(CONST_SLOT_RIGHT)
+ if itemRight and itemRight:getType():getWeaponType() ~= WEAPON_NONE and itemRight:getType():getWeaponType() ~= WEAPON_SHIELD and itemRight:getType():getWeaponType() ~= WEAPON_AMMO and itemRight:getType():getWeaponType() ~= WEAPON_WAND then
+ return itemRight:getType()
+ end
+end
+
+local function getAmmunition(player)
+ local item = player:getSlotItem(CONST_SLOT_AMMO)
+ if item and item:getType():getWeaponType() == WEAPON_AMMO then
+ return item:getType()
+ end
+end
+
+local function getAttack(weapon, ammunition)
+ local attack = 7
+ if weapon ~= nil then
+ attack = weapon:getAttack()
+ if weapon:getWeaponType() == WEAPON_DISTANCE and weapon:getAmmoType() ~= 0 then
+ if ammunition ~= nil and ammunition:getAmmoType() == weapon:getAmmoType() then
+ attack = attack + ammunition:getAttack()
+ end
+ end
+ end
+
+ return attack
+end
+
+local function getDamageFormula(attack, attackSkill, fightMode)
+ local damage = attack
+ if fightMode == FIGHTMODE_ATTACK then
+ damage = math.floor(damage + 2 * damage / 10)
+ elseif fightMode == FIGHTMODE_DEFENSE then
+ damage = math.floor(damage - 4 * damage / 10)
+ end
+
+ local formula = math.floor((5 * (attackSkill) + 50) * damage)
+ return formula
+end
+
+local function getDamage(attack, attackSkill, fightMode, random1, random2)
+ local formula = getDamageFormula(attack, attackSkill, fightMode)
+ local randResult = math.floor(random1 % 100);
+ local damage = -math.floor((math.ceil(formula * ((random2 % 100 + randResult) / 2) / 10000.)));
+ return damage
+end
+
+local function setVocationDamageIncrease(vocationId, damage)
+ if vocationId == 4 or vocationId == 8 then
+ local knightCloseAttackDamageIncreasePercent = configManager.getNumber(configKeys.KNIGHT_CLOSE_ATTACK_DAMAGE_INCREASE_PERCENT)
+ if knightCloseAttackDamageIncreasePercent ~= -1 then
+ damage = math.floor(damage + damage * knightCloseAttackDamageIncreasePercent / 100);
+ end
+ elseif vocationId == 3 or vocationId == 7 then
+ local paladinRangeAttackDamageIncreasePercent = configManager.getNumber(configKeys.PALADIN_RANGE_ATTACK_DAMAGE_INCREASE_PERCENT)
+ if paladinRangeAttackDamageIncreasePercent ~= -1 then
+ damage = math.floor(damage + damage * paladinRangeAttackDamageIncreasePercent / 100);
+ end
+ end
+
+ return damage
+end
+
+local function getDefense(creature, random1, random2)
+ local totalDefense = creature:getType():getDefense() + 1
+ local defenseSkill = creature:getType():getSkill()
+
+ local formula = math.floor((5 * (defenseSkill) + 50) * totalDefense)
+ local randresult = math.floor(random1 % 100)
+
+ return math.floor(formula * ((random2 % 100 + randresult) / 2) / 10000.)
+end
+
+local function rshift(x, by)
+ return math.floor(x / 2 ^ by)
+end
+
+local function getArmor(creature, rand)
+ local armor = creature:getType():getArmor()
+ if armor > 1 then
+ return rand % rshift(armor, 1) + rshift(armor, 1);
+ end
+
+ return armor
+end
+
+local function setPhysicalDamageBlock(creature, damage, random1, random2, randomArmor)
+ if bit.band(creature:getType():getCombatImmunities(), COMBAT_PHYSICALDAMAGE) == COMBAT_PHYSICALDAMAGE then
+ return 0
+ end
+
+ damage = damage + getDefense(creature, random1, random2)
+
+ if damage >= 0 then
+ return 0
+ end
+
+ if damage < 0 then
+ damage = damage + getArmor(creature, randomArmor)
+ if damage >= 0 then
+ return 0
+ end
+ end
+
+ return damage
+end
+
+local function isThrowableHit(weapon, ammunition, skillValue, rand)
+ local distance = 15 -- we consider distance is always the best
+ local hitChance = 75 -- throwables and such
+
+ if weapon:getAmmoType() ~= 0 then
+ hitChance = 90 -- bows and crossbows
+ if ammunition == nil or ammunition:getAmmoType() ~= weapon:getAmmoType() then
+ hitChance = -1 -- no ammo or invalid ammo
+ end
+ end
+
+ if rand % distance <= skillValue then
+ return rand % 100 <= hitChance
+ end
+
+ return false
+end
+
+local function getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, fightMode)
+ local damage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, os.rand(), os.rand()))
+ local minDamage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, 0, 0))
+ local maxDamage = setVocationDamageIncrease(vocation:getId(), getDamage(attack, skillValue, fightMode, 99, 99))
+
+ if weapon ~= nil and weapon:getWeaponType() == WEAPON_DISTANCE then
+ if isThrowableHit(weapon, ammunition, skillValue, os.rand()) then
+ damage = setPhysicalDamageBlock(creature, damage, os.rand(), os.rand(), os.rand())
+ else
+ damage = 0
+ end
+ minDamage = 0
+ if isThrowableHit(weapon, ammunition, skillValue, 0) then
+ maxDamage = setPhysicalDamageBlock(creature, maxDamage, 0, 0, 0)
+ else
+ maxDamage = 0
+ end
+ else
+ damage = setPhysicalDamageBlock(creature, damage, os.rand(), os.rand(), os.rand())
+ minDamage = setPhysicalDamageBlock(creature, minDamage, 99, 99, rshift(creature:getType():getArmor(), 1) + 1)
+ maxDamage = setPhysicalDamageBlock(creature, maxDamage, 0, 0, 0)
+ end
+
+ local container = {}
+ container[0] = damage
+ container[1] = minDamage
+ container[2] = maxDamage
+ return container
+end
+
+function onSay(player, words, param)
+ local split = param:split(",")
+ local creatureName = ltrim(split[1])
+ local skillValueNumber = ltrim(split[2])
+ local vocationName = ltrim(split[3])
+ local weaponName = ltrim(split[4])
+ local ammunitionName = ltrim(split[5])
+
+ local creature = Creature("Troll")
+ if creatureName ~= nil then
+ creature = Creature(creatureName)
+ if creature == nil then
+ player:sendCancelMessage("The monster does not exist.")
+ return false
+ end
+ else
+ creatureName = creature:getName()
+ end
+
+ local weapon = getWeapon(player)
+ if weaponName ~= nil then
+ weapon = ItemType(weaponName)
+ if weapon == nil or weapon:getWeaponType() == WEAPON_NONE or weapon:getWeaponType() == WEAPON_SHIELD or weapon:getWeaponType() == WEAPON_AMMO or weapon:getWeaponType() == WEAPON_WAND then
+ player:sendCancelMessage("The weapon does not exist.")
+ return false
+ end
+ else
+ if weapon ~= nil then
+ weaponName = weapon:getName()
+ end
+ end
+
+ local ammunition = getAmmunition(player)
+ if ammunitionName ~= nil then
+ ammunition = ItemType(ammunitionName)
+ if ammunition == nil or ammunition:getWeaponType() ~= WEAPON_AMMO then
+ player:sendCancelMessage("The ammunition does not exist.")
+ return false
+ end
+ else
+ if ammunition ~= nil then
+ ammunitionName = ammunition:getName()
+ end
+ end
+
+ local skillType = SKILL_FIST
+ local attack = getAttack(weapon, ammunition)
+ if weapon ~= nil then
+ skillType = weaponSkillsConfig[weapon:getWeaponType()]
+ end
+
+ local skillValue = player:getSkillLevel(skillType)
+ if skillValueNumber ~= nil then
+ if tonumber(skillValueNumber) > 0 then
+ skillValue = tonumber(skillValueNumber)
+ else
+ player:sendCancelMessage("The skill value has to be a number and greater than zero.")
+ return false
+ end
+ end
+
+ local vocation = player:getVocation()
+ if vocationName ~= nil then
+ vocation = Vocation(vocationName)
+ if vocation == nil then
+ player:sendCancelMessage("The vocation does not exist.")
+ return false
+ end
+ else
+ vocationName = vocation:getName()
+ end
+
+ local commandStr = "Executing command: !physicaldamage " .. creatureName .. ", " .. skillValue .. ", " .. vocationName .. ""
+
+ if weapon ~= nil then
+ commandStr = commandStr .. ", " .. weaponName
+ end
+
+ if ammunition ~= nil and weapon ~= nil and weapon:getWeaponType() == WEAPON_DISTANCE then
+ commandStr = commandStr .. ", " .. ammunitionName
+ end
+ player:sendCancelMessage(commandStr)
+
+ local offensiveDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_ATTACK)
+ local balancedDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_BALANCED)
+ local defensiveDamageContainer = getTotalDamage(creature, weapon, ammunition, vocation, attack, skillValue, FIGHTMODE_DEFENSE)
+
+ local message = ""
+ message = message .. "Vocation: " .. vocation:getName() .. "\n"
+ message = message .. "Skill Name: " .. skillNameConfig[skillType] .. ", Skill Value: " .. skillValue .. "\n"
+ message = message .. "Weapon: " .. (weaponName or 'none') .. " (atk: " .. attack .. ")\n"
+ message = message .. "Creature: " .. creatureName .. " (arm: " .. creature:getType():getArmor() .. ", def: " .. creature:getType():getDefense() .. ", skill: " .. creature:getType():getSkill() .. ")\n"
+ message = message .. "\nOffensive Fighting Damage\n"
+ message = message .. "Min: " .. offensiveDamageContainer[1] .. ", Max: " .. offensiveDamageContainer[2] .. "\n"
+ message = message .. "\nBalanced Fighting Damage\n"
+ message = message .. "Min: " .. balancedDamageContainer[1] .. ", Max: " .. balancedDamageContainer[2] .. "\n"
+ message = message .. "\nDefensive Fighting Damage\n"
+ message = message .. "Min: " .. defensiveDamageContainer[1] .. ", Max: " .. defensiveDamageContainer[2] .. "\n"
+ player:showTextDialog(weapon and weapon:getId() or 2950, message, false)
+ return false
+end
\ No newline at end of file
diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml
index 104f173..8407033 100644
--- a/data/talkactions/talkactions.xml
+++ b/data/talkactions/talkactions.xml
@@ -56,7 +56,7 @@
-
+
diff --git a/src/luascript.cpp b/src/luascript.cpp
index 5b17983..51015e0 100644
--- a/src/luascript.cpp
+++ b/src/luascript.cpp
@@ -2267,6 +2267,7 @@ void LuaScriptInterface::registerFunctions()
registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense);
registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor);
registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType);
+ registerMethod("ItemType", "getAmmoType", LuaScriptInterface::luaItemTypeGetAmmoType);
registerMethod("ItemType", "getTransformEquipId", LuaScriptInterface::luaItemTypeGetTransformEquipId);
registerMethod("ItemType", "getTransformDeEquipId", LuaScriptInterface::luaItemTypeGetTransformDeEquipId);
@@ -10339,6 +10340,19 @@ int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L)
return 1;
}
+int LuaScriptInterface::luaItemTypeGetAmmoType(lua_State* L)
+{
+ // itemType:getAmmoType()
+ const ItemType* itemType = getUserdata(L, 1);
+ if (itemType) {
+ lua_pushnumber(L, itemType->ammoType);
+ }
+ else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L)
{
// itemType:getTransformEquipId()
diff --git a/src/luascript.h b/src/luascript.h
index 7a93938..3c8467f 100644
--- a/src/luascript.h
+++ b/src/luascript.h
@@ -1084,6 +1084,7 @@ class LuaScriptInterface
static int luaItemTypeGetDefense(lua_State* L);
static int luaItemTypeGetArmor(lua_State* L);
static int luaItemTypeGetWeaponType(lua_State* L);
+ static int luaItemTypeGetAmmoType(lua_State* L);
static int luaItemTypeGetTransformEquipId(lua_State* L);
static int luaItemTypeGetTransformDeEquipId(lua_State* L);
diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp
index 6ad238f..e503be0 100644
--- a/src/networkmessage.cpp
+++ b/src/networkmessage.cpp
@@ -95,7 +95,7 @@ void NetworkMessage::addPosition(const Position& pos)
addByte(pos.z);
}
-void NetworkMessage::addItem(uint16_t id, uint8_t count)
+void NetworkMessage::addItem(uint16_t id, uint8_t count, bool textWindow /* = false*/)
{
const ItemType& it = Item::items[id];
@@ -105,14 +105,17 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count)
add(it.id);
}
- if (it.stackable || it.isRune()) {
- addByte(count);
- } else if (it.isSplash() || it.isFluidContainer()) {
- addByte(getLiquidColor(count));
+ if (!textWindow) {
+ if (it.stackable || it.isRune()) {
+ addByte(count);
+ }
+ else if (it.isSplash() || it.isFluidContainer()) {
+ addByte(getLiquidColor(count));
+ }
}
}
-void NetworkMessage::addItem(const Item* item)
+void NetworkMessage::addItem(const Item* item, bool textWindow /* = false*/)
{
const ItemType& it = Item::items[item->getID()];
@@ -122,12 +125,16 @@ void NetworkMessage::addItem(const Item* item)
add(it.id);
}
- if (it.stackable) {
- addByte(std::min(0xFF, item->getItemCount()));
- } else if (it.isRune()) {
- addByte(std::min(0xFF, item->getCharges()));
- } else if (it.isSplash() || it.isFluidContainer()) {
- addByte(getLiquidColor(item->getFluidType()));
+ if (!textWindow) {
+ if (it.stackable) {
+ addByte(std::min(0xFF, item->getItemCount()));
+ }
+ else if (it.isRune()) {
+ addByte(std::min(0xFF, item->getCharges()));
+ }
+ else if (it.isSplash() || it.isFluidContainer()) {
+ addByte(getLiquidColor(item->getFluidType()));
+ }
}
}
diff --git a/src/networkmessage.h b/src/networkmessage.h
index 7d11153..9ced1bb 100644
--- a/src/networkmessage.h
+++ b/src/networkmessage.h
@@ -110,8 +110,8 @@ class NetworkMessage
// write functions for complex types
void addPosition(const Position& pos);
- void addItem(uint16_t id, uint8_t count);
- void addItem(const Item* item);
+ void addItem(uint16_t id, uint8_t count, bool textWindow = false);
+ void addItem(const Item* item, bool textWindow = false);
void addItemId(uint16_t itemId);
MsgSize_t getLength() const {
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index 42c71f8..90b7388 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -1755,7 +1755,7 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t ma
NetworkMessage msg;
msg.addByte(0x96);
msg.add(windowTextId);
- msg.addItem(item);
+ msg.addItem(item, true);
if (canWrite) {
msg.add(maxlen);
@@ -1791,7 +1791,7 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const
NetworkMessage msg;
msg.addByte(0x96);
msg.add(windowTextId);
- msg.addItem(itemId, 1);
+ msg.addItem(itemId, 1, true);
msg.add(text.size());
msg.addString(text);
msg.add(0x00);