L2Character attack rework.

Adapted from: L2jUnity free files.
This commit is contained in:
MobiusDev
2017-12-06 18:29:40 +00:00
parent 52e54b75ce
commit 86b76fd312
189 changed files with 3462 additions and 3804 deletions
@@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new); EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new); EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new); EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new); EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new); EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new); EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new); EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new); EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new); EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new); EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new); EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new); EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);
@@ -0,0 +1,53 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.commons.util.MathUtil;
import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Nik
*/
public class AttackDamagePosition extends AbstractEffect
{
protected final double _amount;
protected final Position _position;
public AttackDamagePosition(StatsSet params)
{
_amount = params.getDouble("amount");
_position = params.getEnum("position", Position.class);
}
@Override
public void pump(L2Character effected, Skill skill)
{
effected.getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::mul);
}
@Override
public void onExit(BuffInfo info)
{
info.getEffected().getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::div);
}
}
@@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage); damage = Math.max(0, damage);
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(attacker, effected, skill, critical); Formulas.calcCounterAttack(attacker, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
} }
// No matter if lethal succeeded or not, its reflected. // No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false); Formulas.calcCounterAttack(effector, effected, skill, false);
} }
} }
@@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
{ {
@@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
} }
// Check if damage should be reflected. // Check if damage should be reflected.
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -0,0 +1,52 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Sdw
*/
public class PolearmSingleTarget extends AbstractEffect
{
public PolearmSingleTarget(StatsSet params)
{
}
@Override
public void onStart(L2Character effector, L2Character effected, Skill skill)
{
if (effected.isPlayer())
{
effected.getStat().addFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE, 1.0);
}
}
@Override
public void onExit(BuffInfo info)
{
if (info.getEffected().isPlayer())
{
info.getEffected().getStat().removeFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE);
}
}
}
@@ -93,7 +93,7 @@ public final class SoulBlow extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -98,7 +98,7 @@ public class SummonHallucination extends AbstractEffect
clone.setSummoner(player); clone.setSummoner(player);
clone.spawnMe(x, y, z); clone.spawnMe(x, y, z);
clone.scheduleDespawn(_despawnDelay); clone.scheduleDespawn(_despawnDelay);
clone.doAttack(effected); clone.doAutoAttack(effected);
} }
} }
} }
@@ -120,7 +120,7 @@ public class BeastSoulShot implements IItemHandler
if (!pet.isChargedShot(ShotType.SOULSHOTS)) if (!pet.isChargedShot(ShotType.SOULSHOTS))
{ {
activeOwner.sendMessage("Your pet uses soulshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT); activeOwner.sendMessage("Your pet uses soulshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT);
pet.setChargedShot(ShotType.SOULSHOTS, true); pet.chargeShot(ShotType.SOULSHOTS);
// Visual effect change if player has equipped Ruby lvl 3 or higher // Visual effect change if player has equipped Ruby lvl 3 or higher
if (activeOwner.getActiveRubyJewel() != null) if (activeOwner.getActiveRubyJewel() != null)
{ {
@@ -138,7 +138,7 @@ public class BeastSoulShot implements IItemHandler
if (!s.isChargedShot(ShotType.SOULSHOTS)) if (!s.isChargedShot(ShotType.SOULSHOTS))
{ {
activeOwner.sendMessage("Your servitor uses soulshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT); activeOwner.sendMessage("Your servitor uses soulshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT);
s.setChargedShot(ShotType.SOULSHOTS, true); s.chargeShot(ShotType.SOULSHOTS);
// Visual effect change if player has equipped Ruby lvl 3 or higher // Visual effect change if player has equipped Ruby lvl 3 or higher
if (activeOwner.getActiveRubyJewel() != null) if (activeOwner.getActiveRubyJewel() != null)
{ {
@@ -122,7 +122,7 @@ public class BeastSpiritShot implements IItemHandler
if (!pet.isChargedShot(shotType)) if (!pet.isChargedShot(shotType))
{ {
activeOwner.sendMessage(isBlessed ? "Your pet uses blessed spiritshot." : "Your pet uses spiritshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT); activeOwner.sendMessage(isBlessed ? "Your pet uses blessed spiritshot." : "Your pet uses spiritshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT);
pet.setChargedShot(shotType, true); pet.chargeShot(shotType);
// Visual effect change if player has equipped Sapphire lvl 3 or higher // Visual effect change if player has equipped Sapphire lvl 3 or higher
if (activeOwner.getActiveShappireJewel() != null) if (activeOwner.getActiveShappireJewel() != null)
{ {
@@ -140,7 +140,7 @@ public class BeastSpiritShot implements IItemHandler
if (!s.isChargedShot(shotType)) if (!s.isChargedShot(shotType))
{ {
activeOwner.sendMessage(isBlessed ? "Your servitor uses blessed spiritshot." : "Your servitor uses spiritshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT); activeOwner.sendMessage(isBlessed ? "Your servitor uses blessed spiritshot." : "Your servitor uses spiritshot."); // activeOwner.sendPacket(SystemMessageId.YOUR_PET_USES_SPIRITSHOT);
s.setChargedShot(shotType, true); s.chargeShot(shotType);
// Visual effect change if player has equipped Sapphire lvl 3 or higher // Visual effect change if player has equipped Sapphire lvl 3 or higher
if (activeOwner.getActiveShappireJewel() != null) if (activeOwner.getActiveShappireJewel() != null)
{ {
@@ -104,7 +104,7 @@ public class BlessedSoulShots implements IItemHandler
return false; return false;
} }
// Charge soul shot // Charge soul shot
weaponInst.setChargedShot(ShotType.BLESSED_SOULSHOTS, true); activeChar.chargeShot(ShotType.BLESSED_SOULSHOTS);
} }
finally finally
{ {
@@ -94,7 +94,7 @@ public class BlessedSpiritShot implements IItemHandler
} }
// Charge Spirit shot // Charge Spirit shot
activeChar.setChargedShot(ShotType.SPIRITSHOTS, true); activeChar.chargeShot(ShotType.SPIRITSHOTS);
// Send message to client // Send message to client
if (!activeChar.getAutoSoulShot().contains(item.getId())) if (!activeChar.getAutoSoulShot().contains(item.getId()))
@@ -75,7 +75,7 @@ public class FishShots implements IItemHandler
return false; return false;
} }
activeChar.setChargedShot(ShotType.FISH_SOULSHOTS, true); activeChar.chargeShot(ShotType.FISH_SOULSHOTS);
activeChar.destroyItemWithoutTrace("Consume", item.getObjectId(), 1, null, false); activeChar.destroyItemWithoutTrace("Consume", item.getObjectId(), 1, null, false);
final L2Object oldTarget = activeChar.getTarget(); final L2Object oldTarget = activeChar.getTarget();
activeChar.setTarget(activeChar); activeChar.setTarget(activeChar);
@@ -101,7 +101,7 @@ public class SoulShots implements IItemHandler
return false; return false;
} }
// Charge soul shot // Charge soul shot
weaponInst.setChargedShot(ShotType.SOULSHOTS, true); activeChar.chargeShot(ShotType.SOULSHOTS);
} }
finally finally
{ {
@@ -94,7 +94,7 @@ public class SpiritShot implements IItemHandler
} }
// Charge Spirit shot // Charge Spirit shot
activeChar.setChargedShot(ShotType.SPIRITSHOTS, true); activeChar.chargeShot(ShotType.SPIRITSHOTS);
// Send message to client // Send message to client
if (!activeChar.getAutoSoulShot().contains(item.getId())) if (!activeChar.getAutoSoulShot().contains(item.getId()))
@@ -865,14 +865,14 @@ public final class MemoryOfDisaster extends AbstractInstance
{ {
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2); npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2);
npc.doDie(null); npc.doDie(null);
attacker.doAttack(world.getNpc(SILVERA)); attacker.doAutoAttack(world.getNpc(SILVERA));
break; break;
} }
case SILVERA: case SILVERA:
{ {
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD); npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD);
npc.doDie(null); npc.doDie(null);
world.getNpc(SIEGE_GOLEM).doAttack(attacker); world.getNpc(SIEGE_GOLEM).doAutoAttack(attacker);
break; break;
} }
} }
@@ -821,13 +821,7 @@
<hitTime>2000</hitTime> <hitTime>2000</hitTime>
<targetType>NONE</targetType> <targetType>NONE</targetType>
<effects> <effects>
<effect name="HitNumber"> <effect name="PolearmSingleTarget" />
<amount>1</amount>
<mode>DIFF</mode>
<weaponType>
<item>POLE</item>
</weaponType>
</effect>
<effect name="Accuracy"> <effect name="Accuracy">
<amount> <amount>
<value level="1">2</value> <value level="1">2</value>
@@ -902,7 +902,15 @@
<!-- Rear Damage + 3%. --> <!-- Rear Damage + 3%. -->
<operateType>P</operateType> <operateType>P</operateType>
<icon>icon.skill0030</icon> <icon>icon.skill0030</icon>
<!-- TODO: Handle rear damage stat --> <effects>
<effect name="AttackDamagePosition">
<amount>
<value level="1">3</value>
<value level="2">6</value>
</amount>
<position>BACK</position>
</effect>
</effects>
</skill> </skill>
<skill id="19145" toLevel="2" name="Noble Death Whisper"> <skill id="19145" toLevel="2" name="Noble Death Whisper">
<!-- Physical Critical Damage + 3%. --> <!-- Physical Critical Damage + 3%. -->
@@ -13,6 +13,7 @@ AreaDamage: Topography (Danger Zone) resistance stat.
AttackAttribute: Stat that increases specific attack attribute. AttackAttribute: Stat that increases specific attack attribute.
AttackAttributeAdd: Stat that increases all attack attribute. AttackAttributeAdd: Stat that increases all attack attribute.
AttackBehind: Enables all attacks regardless of position to land towards the back. AttackBehind: Enables all attacks regardless of position to land towards the back.
AttackDamagePosition: Bonus damage depending on player position towards the target.
AttackTrait: Stat that manages all attack traits. AttackTrait: Stat that manages all attack traits.
Backstab: Inflicts physical damage according to the backstab formula. Backstab: Inflicts physical damage according to the backstab formula.
Betray: Causes the target summon to attack its owner. Betray: Causes the target summon to attack its owner.
@@ -205,6 +206,7 @@ PhysicalSkillPower: Physical Skill Power stat.
PhysicalSoulAttack: Physical attack depending on souls. PhysicalSoulAttack: Physical attack depending on souls.
PkCount: Increases PK kills. PkCount: Increases PK kills.
Plunder: Takes bonus item from monster. Sweep effect. Plunder: Takes bonus item from monster. Sweep effect.
PolearmSingleTarget: Effect used by Focus Attack (317) skill.
ProtectDeathPenalty: Unable to acquire death penalty. ProtectDeathPenalty: Unable to acquire death penalty.
ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher. ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher.
PullBack: Pulls the target towards you. PullBack: Pulls the target towards you.
@@ -78,7 +78,7 @@ public class DoppelgangerAI extends L2CharacterAI
return; return;
} }
clientStopMoving(null); clientStopMoving(null);
_actor.doAttack(attackTarget); _actor.doAutoAttack(attackTarget);
} }
private void thinkCast() private void thinkCast()
@@ -218,7 +218,7 @@ public class FriendlyNpcAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(originalAttackTarget); _actor.doAutoAttack(originalAttackTarget);
} }
@Override @Override
@@ -994,7 +994,7 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
} }
// Attacks target // Attacks target
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
private boolean checkSkillTarget(Skill skill, L2Object target) private boolean checkSkillTarget(Skill skill, L2Object target)
@@ -223,7 +223,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
protected void thinkForceAttack() protected void thinkForceAttack()
@@ -264,7 +264,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(getForcedTarget()); _actor.doAutoAttack(getForcedTarget());
} }
@Override @Override
@@ -364,7 +364,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
} }
} }
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
} }
@@ -262,7 +262,7 @@ public class L2PlayerAI extends L2PlayableAI
return; return;
} }
_actor.doAttack((L2Character) target); _actor.doAutoAttack((L2Character) target);
} }
private void thinkCast() private void thinkCast()
@@ -107,7 +107,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
return; return;
} }
clientStopMoving(null); clientStopMoving(null);
_actor.doAttack(attackTarget); _actor.doAutoAttack(attackTarget);
} }
private void thinkCast() private void thinkCast()
@@ -276,7 +276,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
final L2Summon summon = getActor(); final L2Summon summon = getActor();
if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false)) if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
{ {
summon.doAttack(attacker); summon.doAutoAttack(attacker);
} }
} }
@@ -245,18 +245,13 @@ public class Fishing
} }
double chance = baitData.getChance(); double chance = baitData.getChance();
final boolean isCharged = _player.isChargedShot(ShotType.FISH_SOULSHOTS); if (_player.isChargedShot(ShotType.FISH_SOULSHOTS))
if (isCharged)
{ {
chance *= 1.50; // +50 % chance to win chance *= 1.5; // +50 % chance to win
} }
if (Rnd.get(0, 100) <= chance) if (Rnd.get(0, 100) <= chance)
{ {
if (isCharged)
{
_player.setChargedShot(ShotType.FISH_SOULSHOTS, false);
}
reelIn(FishingEndReason.WIN, true); reelIn(FishingEndReason.WIN, true);
} }
else else
@@ -16,6 +16,8 @@
*/ */
package com.l2jmobius.gameserver.model; package com.l2jmobius.gameserver.model;
import java.lang.ref.WeakReference;
import com.l2jmobius.gameserver.enums.AttackType; import com.l2jmobius.gameserver.enums.AttackType;
import com.l2jmobius.gameserver.model.actor.L2Character; import com.l2jmobius.gameserver.model.actor.L2Character;
@@ -24,6 +26,7 @@ import com.l2jmobius.gameserver.model.actor.L2Character;
*/ */
public class Hit public class Hit
{ {
private final WeakReference<L2Object> _target;
private final int _targetId; private final int _targetId;
private final int _damage; private final int _damage;
private final int _ssGrade; private final int _ssGrade;
@@ -31,6 +34,7 @@ public class Hit
public Hit(L2Object target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade) public Hit(L2Object target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade)
{ {
_target = new WeakReference<>(target);
_targetId = target.getObjectId(); _targetId = target.getObjectId();
_damage = damage; _damage = damage;
_ssGrade = ssGrade; _ssGrade = ssGrade;
@@ -62,6 +66,11 @@ public class Hit
_flags |= type.getMask(); _flags |= type.getMask();
} }
public L2Object getTarget()
{
return _target.get();
}
public int getTargetId() public int getTargetId()
{ {
return _targetId; return _targetId;
@@ -81,4 +90,24 @@ public class Hit
{ {
return _ssGrade; return _ssGrade;
} }
public boolean isMiss()
{
return (AttackType.MISSED.getMask() & _flags) != 0;
}
public boolean isCritical()
{
return (AttackType.CRITICAL.getMask() & _flags) != 0;
}
public boolean isShotUsed()
{
return (AttackType.SHOT_USED.getMask() & _flags) != 0;
}
public boolean isBlocked()
{
return (AttackType.BLOCKED.getMask() & _flags) != 0;
}
} }
@@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.l2jmobius.gameserver.enums.InstanceType; import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.handler.ActionHandler; import com.l2jmobius.gameserver.handler.ActionHandler;
import com.l2jmobius.gameserver.handler.ActionShiftHandler; import com.l2jmobius.gameserver.handler.ActionShiftHandler;
import com.l2jmobius.gameserver.handler.IActionHandler; import com.l2jmobius.gameserver.handler.IActionHandler;
@@ -463,35 +462,6 @@ public abstract class L2Object extends ListenersContainer implements IIdentifiab
return false; return false;
} }
/**
* Check if current object has charged shot.
* @param type of the shot to be checked.
* @return {@code true} if the object has charged shot
*/
public boolean isChargedShot(ShotType type)
{
return false;
}
/**
* Charging shot into the current object.
* @param type of the shot to be charged.
* @param charged
*/
public void setChargedShot(ShotType type, boolean charged)
{
}
/**
* Try to recharge a shot.
* @param physical skill are using Soul shots.
* @param magical skill are using Spirit shots.
* @param fish
*/
public void rechargeShots(boolean physical, boolean magical, boolean fish)
{
}
/** /**
* @param <T> * @param <T>
* @param script * @param script
@@ -128,7 +128,7 @@ public class ShortCuts implements IRestorable
{ {
if (_owner.removeAutoSoulShot(item.getId())) if (_owner.removeAutoSoulShot(item.getId()))
{ {
_owner.sendPacket(new ExAutoSoulShot(item.getId(), 0)); _owner.sendPacket(new ExAutoSoulShot(item.getId(), false, 0));
} }
} }
} }
@@ -137,7 +137,7 @@ public class ShortCuts implements IRestorable
for (int shotId : _owner.getAutoSoulShot()) for (int shotId : _owner.getAutoSoulShot())
{ {
_owner.sendPacket(new ExAutoSoulShot(shotId, 1)); _owner.sendPacket(new ExAutoSoulShot(shotId, true, 0));
} }
} }
File diff suppressed because it is too large Load Diff
@@ -144,7 +144,6 @@ public class L2Npc extends L2Character
private int _spiritshotamount = 0; private int _spiritshotamount = 0;
private int _displayEffect = 0; private int _displayEffect = 0;
private int _shotsMask = 0;
private int _killingBlowWeaponId; private int _killingBlowWeaponId;
private int _cloneObjId; // Used in NpcInfo packet to clone the specified player. private int _cloneObjId; // Used in NpcInfo packet to clone the specified player.
@@ -1410,26 +1409,22 @@ public class L2Npc extends L2Character
} }
@Override @Override
public boolean isChargedShot(ShotType type) public void rechargeShots(boolean physical, boolean magic, boolean fish)
{ {
return (_shotsMask & type.getMask()) == type.getMask(); if (isFakePlayer() && Config.FAKE_PLAYER_USE_SHOTS)
{
if (physical)
{
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 9193, 1, 0, 0), 600);
chargeShot(ShotType.SOULSHOTS);
} }
if (magic)
@Override
public void setChargedShot(ShotType type, boolean charged)
{ {
if (charged) Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 9195, 1, 0, 0), 600);
{ chargeShot(ShotType.SPIRITSHOTS);
_shotsMask |= type.getMask(); }
} }
else else
{
_shotsMask &= ~type.getMask();
}
}
@Override
public void rechargeShots(boolean physical, boolean magic, boolean fish)
{ {
if (physical && (_soulshotamount > 0)) if (physical && (_soulshotamount > 0))
{ {
@@ -1439,9 +1434,8 @@ public class L2Npc extends L2Character
} }
_soulshotamount--; _soulshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2154, 1, 0, 0), 600); Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2154, 1, 0, 0), 600);
setChargedShot(ShotType.SOULSHOTS, true); chargeShot(ShotType.SOULSHOTS);
} }
if (magic && (_spiritshotamount > 0)) if (magic && (_spiritshotamount > 0))
{ {
if (Rnd.get(100) > getTemplate().getSpiritShotChance()) if (Rnd.get(100) > getTemplate().getSpiritShotChance())
@@ -1450,7 +1444,8 @@ public class L2Npc extends L2Character
} }
_spiritshotamount--; _spiritshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2061, 1, 0, 0), 600); Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2061, 1, 0, 0), 600);
setChargedShot(ShotType.SPIRITSHOTS, true); chargeShot(ShotType.SPIRITSHOTS);
}
} }
} }
@@ -26,7 +26,6 @@ import com.l2jmobius.gameserver.data.xml.impl.ExperienceData;
import com.l2jmobius.gameserver.datatables.ItemTable; import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.enums.InstanceType; import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.Race; import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.Team; import com.l2jmobius.gameserver.enums.Team;
import com.l2jmobius.gameserver.handler.IItemHandler; import com.l2jmobius.gameserver.handler.IItemHandler;
import com.l2jmobius.gameserver.handler.ItemHandler; import com.l2jmobius.gameserver.handler.ItemHandler;
@@ -77,7 +76,6 @@ public abstract class L2Summon extends L2Playable
private boolean _follow = true; private boolean _follow = true;
private boolean _previousFollowStatus = true; private boolean _previousFollowStatus = true;
protected boolean _restoreSummon = true; protected boolean _restoreSummon = true;
private int _shotsMask = 0;
private int _summonPoints = 0; private int _summonPoints = 0;
// @formatter:off // @formatter:off
@@ -1030,25 +1028,6 @@ public abstract class L2Summon extends L2Playable
return true; return true;
} }
@Override
public boolean isChargedShot(ShotType type)
{
return (_shotsMask & type.getMask()) == type.getMask();
}
@Override
public void setChargedShot(ShotType type, boolean charged)
{
if (charged)
{
_shotsMask |= type.getMask();
}
else
{
_shotsMask &= ~type.getMask();
}
}
@Override @Override
public void rechargeShots(boolean physical, boolean magic, boolean fish) public void rechargeShots(boolean physical, boolean magic, boolean fish)
{ {
@@ -91,7 +91,7 @@ public final class L2DoorInstance extends L2Character
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
} }
@@ -92,7 +92,6 @@ import com.l2jmobius.gameserver.enums.PrivateStoreType;
import com.l2jmobius.gameserver.enums.Race; import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.Sex; import com.l2jmobius.gameserver.enums.Sex;
import com.l2jmobius.gameserver.enums.ShortcutType; import com.l2jmobius.gameserver.enums.ShortcutType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType; import com.l2jmobius.gameserver.enums.StatusUpdateType;
import com.l2jmobius.gameserver.enums.SubclassInfoType; import com.l2jmobius.gameserver.enums.SubclassInfoType;
import com.l2jmobius.gameserver.enums.Team; import com.l2jmobius.gameserver.enums.Team;
@@ -4643,9 +4642,9 @@ public final class L2PcInstance extends L2Playable
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
super.doAttack(target); super.doAutoAttack(target);
setRecentFakeDeath(false); setRecentFakeDeath(false);
if (target.isFakePlayer()) if (target.isFakePlayer())
{ {
@@ -8788,7 +8787,8 @@ public final class L2PcInstance extends L2Playable
if (_activeSoulShots.contains(itemId)) if (_activeSoulShots.contains(itemId))
{ {
removeAutoSoulShot(itemId); removeAutoSoulShot(itemId);
sendPacket(new ExAutoSoulShot(itemId, 0)); sendPacket(new ExAutoSoulShot(itemId, false, 0));
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED); final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED);
sm.addItemName(itemId); sm.addItemName(itemId);
sendPacket(sm); sendPacket(sm);
@@ -8804,7 +8804,7 @@ public final class L2PcInstance extends L2Playable
{ {
for (int itemId : _activeSoulShots) for (int itemId : _activeSoulShots)
{ {
sendPacket(new ExAutoSoulShot(itemId, 0)); sendPacket(new ExAutoSoulShot(itemId, false, 0));
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED); final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED);
sm.addItemName(itemId); sm.addItemName(itemId);
sendPacket(sm); sendPacket(sm);
@@ -13065,23 +13065,6 @@ public final class L2PcInstance extends L2Playable
return true; return true;
} }
@Override
public boolean isChargedShot(ShotType type)
{
final L2ItemInstance weapon = getActiveWeaponInstance();
return (weapon != null) && weapon.isChargedShot(type);
}
@Override
public void setChargedShot(ShotType type, boolean charged)
{
final L2ItemInstance weapon = getActiveWeaponInstance();
if (weapon != null)
{
weapon.setChargedShot(type, charged);
}
}
/** /**
* @param skillId the display skill Id * @param skillId the display skill Id
* @return the custom skill * @return the custom skill
@@ -195,7 +195,7 @@ public final class L2StaticObjectInstance extends L2Character
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
} }
@@ -1,61 +0,0 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.model.actor.tasks.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
* Task launching the function onHitTimer().<br>
* <B><U>Actions</U>:</B>
* <ul>
* <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance)</li>
* <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance</li>
* <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary</li>
* <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)</li>
* </ul>
* @author xban1x
*/
public final class HitTask implements Runnable
{
private final L2Character _character;
private final L2Character _hitTarget;
private final int _damage;
private final boolean _crit;
private final boolean _miss;
private final byte _shld;
private final boolean _soulshot;
public HitTask(L2Character character, L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, byte shld)
{
_character = character;
_hitTarget = target;
_damage = damage;
_crit = crit;
_shld = shld;
_miss = miss;
_soulshot = soulshot;
}
@Override
public void run()
{
if (_character != null)
{
_character.onHitTimer(_hitTarget, _damage, _crit, _miss, _soulshot, _shld);
}
}
}
@@ -19,6 +19,7 @@ package com.l2jmobius.gameserver.model.events.impl.character;
import com.l2jmobius.gameserver.model.actor.L2Character; import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.events.EventType; import com.l2jmobius.gameserver.model.events.EventType;
import com.l2jmobius.gameserver.model.events.impl.IBaseEvent; import com.l2jmobius.gameserver.model.events.impl.IBaseEvent;
import com.l2jmobius.gameserver.model.skills.Skill;
/** /**
* An instantly executed event when L2Character is attacked by L2Character. * An instantly executed event when L2Character is attacked by L2Character.
@@ -28,11 +29,13 @@ public class OnCreatureAttack implements IBaseEvent
{ {
private final L2Character _attacker; private final L2Character _attacker;
private final L2Character _target; private final L2Character _target;
private final Skill _skill;
public OnCreatureAttack(L2Character attacker, L2Character target) public OnCreatureAttack(L2Character attacker, L2Character target, Skill skill)
{ {
_attacker = attacker; _attacker = attacker;
_target = target; _target = target;
_skill = skill;
} }
public final L2Character getAttacker() public final L2Character getAttacker()
@@ -45,6 +48,11 @@ public class OnCreatureAttack implements IBaseEvent
return _target; return _target;
} }
public final Skill getSkill()
{
return _skill;
}
@Override @Override
public EventType getType() public EventType getType()
{ {
@@ -45,7 +45,6 @@ import com.l2jmobius.gameserver.enums.AttributeType;
import com.l2jmobius.gameserver.enums.InstanceType; import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemLocation; import com.l2jmobius.gameserver.enums.ItemLocation;
import com.l2jmobius.gameserver.enums.ItemSkillType; import com.l2jmobius.gameserver.enums.ItemSkillType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.UserInfoType; import com.l2jmobius.gameserver.enums.UserInfoType;
import com.l2jmobius.gameserver.geoengine.GeoEngine; import com.l2jmobius.gameserver.geoengine.GeoEngine;
import com.l2jmobius.gameserver.idfactory.IdFactory; import com.l2jmobius.gameserver.idfactory.IdFactory;
@@ -173,8 +172,6 @@ public final class L2ItemInstance extends L2Object
private final DropProtection _dropProtection = new DropProtection(); private final DropProtection _dropProtection = new DropProtection();
private int _shotsMask = 0;
private final List<Options> _enchantOptions = new ArrayList<>(); private final List<Options> _enchantOptions = new ArrayList<>();
/** /**
@@ -1399,7 +1396,6 @@ public final class L2ItemInstance extends L2Object
final InventoryUpdate iu = new InventoryUpdate(); final InventoryUpdate iu = new InventoryUpdate();
for (L2ItemInstance item : unequiped) for (L2ItemInstance item : unequiped)
{ {
item.unChargeAllShots();
iu.addModifiedItem(item); iu.addModifiedItem(item);
} }
player.sendInventoryUpdate(iu); player.sendInventoryUpdate(iu);
@@ -1827,7 +1823,6 @@ public final class L2ItemInstance extends L2Object
final InventoryUpdate iu = new InventoryUpdate(); final InventoryUpdate iu = new InventoryUpdate();
for (L2ItemInstance item : unequiped) for (L2ItemInstance item : unequiped)
{ {
item.unChargeAllShots();
iu.addModifiedItem(item); iu.addModifiedItem(item);
} }
player.sendInventoryUpdate(iu); player.sendInventoryUpdate(iu);
@@ -2077,30 +2072,6 @@ public final class L2ItemInstance extends L2Object
} }
} }
@Override
public boolean isChargedShot(ShotType type)
{
return (_shotsMask & type.getMask()) == type.getMask();
}
@Override
public void setChargedShot(ShotType type, boolean charged)
{
if (charged)
{
_shotsMask |= type.getMask();
}
else
{
_shotsMask &= ~type.getMask();
}
}
public void unChargeAllShots()
{
_shotsMask = 0;
}
/** /**
* Returns enchant effect object for this item * Returns enchant effect object for this item
* @return enchanteffect * @return enchanteffect
@@ -271,11 +271,7 @@ public abstract class AbstractOlympiadGame
player.disableAutoShotsAll(); player.disableAutoShotsAll();
// Discharge any active shots // Discharge any active shots
final L2ItemInstance item = player.getActiveWeaponInstance(); player.unchargeAllShots();
if (item != null)
{
item.unChargeAllShots();
}
// enable skills with cool time <= 15 minutes // enable skills with cool time <= 15 minutes
for (Skill skill : player.getAllSkills()) for (Skill skill : player.getAllSkills())
@@ -1514,11 +1514,11 @@ public final class Skill implements IIdentifiable
{ {
if (useSpiritShot()) if (useSpiritShot())
{ {
caster.setChargedShot(caster.isChargedShot(ShotType.BLESSED_SPIRITSHOTS) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS, false); caster.unchargeShot(caster.isChargedShot(ShotType.BLESSED_SPIRITSHOTS) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS);
} }
else if (useSoulShot()) else if (useSoulShot())
{ {
caster.setChargedShot(caster.isChargedShot(ShotType.BLESSED_SOULSHOTS) ? ShotType.BLESSED_SOULSHOTS : ShotType.SOULSHOTS, false); caster.unchargeShot(caster.isChargedShot(ShotType.BLESSED_SOULSHOTS) ? ShotType.BLESSED_SOULSHOTS : ShotType.SOULSHOTS);
} }
} }
@@ -217,11 +217,11 @@ public class SkillChannelizer implements Runnable
// Reduce shots. // Reduce shots.
if (skill.useSpiritShot()) if (skill.useSpiritShot())
{ {
_channelizer.setChargedShot(_channelizer.isChargedShot(ShotType.BLESSED_SPIRITSHOTS) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS, false); _channelizer.unchargeShot(_channelizer.isChargedShot(ShotType.BLESSED_SPIRITSHOTS) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS);
} }
else else
{ {
_channelizer.setChargedShot(_channelizer.isChargedShot(ShotType.BLESSED_SOULSHOTS) ? ShotType.BLESSED_SOULSHOTS : ShotType.SOULSHOTS, false); _channelizer.unchargeShot(_channelizer.isChargedShot(ShotType.BLESSED_SOULSHOTS) ? ShotType.BLESSED_SOULSHOTS : ShotType.SOULSHOTS);
} }
// Shots are re-charged every cast. // Shots are re-charged every cast.
@@ -37,6 +37,7 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance; import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag; import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType; import com.l2jmobius.gameserver.model.effects.L2EffectType;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor; import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item; import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon; import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -271,8 +272,12 @@ public final class Formulas
} }
// Autoattack critical rate. // Autoattack critical rate.
// It is capped to 500, but unbound by positional critical rate and level diff bonus. // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target)); // TODO: Find retail-like calculation for criticalRateMod.
final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher. // In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp. // Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -286,6 +291,37 @@ public final class Formulas
return finalRate > Rnd.get(1000); return finalRate > Rnd.get(1000);
} }
/**
* Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
* @param activeChar the attacker.
* @param target the target.
* @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
*/
public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
{
final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
switch (position)
{
case SIDE: // 10% Critical Chance bonus when attacking from side.
{
return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
}
case BACK: // 30% Critical Chance bonus when attacking from back.
{
return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
}
default: // No Critical Chance bonus when attacking from front.
{
return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
}
}
}
public static double calcCriticalHeightBonus(ILocational from, ILocational target)
{
return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
}
/** /**
* @param attacker * @param attacker
* @param target * @param target
@@ -403,16 +439,6 @@ public final class Formulas
return Rnd.get(100) < rate; return Rnd.get(100) < rate;
} }
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
}
/** /**
* Calculate delay (in milliseconds) for skills cast * Calculate delay (in milliseconds) for skills cast
* @param attacker * @param attacker
@@ -1023,7 +1049,7 @@ public final class Formulas
return 1; return 1;
} }
public static void calcDamageReflected(L2Character attacker, L2Character target, Skill skill, boolean crit) public static void calcCounterAttack(L2Character attacker, L2Character target, Skill skill, boolean crit)
{ {
// Only melee skills can be reflected // Only melee skills can be reflected
if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE)) if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE))
@@ -1049,7 +1075,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target)); double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target); counterdmg *= calcWeaponTraitBonus(attacker, target);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false); counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
counterdmg *= calcAttributeBonus(attacker, target, skill); counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill); attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1086,35 +1112,37 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0); return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
} }
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance) /**
* Basic chance formula:<br>
* <ul>
* <li>chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate</li>
* <li>weapon_critical = (12 for daggers)</li>
* <li>dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).</li>
* <li>crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.</li>
* <li>crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus</li>
* <li>effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.</li>
* </ul>
* Chance cannot be higher than 80%.
* @param activeChar
* @param target
* @param skill
* @param chanceBoost
* @return
*/
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
{ {
final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons. final L2Weapon weapon = activeChar.getActiveWeaponItem();
final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD // double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1; final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
final Position position = Position.getPosition(activeChar, target); final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side. final double chanceBoostMod = (100 + chanceBoost) / 100;
final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1); final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
blowChance = (weaponCritical + blowChance) * 10;
final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod; final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
// Debug
if (activeChar.isDebug())
{
final StatsSet set = new StatsSet();
set.set("weaponCritical", weaponCritical);
set.set("critHeightBonus", critHeightBonus);
set.set("criticalPosition", criticalPosition);
set.set("criticalPositionMod", criticalPositionMod);
set.set("blowRate", blowRateMod);
set.set("blowChance", blowChance);
set.set("rate(max 800 of 1000)", rate);
Debug.sendSkillDebug(activeChar, target, skill, set);
}
// Blow rate is capped at 80% // Blow rate is capped at 80%
return Rnd.get(1000) < Math.min(rate, 800); return Rnd.get(100) < Math.min(rate, 80);
} }
public static List<BuffInfo> calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max) public static List<BuffInfo> calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1224,6 +1252,12 @@ public final class Formulas
*/ */
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill) public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{ {
// Skills without set probability should only test against trait invulnerability.
if (Double.isNaN(baseChance))
{
return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
}
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false)); // Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula // TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false)); return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
@@ -1486,7 +1520,7 @@ public final class Formulas
*/ */
public static boolean calcStunBreak(L2Character activeChar) public static boolean calcStunBreak(L2Character activeChar)
{ {
// Check if target is stunned and 10% chance. // Check if target is stunned and 10% chance (retail is 14% and 35% on crit?)
if (activeChar.hasBlockActions() && (Rnd.get(10) == 0)) if (activeChar.hasBlockActions() && (Rnd.get(10) == 0))
{ {
// Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time. // Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time.
@@ -1497,8 +1531,18 @@ public final class Formulas
public static boolean calcRealTargetBreak() public static boolean calcRealTargetBreak()
{ {
// Real Target breaks at 5% probability. // Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
return Rnd.get(20) == 0; return Rnd.get(100) <= 3;
}
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
} }
/** /**
@@ -143,6 +143,7 @@ public enum Stats
PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()), PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()),
MAGIC_ATTACK_RANGE("mAtkRange"), MAGIC_ATTACK_RANGE("mAtkRange"),
ATTACK_COUNT_MAX("atkCountMax"), ATTACK_COUNT_MAX("atkCountMax"),
PHYSICAL_POLEARM_TARGET_SINGLE("polearmSingleTarget"),
// Run speed, walk & escape speed are calculated proportionally, magic speed is a buff // Run speed, walk & escape speed are calculated proportionally, magic speed is a buff
MOVE_SPEED("moveSpeed"), MOVE_SPEED("moveSpeed"),
RUN_SPEED("runSpd", new SpeedFinalizer()), RUN_SPEED("runSpd", new SpeedFinalizer()),
@@ -266,7 +267,8 @@ public enum Stats
// Which base stat ordinal should alter skill critical formula. // Which base stat ordinal should alter skill critical formula.
STAT_BONUS_SKILL_CRITICAL("statSkillCritical"), STAT_BONUS_SKILL_CRITICAL("statSkillCritical"),
STAT_BONUS_SPEED("statSpeed"), STAT_BONUS_SPEED("statSpeed"),
SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer()); SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer()),
ATTACK_DAMAGE("attackDamage");
static final Logger LOGGER = Logger.getLogger(Stats.class.getName()); static final Logger LOGGER = Logger.getLogger(Stats.class.getName());
public static final int NUM_STATS = values().length; public static final int NUM_STATS = values().length;
@@ -18,6 +18,7 @@ package com.l2jmobius.gameserver.network.clientpackets;
import com.l2jmobius.commons.network.PacketReader; import com.l2jmobius.commons.network.PacketReader;
import com.l2jmobius.gameserver.enums.PrivateStoreType; import com.l2jmobius.gameserver.enums.PrivateStoreType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.model.actor.L2Summon; import com.l2jmobius.gameserver.model.actor.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.items.L2Item; import com.l2jmobius.gameserver.model.items.L2Item;
@@ -34,6 +35,7 @@ import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
public final class RequestAutoSoulShot implements IClientIncomingPacket public final class RequestAutoSoulShot implements IClientIncomingPacket
{ {
private int _itemId; private int _itemId;
private boolean _enable;
private int _type; private int _type;
@Override @Override
@@ -41,6 +43,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
{ {
_itemId = packet.readD(); _itemId = packet.readD();
_type = packet.readD(); _type = packet.readD();
_enable = _type == 1;
return true; return true;
} }
@@ -61,7 +64,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
return; return;
} }
if (_type == 1) if (_enable)
{ {
if (!activeChar.getInventory().canManipulateWithItemId(item.getId())) if (!activeChar.getInventory().canManipulateWithItemId(item.getId()))
{ {
@@ -114,23 +117,34 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
// Activate shots // Activate shots
activeChar.addAutoSoulShot(_itemId); activeChar.addAutoSoulShot(_itemId);
client.sendPacket(new ExAutoSoulShot(_itemId, _type)); client.sendPacket(new ExAutoSoulShot(_itemId, _enable, _type));
// Send message
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED);
sm.addItemName(item);
client.sendPacket(sm);
// Recharge summon's shots // Recharge summon's shots
final L2Summon pet = activeChar.getPet(); final L2Summon pet = activeChar.getPet();
if (pet != null) if (pet != null)
{ {
// Send message
if (!pet.isChargedShot(item.getItem().getDefaultAction() == ActionType.SUMMON_SOULSHOT ? ShotType.SOULSHOTS : ((item.getId() == 6647) || (item.getId() == 20334)) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS))
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED);
sm.addItemName(item);
client.sendPacket(sm);
}
// Charge
pet.rechargeShots(isSoulshot, isSpiritshot, false); pet.rechargeShots(isSoulshot, isSpiritshot, false);
} }
activeChar.getServitors().values().forEach(s -> for (L2Summon summon : activeChar.getServitors().values())
{ {
s.rechargeShots(isSoulshot, isSpiritshot, false); // Send message
}); if (!summon.isChargedShot(item.getItem().getDefaultAction() == ActionType.SUMMON_SOULSHOT ? ShotType.SOULSHOTS : ((item.getId() == 6647) || (item.getId() == 20334)) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS))
{
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED);
sm.addItemName(item);
client.sendPacket(sm);
}
// Charge
summon.rechargeShots(isSoulshot, isSpiritshot, false);
}
} }
else else
{ {
@@ -150,7 +164,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
// Activate shots // Activate shots
activeChar.addAutoSoulShot(_itemId); activeChar.addAutoSoulShot(_itemId);
client.sendPacket(new ExAutoSoulShot(_itemId, _type)); client.sendPacket(new ExAutoSoulShot(_itemId, _enable, _type));
// Send message // Send message
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED); final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED);
@@ -165,7 +179,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
{ {
// Cancel auto shots // Cancel auto shots
activeChar.removeAutoSoulShot(_itemId); activeChar.removeAutoSoulShot(_itemId);
client.sendPacket(new ExAutoSoulShot(_itemId, _type)); client.sendPacket(new ExAutoSoulShot(_itemId, _enable, _type));
// Send message // Send message
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED); final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_DEACTIVATED);
@@ -180,11 +180,7 @@ public final class RequestDropItem implements IClientIncomingPacket
if (item.isEquipped()) if (item.isEquipped())
{ {
final L2ItemInstance[] unequiped = activeChar.getInventory().unEquipItemInSlotAndRecord(item.getLocationSlot()); activeChar.getInventory().unEquipItemInSlot(item.getLocationSlot());
for (L2ItemInstance itm : unequiped)
{
itm.unChargeAllShots();
}
activeChar.broadcastUserInfo(); activeChar.broadcastUserInfo();
activeChar.sendItemList(true); activeChar.sendItemList(true);
} }
@@ -29,8 +29,6 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class Attack implements IClientOutgoingPacket public class Attack implements IClientOutgoingPacket
{ {
private final int _attackerObjId; private final int _attackerObjId;
private final boolean _soulshot;
private final int _ssGrade;
private final Location _attackerLoc; private final Location _attackerLoc;
private final Location _targetLoc; private final Location _targetLoc;
private final List<Hit> _hits = new ArrayList<>(); private final List<Hit> _hits = new ArrayList<>();
@@ -38,29 +36,26 @@ public class Attack implements IClientOutgoingPacket
/** /**
* @param attacker * @param attacker
* @param target * @param target
* @param useShots
* @param ssGrade
*/ */
public Attack(L2Character attacker, L2Character target, boolean useShots, int ssGrade) public Attack(L2Character attacker, L2Character target)
{ {
_attackerObjId = attacker.getObjectId(); _attackerObjId = attacker.getObjectId();
_soulshot = useShots;
_ssGrade = Math.min(ssGrade, 6);
_attackerLoc = new Location(attacker); _attackerLoc = new Location(attacker);
_targetLoc = new Location(target); _targetLoc = new Location(target);
} }
/** /**
* Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits) * Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits)
* @param target * @param hit
* @param damage
* @param miss
* @param crit
* @param shld
*/ */
public void addHit(L2Character target, int damage, boolean miss, boolean crit, byte shld) public void addHit(Hit hit)
{ {
_hits.add(new Hit(target, damage, miss, crit, shld, _soulshot, _ssGrade)); _hits.add(hit);
}
public List<Hit> getHits()
{
return _hits;
} }
/** /**
@@ -71,14 +66,6 @@ public class Attack implements IClientOutgoingPacket
return !_hits.isEmpty(); return !_hits.isEmpty();
} }
/**
* @return {@code true} if attack has soul shot charged.
*/
public boolean hasSoulshot()
{
return _soulshot;
}
/** /**
* Writes current hit * Writes current hit
* @param packet * @param packet
@@ -22,11 +22,19 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class ExAutoSoulShot implements IClientOutgoingPacket public class ExAutoSoulShot implements IClientOutgoingPacket
{ {
private final int _itemId; private final int _itemId;
@SuppressWarnings("unused")
private final boolean _enable;
private final int _type; private final int _type;
public ExAutoSoulShot(int itemId, int type) /**
* @param itemId
* @param enable
* @param type
*/
public ExAutoSoulShot(int itemId, boolean enable, int type)
{ {
_itemId = itemId; _itemId = itemId;
_enable = enable;
_type = type; _type = type;
} }
@@ -36,6 +44,7 @@ public class ExAutoSoulShot implements IClientOutgoingPacket
OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet); OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet);
packet.writeD(_itemId); packet.writeD(_itemId);
// packet.writeD(_enable ? 0x01 : 0x00);
packet.writeD(_type); packet.writeD(_type);
return true; return true;
} }
@@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new); EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new); EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new); EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new); EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new); EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new); EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new); EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new); EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new); EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new); EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new); EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new); EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);
@@ -0,0 +1,53 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.commons.util.MathUtil;
import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Nik
*/
public class AttackDamagePosition extends AbstractEffect
{
protected final double _amount;
protected final Position _position;
public AttackDamagePosition(StatsSet params)
{
_amount = params.getDouble("amount");
_position = params.getEnum("position", Position.class);
}
@Override
public void pump(L2Character effected, Skill skill)
{
effected.getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::mul);
}
@Override
public void onExit(BuffInfo info)
{
info.getEffected().getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::div);
}
}
@@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage); damage = Math.max(0, damage);
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(attacker, effected, skill, critical); Formulas.calcCounterAttack(attacker, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
} }
// No matter if lethal succeeded or not, its reflected. // No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false); Formulas.calcCounterAttack(effector, effected, skill, false);
} }
} }
@@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
{ {
@@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
} }
// Check if damage should be reflected. // Check if damage should be reflected.
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -0,0 +1,52 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Sdw
*/
public class PolearmSingleTarget extends AbstractEffect
{
public PolearmSingleTarget(StatsSet params)
{
}
@Override
public void onStart(L2Character effector, L2Character effected, Skill skill)
{
if (effected.isPlayer())
{
effected.getStat().addFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE, 1.0);
}
}
@Override
public void onExit(BuffInfo info)
{
if (info.getEffected().isPlayer())
{
info.getEffected().getStat().removeFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE);
}
}
}
@@ -93,7 +93,7 @@ public final class SoulBlow extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -98,7 +98,7 @@ public class SummonHallucination extends AbstractEffect
clone.setSummoner(player); clone.setSummoner(player);
clone.spawnMe(x, y, z); clone.spawnMe(x, y, z);
clone.scheduleDespawn(_despawnDelay); clone.scheduleDespawn(_despawnDelay);
clone.doAttack(effected); clone.doAutoAttack(effected);
} }
} }
} }
@@ -865,14 +865,14 @@ public final class MemoryOfDisaster extends AbstractInstance
{ {
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2); npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2);
npc.doDie(null); npc.doDie(null);
attacker.doAttack(world.getNpc(SILVERA)); attacker.doAutoAttack(world.getNpc(SILVERA));
break; break;
} }
case SILVERA: case SILVERA:
{ {
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD); npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD);
npc.doDie(null); npc.doDie(null);
world.getNpc(SIEGE_GOLEM).doAttack(attacker); world.getNpc(SIEGE_GOLEM).doAutoAttack(attacker);
break; break;
} }
} }
@@ -821,13 +821,7 @@
<hitTime>2000</hitTime> <hitTime>2000</hitTime>
<targetType>NONE</targetType> <targetType>NONE</targetType>
<effects> <effects>
<effect name="HitNumber"> <effect name="PolearmSingleTarget" />
<amount>1</amount>
<mode>DIFF</mode>
<weaponType>
<item>POLE</item>
</weaponType>
</effect>
<effect name="Accuracy"> <effect name="Accuracy">
<amount> <amount>
<value level="1">2</value> <value level="1">2</value>
@@ -902,7 +902,15 @@
<!-- Rear Damage + 3%. --> <!-- Rear Damage + 3%. -->
<operateType>P</operateType> <operateType>P</operateType>
<icon>icon.skill0030</icon> <icon>icon.skill0030</icon>
<!-- TODO: Handle rear damage stat --> <effects>
<effect name="AttackDamagePosition">
<amount>
<value level="1">3</value>
<value level="2">6</value>
</amount>
<position>BACK</position>
</effect>
</effects>
</skill> </skill>
<skill id="19145" toLevel="2" name="Noble Death Whisper"> <skill id="19145" toLevel="2" name="Noble Death Whisper">
<!-- Physical Critical Damage + 3%. --> <!-- Physical Critical Damage + 3%. -->
@@ -13,6 +13,7 @@ AreaDamage: Topography (Danger Zone) resistance stat.
AttackAttribute: Stat that increases specific attack attribute. AttackAttribute: Stat that increases specific attack attribute.
AttackAttributeAdd: Stat that increases all attack attribute. AttackAttributeAdd: Stat that increases all attack attribute.
AttackBehind: Enables all attacks regardless of position to land towards the back. AttackBehind: Enables all attacks regardless of position to land towards the back.
AttackDamagePosition: Bonus damage depending on player position towards the target.
AttackTrait: Stat that manages all attack traits. AttackTrait: Stat that manages all attack traits.
Backstab: Inflicts physical damage according to the backstab formula. Backstab: Inflicts physical damage according to the backstab formula.
Betray: Causes the target summon to attack its owner. Betray: Causes the target summon to attack its owner.
@@ -205,6 +206,7 @@ PhysicalSkillPower: Physical Skill Power stat.
PhysicalSoulAttack: Physical attack depending on souls. PhysicalSoulAttack: Physical attack depending on souls.
PkCount: Increases PK kills. PkCount: Increases PK kills.
Plunder: Takes bonus item from monster. Sweep effect. Plunder: Takes bonus item from monster. Sweep effect.
PolearmSingleTarget: Effect used by Focus Attack (317) skill.
ProtectDeathPenalty: Unable to acquire death penalty. ProtectDeathPenalty: Unable to acquire death penalty.
ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher. ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher.
PullBack: Pulls the target towards you. PullBack: Pulls the target towards you.
@@ -78,7 +78,7 @@ public class DoppelgangerAI extends L2CharacterAI
return; return;
} }
clientStopMoving(null); clientStopMoving(null);
_actor.doAttack(attackTarget); _actor.doAutoAttack(attackTarget);
} }
private void thinkCast() private void thinkCast()
@@ -218,7 +218,7 @@ public class FriendlyNpcAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(originalAttackTarget); _actor.doAutoAttack(originalAttackTarget);
} }
@Override @Override
@@ -994,7 +994,7 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
} }
// Attacks target // Attacks target
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
private boolean checkSkillTarget(Skill skill, L2Object target) private boolean checkSkillTarget(Skill skill, L2Object target)
@@ -223,7 +223,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
protected void thinkForceAttack() protected void thinkForceAttack()
@@ -264,7 +264,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return; return;
} }
_actor.doAttack(getForcedTarget()); _actor.doAutoAttack(getForcedTarget());
} }
@Override @Override
@@ -364,7 +364,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
} }
} }
_actor.doAttack(target); _actor.doAutoAttack(target);
} }
} }
@@ -262,7 +262,7 @@ public class L2PlayerAI extends L2PlayableAI
return; return;
} }
_actor.doAttack((L2Character) target); _actor.doAutoAttack((L2Character) target);
} }
private void thinkCast() private void thinkCast()
@@ -107,7 +107,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
return; return;
} }
clientStopMoving(null); clientStopMoving(null);
_actor.doAttack(attackTarget); _actor.doAutoAttack(attackTarget);
} }
private void thinkCast() private void thinkCast()
@@ -276,7 +276,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
final L2Summon summon = getActor(); final L2Summon summon = getActor();
if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false)) if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
{ {
summon.doAttack(attacker); summon.doAutoAttack(attacker);
} }
} }
@@ -16,6 +16,8 @@
*/ */
package com.l2jmobius.gameserver.model; package com.l2jmobius.gameserver.model;
import java.lang.ref.WeakReference;
import com.l2jmobius.gameserver.enums.AttackType; import com.l2jmobius.gameserver.enums.AttackType;
import com.l2jmobius.gameserver.model.actor.L2Character; import com.l2jmobius.gameserver.model.actor.L2Character;
@@ -24,6 +26,7 @@ import com.l2jmobius.gameserver.model.actor.L2Character;
*/ */
public class Hit public class Hit
{ {
private final WeakReference<L2Object> _target;
private final int _targetId; private final int _targetId;
private final int _damage; private final int _damage;
private final int _ssGrade; private final int _ssGrade;
@@ -31,6 +34,7 @@ public class Hit
public Hit(L2Object target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade) public Hit(L2Object target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade)
{ {
_target = new WeakReference<>(target);
_targetId = target.getObjectId(); _targetId = target.getObjectId();
_damage = damage; _damage = damage;
_ssGrade = ssGrade; _ssGrade = ssGrade;
@@ -62,6 +66,11 @@ public class Hit
_flags |= type.getMask(); _flags |= type.getMask();
} }
public L2Object getTarget()
{
return _target.get();
}
public int getTargetId() public int getTargetId()
{ {
return _targetId; return _targetId;
@@ -81,4 +90,24 @@ public class Hit
{ {
return _ssGrade; return _ssGrade;
} }
public boolean isMiss()
{
return (AttackType.MISSED.getMask() & _flags) != 0;
}
public boolean isCritical()
{
return (AttackType.CRITICAL.getMask() & _flags) != 0;
}
public boolean isShotUsed()
{
return (AttackType.SHOT_USED.getMask() & _flags) != 0;
}
public boolean isBlocked()
{
return (AttackType.BLOCKED.getMask() & _flags) != 0;
}
} }
File diff suppressed because it is too large Load Diff
@@ -91,7 +91,7 @@ public final class L2DoorInstance extends L2Character
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
} }
@@ -4642,9 +4642,9 @@ public final class L2PcInstance extends L2Playable
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
super.doAttack(target); super.doAutoAttack(target);
setRecentFakeDeath(false); setRecentFakeDeath(false);
if (target.isFakePlayer()) if (target.isFakePlayer())
{ {
@@ -195,7 +195,7 @@ public final class L2StaticObjectInstance extends L2Character
} }
@Override @Override
public void doAttack(L2Character target) public void doAutoAttack(L2Character target)
{ {
} }
@@ -1,61 +0,0 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.model.actor.tasks.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
* Task launching the function onHitTimer().<br>
* <B><U>Actions</U>:</B>
* <ul>
* <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance)</li>
* <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance</li>
* <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary</li>
* <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)</li>
* </ul>
* @author xban1x
*/
public final class HitTask implements Runnable
{
private final L2Character _character;
private final L2Character _hitTarget;
private final int _damage;
private final boolean _crit;
private final boolean _miss;
private final byte _shld;
private final boolean _soulshot;
public HitTask(L2Character character, L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, byte shld)
{
_character = character;
_hitTarget = target;
_damage = damage;
_crit = crit;
_shld = shld;
_miss = miss;
_soulshot = soulshot;
}
@Override
public void run()
{
if (_character != null)
{
_character.onHitTimer(_hitTarget, _damage, _crit, _miss, _soulshot, _shld);
}
}
}
@@ -19,6 +19,7 @@ package com.l2jmobius.gameserver.model.events.impl.character;
import com.l2jmobius.gameserver.model.actor.L2Character; import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.events.EventType; import com.l2jmobius.gameserver.model.events.EventType;
import com.l2jmobius.gameserver.model.events.impl.IBaseEvent; import com.l2jmobius.gameserver.model.events.impl.IBaseEvent;
import com.l2jmobius.gameserver.model.skills.Skill;
/** /**
* An instantly executed event when L2Character is attacked by L2Character. * An instantly executed event when L2Character is attacked by L2Character.
@@ -28,11 +29,13 @@ public class OnCreatureAttack implements IBaseEvent
{ {
private final L2Character _attacker; private final L2Character _attacker;
private final L2Character _target; private final L2Character _target;
private final Skill _skill;
public OnCreatureAttack(L2Character attacker, L2Character target) public OnCreatureAttack(L2Character attacker, L2Character target, Skill skill)
{ {
_attacker = attacker; _attacker = attacker;
_target = target; _target = target;
_skill = skill;
} }
public final L2Character getAttacker() public final L2Character getAttacker()
@@ -45,6 +48,11 @@ public class OnCreatureAttack implements IBaseEvent
return _target; return _target;
} }
public final Skill getSkill()
{
return _skill;
}
@Override @Override
public EventType getType() public EventType getType()
{ {
@@ -37,6 +37,7 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance; import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag; import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType; import com.l2jmobius.gameserver.model.effects.L2EffectType;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor; import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item; import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon; import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -271,8 +272,12 @@ public final class Formulas
} }
// Autoattack critical rate. // Autoattack critical rate.
// It is capped to 500, but unbound by positional critical rate and level diff bonus. // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target)); // TODO: Find retail-like calculation for criticalRateMod.
final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher. // In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp. // Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -286,6 +291,37 @@ public final class Formulas
return finalRate > Rnd.get(1000); return finalRate > Rnd.get(1000);
} }
/**
* Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
* @param activeChar the attacker.
* @param target the target.
* @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
*/
public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
{
final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
switch (position)
{
case SIDE: // 10% Critical Chance bonus when attacking from side.
{
return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
}
case BACK: // 30% Critical Chance bonus when attacking from back.
{
return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
}
default: // No Critical Chance bonus when attacking from front.
{
return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
}
}
}
public static double calcCriticalHeightBonus(ILocational from, ILocational target)
{
return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
}
/** /**
* @param attacker * @param attacker
* @param target * @param target
@@ -403,16 +439,6 @@ public final class Formulas
return Rnd.get(100) < rate; return Rnd.get(100) < rate;
} }
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
}
/** /**
* Calculate delay (in milliseconds) for skills cast * Calculate delay (in milliseconds) for skills cast
* @param attacker * @param attacker
@@ -1023,7 +1049,7 @@ public final class Formulas
return 1; return 1;
} }
public static void calcDamageReflected(L2Character attacker, L2Character target, Skill skill, boolean crit) public static void calcCounterAttack(L2Character attacker, L2Character target, Skill skill, boolean crit)
{ {
// Only melee skills can be reflected // Only melee skills can be reflected
if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE)) if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE))
@@ -1049,7 +1075,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target)); double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target); counterdmg *= calcWeaponTraitBonus(attacker, target);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false); counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
counterdmg *= calcAttributeBonus(attacker, target, skill); counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill); attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1086,35 +1112,37 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0); return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
} }
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance) /**
* Basic chance formula:<br>
* <ul>
* <li>chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate</li>
* <li>weapon_critical = (12 for daggers)</li>
* <li>dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).</li>
* <li>crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.</li>
* <li>crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus</li>
* <li>effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.</li>
* </ul>
* Chance cannot be higher than 80%.
* @param activeChar
* @param target
* @param skill
* @param chanceBoost
* @return
*/
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
{ {
final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons. final L2Weapon weapon = activeChar.getActiveWeaponItem();
final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD // double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1; final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
final Position position = Position.getPosition(activeChar, target); final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side. final double chanceBoostMod = (100 + chanceBoost) / 100;
final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1); final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
blowChance = (weaponCritical + blowChance) * 10;
final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod; final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
// Debug
if (activeChar.isDebug())
{
final StatsSet set = new StatsSet();
set.set("weaponCritical", weaponCritical);
set.set("critHeightBonus", critHeightBonus);
set.set("criticalPosition", criticalPosition);
set.set("criticalPositionMod", criticalPositionMod);
set.set("blowRate", blowRateMod);
set.set("blowChance", blowChance);
set.set("rate(max 800 of 1000)", rate);
Debug.sendSkillDebug(activeChar, target, skill, set);
}
// Blow rate is capped at 80% // Blow rate is capped at 80%
return Rnd.get(1000) < Math.min(rate, 800); return Rnd.get(100) < Math.min(rate, 80);
} }
public static List<BuffInfo> calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max) public static List<BuffInfo> calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1224,6 +1252,12 @@ public final class Formulas
*/ */
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill) public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{ {
// Skills without set probability should only test against trait invulnerability.
if (Double.isNaN(baseChance))
{
return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
}
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false)); // Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula // TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false)); return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
@@ -1486,7 +1520,7 @@ public final class Formulas
*/ */
public static boolean calcStunBreak(L2Character activeChar) public static boolean calcStunBreak(L2Character activeChar)
{ {
// Check if target is stunned and 10% chance. // Check if target is stunned and 10% chance (retail is 14% and 35% on crit?)
if (activeChar.hasBlockActions() && (Rnd.get(10) == 0)) if (activeChar.hasBlockActions() && (Rnd.get(10) == 0))
{ {
// Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time. // Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time.
@@ -1497,8 +1531,18 @@ public final class Formulas
public static boolean calcRealTargetBreak() public static boolean calcRealTargetBreak()
{ {
// Real Target breaks at 5% probability. // Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
return Rnd.get(20) == 0; return Rnd.get(100) <= 3;
}
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
} }
/** /**
@@ -143,6 +143,7 @@ public enum Stats
PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()), PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()),
MAGIC_ATTACK_RANGE("mAtkRange"), MAGIC_ATTACK_RANGE("mAtkRange"),
ATTACK_COUNT_MAX("atkCountMax"), ATTACK_COUNT_MAX("atkCountMax"),
PHYSICAL_POLEARM_TARGET_SINGLE("polearmSingleTarget"),
// Run speed, walk & escape speed are calculated proportionally, magic speed is a buff // Run speed, walk & escape speed are calculated proportionally, magic speed is a buff
MOVE_SPEED("moveSpeed"), MOVE_SPEED("moveSpeed"),
RUN_SPEED("runSpd", new SpeedFinalizer()), RUN_SPEED("runSpd", new SpeedFinalizer()),
@@ -266,7 +267,8 @@ public enum Stats
// Which base stat ordinal should alter skill critical formula. // Which base stat ordinal should alter skill critical formula.
STAT_BONUS_SKILL_CRITICAL("statSkillCritical"), STAT_BONUS_SKILL_CRITICAL("statSkillCritical"),
STAT_BONUS_SPEED("statSpeed"), STAT_BONUS_SPEED("statSpeed"),
SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer()); SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer()),
ATTACK_DAMAGE("attackDamage");
static final Logger LOGGER = Logger.getLogger(Stats.class.getName()); static final Logger LOGGER = Logger.getLogger(Stats.class.getName());
public static final int NUM_STATS = values().length; public static final int NUM_STATS = values().length;
@@ -29,8 +29,6 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class Attack implements IClientOutgoingPacket public class Attack implements IClientOutgoingPacket
{ {
private final int _attackerObjId; private final int _attackerObjId;
private final boolean _soulshot;
private final int _ssGrade;
private final Location _attackerLoc; private final Location _attackerLoc;
private final Location _targetLoc; private final Location _targetLoc;
private final List<Hit> _hits = new ArrayList<>(); private final List<Hit> _hits = new ArrayList<>();
@@ -38,29 +36,26 @@ public class Attack implements IClientOutgoingPacket
/** /**
* @param attacker * @param attacker
* @param target * @param target
* @param useShots
* @param ssGrade
*/ */
public Attack(L2Character attacker, L2Character target, boolean useShots, int ssGrade) public Attack(L2Character attacker, L2Character target)
{ {
_attackerObjId = attacker.getObjectId(); _attackerObjId = attacker.getObjectId();
_soulshot = useShots;
_ssGrade = Math.min(ssGrade, 6);
_attackerLoc = new Location(attacker); _attackerLoc = new Location(attacker);
_targetLoc = new Location(target); _targetLoc = new Location(target);
} }
/** /**
* Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits) * Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits)
* @param target * @param hit
* @param damage
* @param miss
* @param crit
* @param shld
*/ */
public void addHit(L2Character target, int damage, boolean miss, boolean crit, byte shld) public void addHit(Hit hit)
{ {
_hits.add(new Hit(target, damage, miss, crit, shld, _soulshot, _ssGrade)); _hits.add(hit);
}
public List<Hit> getHits()
{
return _hits;
} }
/** /**
@@ -71,14 +66,6 @@ public class Attack implements IClientOutgoingPacket
return !_hits.isEmpty(); return !_hits.isEmpty();
} }
/**
* @return {@code true} if attack has soul shot charged.
*/
public boolean hasSoulshot()
{
return _soulshot;
}
/** /**
* Writes current hit * Writes current hit
* @param packet * @param packet
@@ -43,7 +43,7 @@ public class ExAutoSoulShot implements IClientOutgoingPacket
OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet); OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet);
packet.writeD(_itemId); packet.writeD(_itemId);
packet.writeD(_enable ? 0x01 : 0x00); packet.writeD(_enable ? 0x01 : 0x00); // Underground
packet.writeD(_type); packet.writeD(_type);
return true; return true;
} }
@@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new); EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new); EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new); EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new); EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new); EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new); EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new); EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new); EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new); EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new); EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new); EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new); EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);
@@ -0,0 +1,53 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.commons.util.MathUtil;
import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Nik
*/
public class AttackDamagePosition extends AbstractEffect
{
protected final double _amount;
protected final Position _position;
public AttackDamagePosition(StatsSet params)
{
_amount = params.getDouble("amount");
_position = params.getEnum("position", Position.class);
}
@Override
public void pump(L2Character effected, Skill skill)
{
effected.getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::mul);
}
@Override
public void onExit(BuffInfo info)
{
info.getEffected().getStat().mergePositionTypeValue(Stats.ATTACK_DAMAGE, _position, (_amount / 100) + 1, MathUtil::div);
}
}
@@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage); damage = Math.max(0, damage);
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(attacker, effected, skill, critical); Formulas.calcCounterAttack(attacker, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, true); Formulas.calcCounterAttack(effector, effected, skill, true);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
} }
// No matter if lethal succeeded or not, its reflected. // No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false); Formulas.calcCounterAttack(effector, effected, skill, false);
} }
} }
@@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
{ {
@@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
} }
// Check if damage should be reflected. // Check if damage should be reflected.
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
} }
// Check if damage should be reflected // Check if damage should be reflected
Formulas.calcDamageReflected(effector, effected, skill, critical); Formulas.calcCounterAttack(effector, effected, skill, critical);
final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT); final double damageCap = effected.getStat().getValue(Stats.DAMAGE_LIMIT);
if (damageCap > 0) if (damageCap > 0)
@@ -0,0 +1,52 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package handlers.effecthandlers;
import com.l2jmobius.gameserver.model.StatsSet;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.effects.AbstractEffect;
import com.l2jmobius.gameserver.model.skills.BuffInfo;
import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.stats.Stats;
/**
* @author Sdw
*/
public class PolearmSingleTarget extends AbstractEffect
{
public PolearmSingleTarget(StatsSet params)
{
}
@Override
public void onStart(L2Character effector, L2Character effected, Skill skill)
{
if (effected.isPlayer())
{
effected.getStat().addFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE, 1.0);
}
}
@Override
public void onExit(BuffInfo info)
{
if (info.getEffected().isPlayer())
{
info.getEffected().getStat().removeFixedValue(Stats.PHYSICAL_POLEARM_TARGET_SINGLE);
}
}
}

Some files were not shown because too many files have changed in this diff Show More