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

View File

@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);

View File

@ -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);
}
}

View File

@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage);
// 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);
if (damageCap > 0)

View File

@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
}
// No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false);
Formulas.calcCounterAttack(effector, effected, skill, false);
}
}

View File

@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)
{

View File

@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -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);
}
}
}

View File

@ -93,7 +93,7 @@ public final class SoulBlow extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -98,7 +98,7 @@ public class SummonHallucination extends AbstractEffect
clone.setSummoner(player);
clone.spawnMe(x, y, z);
clone.scheduleDespawn(_despawnDelay);
clone.doAttack(effected);
clone.doAutoAttack(effected);
}
}
}

View File

@ -120,7 +120,7 @@ public class BeastSoulShot implements IItemHandler
if (!pet.isChargedShot(ShotType.SOULSHOTS))
{
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
if (activeOwner.getActiveRubyJewel() != null)
{
@ -138,7 +138,7 @@ public class BeastSoulShot implements IItemHandler
if (!s.isChargedShot(ShotType.SOULSHOTS))
{
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
if (activeOwner.getActiveRubyJewel() != null)
{

View File

@ -122,7 +122,7 @@ public class BeastSpiritShot implements IItemHandler
if (!pet.isChargedShot(shotType))
{
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
if (activeOwner.getActiveShappireJewel() != null)
{
@ -140,7 +140,7 @@ public class BeastSpiritShot implements IItemHandler
if (!s.isChargedShot(shotType))
{
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
if (activeOwner.getActiveShappireJewel() != null)
{

View File

@ -104,7 +104,7 @@ public class BlessedSoulShots implements IItemHandler
return false;
}
// Charge soul shot
weaponInst.setChargedShot(ShotType.BLESSED_SOULSHOTS, true);
activeChar.chargeShot(ShotType.BLESSED_SOULSHOTS);
}
finally
{

View File

@ -94,7 +94,7 @@ public class BlessedSpiritShot implements IItemHandler
}
// Charge Spirit shot
activeChar.setChargedShot(ShotType.SPIRITSHOTS, true);
activeChar.chargeShot(ShotType.SPIRITSHOTS);
// Send message to client
if (!activeChar.getAutoSoulShot().contains(item.getId()))

View File

@ -75,7 +75,7 @@ public class FishShots implements IItemHandler
return false;
}
activeChar.setChargedShot(ShotType.FISH_SOULSHOTS, true);
activeChar.chargeShot(ShotType.FISH_SOULSHOTS);
activeChar.destroyItemWithoutTrace("Consume", item.getObjectId(), 1, null, false);
final L2Object oldTarget = activeChar.getTarget();
activeChar.setTarget(activeChar);

View File

@ -101,7 +101,7 @@ public class SoulShots implements IItemHandler
return false;
}
// Charge soul shot
weaponInst.setChargedShot(ShotType.SOULSHOTS, true);
activeChar.chargeShot(ShotType.SOULSHOTS);
}
finally
{

View File

@ -94,7 +94,7 @@ public class SpiritShot implements IItemHandler
}
// Charge Spirit shot
activeChar.setChargedShot(ShotType.SPIRITSHOTS, true);
activeChar.chargeShot(ShotType.SPIRITSHOTS);
// Send message to client
if (!activeChar.getAutoSoulShot().contains(item.getId()))

View File

@ -865,14 +865,14 @@ public final class MemoryOfDisaster extends AbstractInstance
{
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2);
npc.doDie(null);
attacker.doAttack(world.getNpc(SILVERA));
attacker.doAutoAttack(world.getNpc(SILVERA));
break;
}
case SILVERA:
{
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD);
npc.doDie(null);
world.getNpc(SIEGE_GOLEM).doAttack(attacker);
world.getNpc(SIEGE_GOLEM).doAutoAttack(attacker);
break;
}
}

View File

@ -821,13 +821,7 @@
<hitTime>2000</hitTime>
<targetType>NONE</targetType>
<effects>
<effect name="HitNumber">
<amount>1</amount>
<mode>DIFF</mode>
<weaponType>
<item>POLE</item>
</weaponType>
</effect>
<effect name="PolearmSingleTarget" />
<effect name="Accuracy">
<amount>
<value level="1">2</value>

View File

@ -902,7 +902,15 @@
<!-- Rear Damage + 3%. -->
<operateType>P</operateType>
<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 id="19145" toLevel="2" name="Noble Death Whisper">
<!-- Physical Critical Damage + 3%. -->

View File

@ -13,6 +13,7 @@ AreaDamage: Topography (Danger Zone) resistance stat.
AttackAttribute: Stat that increases specific attack attribute.
AttackAttributeAdd: Stat that increases all attack attribute.
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.
Backstab: Inflicts physical damage according to the backstab formula.
Betray: Causes the target summon to attack its owner.
@ -205,6 +206,7 @@ PhysicalSkillPower: Physical Skill Power stat.
PhysicalSoulAttack: Physical attack depending on souls.
PkCount: Increases PK kills.
Plunder: Takes bonus item from monster. Sweep effect.
PolearmSingleTarget: Effect used by Focus Attack (317) skill.
ProtectDeathPenalty: Unable to acquire death penalty.
ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher.
PullBack: Pulls the target towards you.

View File

@ -78,7 +78,7 @@ public class DoppelgangerAI extends L2CharacterAI
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()

View File

@ -218,7 +218,7 @@ public class FriendlyNpcAI extends L2AttackableAI
return;
}
_actor.doAttack(originalAttackTarget);
_actor.doAutoAttack(originalAttackTarget);
}
@Override

View File

@ -994,7 +994,7 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
}
// Attacks target
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
private boolean checkSkillTarget(Skill skill, L2Object target)

View File

@ -223,7 +223,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
protected void thinkForceAttack()
@ -264,7 +264,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(getForcedTarget());
_actor.doAutoAttack(getForcedTarget());
}
@Override
@ -364,7 +364,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
}
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
}

View File

@ -262,7 +262,7 @@ public class L2PlayerAI extends L2PlayableAI
return;
}
_actor.doAttack((L2Character) target);
_actor.doAutoAttack((L2Character) target);
}
private void thinkCast()

View File

@ -107,7 +107,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()
@ -276,7 +276,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
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))
{
summon.doAttack(attacker);
summon.doAutoAttack(attacker);
}
}

View File

@ -245,18 +245,13 @@ public class Fishing
}
double chance = baitData.getChance();
final boolean isCharged = _player.isChargedShot(ShotType.FISH_SOULSHOTS);
if (isCharged)
if (_player.isChargedShot(ShotType.FISH_SOULSHOTS))
{
chance *= 1.50; // +50 % chance to win
chance *= 1.5; // +50 % chance to win
}
if (Rnd.get(0, 100) <= chance)
{
if (isCharged)
{
_player.setChargedShot(ShotType.FISH_SOULSHOTS, false);
}
reelIn(FishingEndReason.WIN, true);
}
else

View File

@ -16,6 +16,8 @@
*/
package com.l2jmobius.gameserver.model;
import java.lang.ref.WeakReference;
import com.l2jmobius.gameserver.enums.AttackType;
import com.l2jmobius.gameserver.model.actor.L2Character;
@ -24,6 +26,7 @@ import com.l2jmobius.gameserver.model.actor.L2Character;
*/
public class Hit
{
private final WeakReference<L2Object> _target;
private final int _targetId;
private final int _damage;
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)
{
_target = new WeakReference<>(target);
_targetId = target.getObjectId();
_damage = damage;
_ssGrade = ssGrade;
@ -62,6 +66,11 @@ public class Hit
_flags |= type.getMask();
}
public L2Object getTarget()
{
return _target.get();
}
public int getTargetId()
{
return _targetId;
@ -81,4 +90,24 @@ public class Hit
{
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;
}
}

View File

@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.handler.ActionHandler;
import com.l2jmobius.gameserver.handler.ActionShiftHandler;
import com.l2jmobius.gameserver.handler.IActionHandler;
@ -463,35 +462,6 @@ public abstract class L2Object extends ListenersContainer implements IIdentifiab
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 script

View File

@ -128,7 +128,7 @@ public class ShortCuts implements IRestorable
{
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())
{
_owner.sendPacket(new ExAutoSoulShot(shotId, 1));
_owner.sendPacket(new ExAutoSoulShot(shotId, true, 0));
}
}

View File

@ -144,7 +144,6 @@ public class L2Npc extends L2Character
private int _spiritshotamount = 0;
private int _displayEffect = 0;
private int _shotsMask = 0;
private int _killingBlowWeaponId;
private int _cloneObjId; // Used in NpcInfo packet to clone the specified player.
@ -1410,47 +1409,43 @@ public class L2Npc extends L2Character
}
@Override
public boolean isChargedShot(ShotType type)
public void rechargeShots(boolean physical, boolean magic, boolean fish)
{
return (_shotsMask & type.getMask()) == type.getMask();
}
@Override
public void setChargedShot(ShotType type, boolean charged)
{
if (charged)
if (isFakePlayer() && Config.FAKE_PLAYER_USE_SHOTS)
{
_shotsMask |= type.getMask();
if (physical)
{
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 9193, 1, 0, 0), 600);
chargeShot(ShotType.SOULSHOTS);
}
if (magic)
{
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 9195, 1, 0, 0), 600);
chargeShot(ShotType.SPIRITSHOTS);
}
}
else
{
_shotsMask &= ~type.getMask();
}
}
@Override
public void rechargeShots(boolean physical, boolean magic, boolean fish)
{
if (physical && (_soulshotamount > 0))
{
if (Rnd.get(100) > getTemplate().getSoulShotChance())
if (physical && (_soulshotamount > 0))
{
return;
if (Rnd.get(100) > getTemplate().getSoulShotChance())
{
return;
}
_soulshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2154, 1, 0, 0), 600);
chargeShot(ShotType.SOULSHOTS);
}
_soulshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2154, 1, 0, 0), 600);
setChargedShot(ShotType.SOULSHOTS, true);
}
if (magic && (_spiritshotamount > 0))
{
if (Rnd.get(100) > getTemplate().getSpiritShotChance())
if (magic && (_spiritshotamount > 0))
{
return;
if (Rnd.get(100) > getTemplate().getSpiritShotChance())
{
return;
}
_spiritshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2061, 1, 0, 0), 600);
chargeShot(ShotType.SPIRITSHOTS);
}
_spiritshotamount--;
Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2061, 1, 0, 0), 600);
setChargedShot(ShotType.SPIRITSHOTS, true);
}
}

View File

@ -26,7 +26,6 @@ import com.l2jmobius.gameserver.data.xml.impl.ExperienceData;
import com.l2jmobius.gameserver.datatables.ItemTable;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.Team;
import com.l2jmobius.gameserver.handler.IItemHandler;
import com.l2jmobius.gameserver.handler.ItemHandler;
@ -77,7 +76,6 @@ public abstract class L2Summon extends L2Playable
private boolean _follow = true;
private boolean _previousFollowStatus = true;
protected boolean _restoreSummon = true;
private int _shotsMask = 0;
private int _summonPoints = 0;
// @formatter:off
@ -1030,25 +1028,6 @@ public abstract class L2Summon extends L2Playable
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
public void rechargeShots(boolean physical, boolean magic, boolean fish)
{

View File

@ -91,7 +91,7 @@ public final class L2DoorInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@ -92,7 +92,6 @@ import com.l2jmobius.gameserver.enums.PrivateStoreType;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.Sex;
import com.l2jmobius.gameserver.enums.ShortcutType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
import com.l2jmobius.gameserver.enums.SubclassInfoType;
import com.l2jmobius.gameserver.enums.Team;
@ -4643,9 +4642,9 @@ public final class L2PcInstance extends L2Playable
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
super.doAttack(target);
super.doAutoAttack(target);
setRecentFakeDeath(false);
if (target.isFakePlayer())
{
@ -8788,7 +8787,8 @@ public final class L2PcInstance extends L2Playable
if (_activeSoulShots.contains(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);
sm.addItemName(itemId);
sendPacket(sm);
@ -8804,7 +8804,7 @@ public final class L2PcInstance extends L2Playable
{
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);
sm.addItemName(itemId);
sendPacket(sm);
@ -13065,23 +13065,6 @@ public final class L2PcInstance extends L2Playable
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
* @return the custom skill

View File

@ -195,7 +195,7 @@ public final class L2StaticObjectInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@ -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);
}
}
}

View File

@ -19,6 +19,7 @@ package com.l2jmobius.gameserver.model.events.impl.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.events.EventType;
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.
@ -28,11 +29,13 @@ public class OnCreatureAttack implements IBaseEvent
{
private final L2Character _attacker;
private final L2Character _target;
private final Skill _skill;
public OnCreatureAttack(L2Character attacker, L2Character target)
public OnCreatureAttack(L2Character attacker, L2Character target, Skill skill)
{
_attacker = attacker;
_target = target;
_skill = skill;
}
public final L2Character getAttacker()
@ -45,6 +48,11 @@ public class OnCreatureAttack implements IBaseEvent
return _target;
}
public final Skill getSkill()
{
return _skill;
}
@Override
public EventType getType()
{

View File

@ -45,7 +45,6 @@ import com.l2jmobius.gameserver.enums.AttributeType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemLocation;
import com.l2jmobius.gameserver.enums.ItemSkillType;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.UserInfoType;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
import com.l2jmobius.gameserver.idfactory.IdFactory;
@ -173,8 +172,6 @@ public final class L2ItemInstance extends L2Object
private final DropProtection _dropProtection = new DropProtection();
private int _shotsMask = 0;
private final List<Options> _enchantOptions = new ArrayList<>();
/**
@ -1399,7 +1396,6 @@ public final class L2ItemInstance extends L2Object
final InventoryUpdate iu = new InventoryUpdate();
for (L2ItemInstance item : unequiped)
{
item.unChargeAllShots();
iu.addModifiedItem(item);
}
player.sendInventoryUpdate(iu);
@ -1827,7 +1823,6 @@ public final class L2ItemInstance extends L2Object
final InventoryUpdate iu = new InventoryUpdate();
for (L2ItemInstance item : unequiped)
{
item.unChargeAllShots();
iu.addModifiedItem(item);
}
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
* @return enchanteffect

View File

@ -271,11 +271,7 @@ public abstract class AbstractOlympiadGame
player.disableAutoShotsAll();
// Discharge any active shots
final L2ItemInstance item = player.getActiveWeaponInstance();
if (item != null)
{
item.unChargeAllShots();
}
player.unchargeAllShots();
// enable skills with cool time <= 15 minutes
for (Skill skill : player.getAllSkills())

View File

@ -1514,11 +1514,11 @@ public final class Skill implements IIdentifiable
{
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())
{
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);
}
}

View File

@ -217,11 +217,11 @@ public class SkillChannelizer implements Runnable
// Reduce shots.
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
{
_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.

View File

@ -37,6 +37,7 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
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.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@ -271,8 +272,12 @@ public final class Formulas
}
// Autoattack critical rate.
// It is capped to 500, but unbound by positional critical rate and level diff bonus.
rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
// 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.
// 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);
}
/**
* 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 target
@ -403,16 +439,6 @@ public final class Formulas
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
* @param attacker
@ -1023,7 +1049,7 @@ public final class Formulas
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
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));
counterdmg *= calcWeaponTraitBonus(attacker, target);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
counterdmg *= calcAttributeBonus(attacker, 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);
}
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
final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
final Position position = Position.getPosition(activeChar, target);
final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double critHeightBonus = calcCriticalHeightBonus(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 chanceBoostMod = (100 + chanceBoost) / 100;
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
blowChance = (weaponCritical + blowChance) * 10;
final double rate = blowChance * critHeightBonus * criticalPositionMod * 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);
}
final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
// 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)
@ -1224,6 +1252,12 @@ public final class Formulas
*/
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));
// 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));
@ -1486,7 +1520,7 @@ public final class Formulas
*/
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))
{
// 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()
{
// Real Target breaks at 5% probability.
return Rnd.get(20) == 0;
// Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
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));
}
/**

View File

@ -143,6 +143,7 @@ public enum Stats
PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()),
MAGIC_ATTACK_RANGE("mAtkRange"),
ATTACK_COUNT_MAX("atkCountMax"),
PHYSICAL_POLEARM_TARGET_SINGLE("polearmSingleTarget"),
// Run speed, walk & escape speed are calculated proportionally, magic speed is a buff
MOVE_SPEED("moveSpeed"),
RUN_SPEED("runSpd", new SpeedFinalizer()),
@ -266,7 +267,8 @@ public enum Stats
// Which base stat ordinal should alter skill critical formula.
STAT_BONUS_SKILL_CRITICAL("statSkillCritical"),
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());
public static final int NUM_STATS = values().length;

View File

@ -18,6 +18,7 @@ package com.l2jmobius.gameserver.network.clientpackets;
import com.l2jmobius.commons.network.PacketReader;
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.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.items.L2Item;
@ -34,6 +35,7 @@ import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
public final class RequestAutoSoulShot implements IClientIncomingPacket
{
private int _itemId;
private boolean _enable;
private int _type;
@Override
@ -41,6 +43,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
{
_itemId = packet.readD();
_type = packet.readD();
_enable = _type == 1;
return true;
}
@ -61,7 +64,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
return;
}
if (_type == 1)
if (_enable)
{
if (!activeChar.getInventory().canManipulateWithItemId(item.getId()))
{
@ -114,23 +117,34 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
// Activate shots
activeChar.addAutoSoulShot(_itemId);
client.sendPacket(new ExAutoSoulShot(_itemId, _type));
// Send message
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_AUTOMATIC_USE_OF_S1_HAS_BEEN_ACTIVATED);
sm.addItemName(item);
client.sendPacket(sm);
client.sendPacket(new ExAutoSoulShot(_itemId, _enable, _type));
// Recharge summon's shots
final L2Summon pet = activeChar.getPet();
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);
}
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
{
@ -150,7 +164,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
// Activate shots
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);
@ -165,7 +179,7 @@ public final class RequestAutoSoulShot implements IClientIncomingPacket
{
// Cancel auto shots
activeChar.removeAutoSoulShot(_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_DEACTIVATED);

View File

@ -180,11 +180,7 @@ public final class RequestDropItem implements IClientIncomingPacket
if (item.isEquipped())
{
final L2ItemInstance[] unequiped = activeChar.getInventory().unEquipItemInSlotAndRecord(item.getLocationSlot());
for (L2ItemInstance itm : unequiped)
{
itm.unChargeAllShots();
}
activeChar.getInventory().unEquipItemInSlot(item.getLocationSlot());
activeChar.broadcastUserInfo();
activeChar.sendItemList(true);
}

View File

@ -29,8 +29,6 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class Attack implements IClientOutgoingPacket
{
private final int _attackerObjId;
private final boolean _soulshot;
private final int _ssGrade;
private final Location _attackerLoc;
private final Location _targetLoc;
private final List<Hit> _hits = new ArrayList<>();
@ -38,29 +36,26 @@ public class Attack implements IClientOutgoingPacket
/**
* @param attacker
* @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();
_soulshot = useShots;
_ssGrade = Math.min(ssGrade, 6);
_attackerLoc = new Location(attacker);
_targetLoc = new Location(target);
}
/**
* Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits)
* @param target
* @param damage
* @param miss
* @param crit
* @param shld
* @param hit
*/
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 {@code true} if attack has soul shot charged.
*/
public boolean hasSoulshot()
{
return _soulshot;
}
/**
* Writes current hit
* @param packet

View File

@ -22,11 +22,19 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class ExAutoSoulShot implements IClientOutgoingPacket
{
private final int _itemId;
@SuppressWarnings("unused")
private final boolean _enable;
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;
_enable = enable;
_type = type;
}
@ -36,6 +44,7 @@ public class ExAutoSoulShot implements IClientOutgoingPacket
OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet);
packet.writeD(_itemId);
// packet.writeD(_enable ? 0x01 : 0x00);
packet.writeD(_type);
return true;
}

View File

@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);

View File

@ -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);
}
}

View File

@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage);
// 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);
if (damageCap > 0)

View File

@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
}
// No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false);
Formulas.calcCounterAttack(effector, effected, skill, false);
}
}

View File

@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)
{

View File

@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -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);
}
}
}

View File

@ -93,7 +93,7 @@ public final class SoulBlow extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -98,7 +98,7 @@ public class SummonHallucination extends AbstractEffect
clone.setSummoner(player);
clone.spawnMe(x, y, z);
clone.scheduleDespawn(_despawnDelay);
clone.doAttack(effected);
clone.doAutoAttack(effected);
}
}
}

View File

@ -865,14 +865,14 @@ public final class MemoryOfDisaster extends AbstractInstance
{
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.NO_WAY2);
npc.doDie(null);
attacker.doAttack(world.getNpc(SILVERA));
attacker.doAutoAttack(world.getNpc(SILVERA));
break;
}
case SILVERA:
{
npc.broadcastSay(ChatType.NPC_GENERAL, NpcStringId.MY_GOD);
npc.doDie(null);
world.getNpc(SIEGE_GOLEM).doAttack(attacker);
world.getNpc(SIEGE_GOLEM).doAutoAttack(attacker);
break;
}
}

View File

@ -821,13 +821,7 @@
<hitTime>2000</hitTime>
<targetType>NONE</targetType>
<effects>
<effect name="HitNumber">
<amount>1</amount>
<mode>DIFF</mode>
<weaponType>
<item>POLE</item>
</weaponType>
</effect>
<effect name="PolearmSingleTarget" />
<effect name="Accuracy">
<amount>
<value level="1">2</value>

View File

@ -902,7 +902,15 @@
<!-- Rear Damage + 3%. -->
<operateType>P</operateType>
<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 id="19145" toLevel="2" name="Noble Death Whisper">
<!-- Physical Critical Damage + 3%. -->

View File

@ -13,6 +13,7 @@ AreaDamage: Topography (Danger Zone) resistance stat.
AttackAttribute: Stat that increases specific attack attribute.
AttackAttributeAdd: Stat that increases all attack attribute.
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.
Backstab: Inflicts physical damage according to the backstab formula.
Betray: Causes the target summon to attack its owner.
@ -205,6 +206,7 @@ PhysicalSkillPower: Physical Skill Power stat.
PhysicalSoulAttack: Physical attack depending on souls.
PkCount: Increases PK kills.
Plunder: Takes bonus item from monster. Sweep effect.
PolearmSingleTarget: Effect used by Focus Attack (317) skill.
ProtectDeathPenalty: Unable to acquire death penalty.
ProtectionBlessing: Keeps you safe from a PK if he is 10 levels or higher.
PullBack: Pulls the target towards you.

View File

@ -78,7 +78,7 @@ public class DoppelgangerAI extends L2CharacterAI
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()

View File

@ -218,7 +218,7 @@ public class FriendlyNpcAI extends L2AttackableAI
return;
}
_actor.doAttack(originalAttackTarget);
_actor.doAutoAttack(originalAttackTarget);
}
@Override

View File

@ -994,7 +994,7 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
}
// Attacks target
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
private boolean checkSkillTarget(Skill skill, L2Object target)

View File

@ -223,7 +223,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
protected void thinkForceAttack()
@ -264,7 +264,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(getForcedTarget());
_actor.doAutoAttack(getForcedTarget());
}
@Override
@ -364,7 +364,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
}
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
}

View File

@ -262,7 +262,7 @@ public class L2PlayerAI extends L2PlayableAI
return;
}
_actor.doAttack((L2Character) target);
_actor.doAutoAttack((L2Character) target);
}
private void thinkCast()

View File

@ -107,7 +107,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()
@ -276,7 +276,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
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))
{
summon.doAttack(attacker);
summon.doAutoAttack(attacker);
}
}

View File

@ -16,6 +16,8 @@
*/
package com.l2jmobius.gameserver.model;
import java.lang.ref.WeakReference;
import com.l2jmobius.gameserver.enums.AttackType;
import com.l2jmobius.gameserver.model.actor.L2Character;
@ -24,6 +26,7 @@ import com.l2jmobius.gameserver.model.actor.L2Character;
*/
public class Hit
{
private final WeakReference<L2Object> _target;
private final int _targetId;
private final int _damage;
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)
{
_target = new WeakReference<>(target);
_targetId = target.getObjectId();
_damage = damage;
_ssGrade = ssGrade;
@ -62,6 +66,11 @@ public class Hit
_flags |= type.getMask();
}
public L2Object getTarget()
{
return _target.get();
}
public int getTargetId()
{
return _targetId;
@ -81,4 +90,24 @@ public class Hit
{
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;
}
}

View File

@ -91,7 +91,7 @@ public final class L2DoorInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@ -4642,9 +4642,9 @@ public final class L2PcInstance extends L2Playable
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
super.doAttack(target);
super.doAutoAttack(target);
setRecentFakeDeath(false);
if (target.isFakePlayer())
{

View File

@ -195,7 +195,7 @@ public final class L2StaticObjectInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@ -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);
}
}
}

View File

@ -19,6 +19,7 @@ package com.l2jmobius.gameserver.model.events.impl.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.events.EventType;
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.
@ -28,11 +29,13 @@ public class OnCreatureAttack implements IBaseEvent
{
private final L2Character _attacker;
private final L2Character _target;
private final Skill _skill;
public OnCreatureAttack(L2Character attacker, L2Character target)
public OnCreatureAttack(L2Character attacker, L2Character target, Skill skill)
{
_attacker = attacker;
_target = target;
_skill = skill;
}
public final L2Character getAttacker()
@ -45,6 +48,11 @@ public class OnCreatureAttack implements IBaseEvent
return _target;
}
public final Skill getSkill()
{
return _skill;
}
@Override
public EventType getType()
{

View File

@ -37,6 +37,7 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
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.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@ -271,8 +272,12 @@ public final class Formulas
}
// Autoattack critical rate.
// It is capped to 500, but unbound by positional critical rate and level diff bonus.
rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
// 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.
// 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);
}
/**
* 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 target
@ -403,16 +439,6 @@ public final class Formulas
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
* @param attacker
@ -1023,7 +1049,7 @@ public final class Formulas
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
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));
counterdmg *= calcWeaponTraitBonus(attacker, target);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
counterdmg *= calcAttributeBonus(attacker, 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);
}
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
final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
final Position position = Position.getPosition(activeChar, target);
final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double critHeightBonus = calcCriticalHeightBonus(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 chanceBoostMod = (100 + chanceBoost) / 100;
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
blowChance = (weaponCritical + blowChance) * 10;
final double rate = blowChance * critHeightBonus * criticalPositionMod * 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);
}
final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
// 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)
@ -1224,6 +1252,12 @@ public final class Formulas
*/
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));
// 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));
@ -1486,7 +1520,7 @@ public final class Formulas
*/
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))
{
// 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()
{
// Real Target breaks at 5% probability.
return Rnd.get(20) == 0;
// Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
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));
}
/**

View File

@ -143,6 +143,7 @@ public enum Stats
PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()),
MAGIC_ATTACK_RANGE("mAtkRange"),
ATTACK_COUNT_MAX("atkCountMax"),
PHYSICAL_POLEARM_TARGET_SINGLE("polearmSingleTarget"),
// Run speed, walk & escape speed are calculated proportionally, magic speed is a buff
MOVE_SPEED("moveSpeed"),
RUN_SPEED("runSpd", new SpeedFinalizer()),
@ -266,7 +267,8 @@ public enum Stats
// Which base stat ordinal should alter skill critical formula.
STAT_BONUS_SKILL_CRITICAL("statSkillCritical"),
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());
public static final int NUM_STATS = values().length;

View File

@ -29,8 +29,6 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class Attack implements IClientOutgoingPacket
{
private final int _attackerObjId;
private final boolean _soulshot;
private final int _ssGrade;
private final Location _attackerLoc;
private final Location _targetLoc;
private final List<Hit> _hits = new ArrayList<>();
@ -38,29 +36,26 @@ public class Attack implements IClientOutgoingPacket
/**
* @param attacker
* @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();
_soulshot = useShots;
_ssGrade = Math.min(ssGrade, 6);
_attackerLoc = new Location(attacker);
_targetLoc = new Location(target);
}
/**
* Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits)
* @param target
* @param damage
* @param miss
* @param crit
* @param shld
* @param hit
*/
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 {@code true} if attack has soul shot charged.
*/
public boolean hasSoulshot()
{
return _soulshot;
}
/**
* Writes current hit
* @param packet

View File

@ -43,7 +43,7 @@ public class ExAutoSoulShot implements IClientOutgoingPacket
OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet);
packet.writeD(_itemId);
packet.writeD(_enable ? 0x01 : 0x00);
packet.writeD(_enable ? 0x01 : 0x00); // Underground
packet.writeD(_type);
return true;
}

View File

@ -44,6 +44,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("AttackAttribute", AttackAttribute::new);
EffectHandler.getInstance().registerHandler("AttackAttributeAdd", AttackAttributeAdd::new);
EffectHandler.getInstance().registerHandler("AttackBehind", AttackBehind::new);
EffectHandler.getInstance().registerHandler("AttackDamagePosition", AttackDamagePosition::new);
EffectHandler.getInstance().registerHandler("AttackTrait", AttackTrait::new);
EffectHandler.getInstance().registerHandler("Backstab", Backstab::new);
EffectHandler.getInstance().registerHandler("Betray", Betray::new);
@ -236,6 +237,7 @@ public final class EffectMasterHandler
EffectHandler.getInstance().registerHandler("PhysicalSoulAttack", PhysicalSoulAttack::new);
EffectHandler.getInstance().registerHandler("PkCount", PkCount::new);
EffectHandler.getInstance().registerHandler("Plunder", Plunder::new);
EffectHandler.getInstance().registerHandler("PolearmSingleTarget", PolearmSingleTarget::new);
EffectHandler.getInstance().registerHandler("ProtectionBlessing", ProtectionBlessing::new);
EffectHandler.getInstance().registerHandler("ProtectDeathPenalty", ProtectDeathPenalty::new);
EffectHandler.getInstance().registerHandler("PullBack", PullBack::new);

View File

@ -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);
}
}

View File

@ -88,7 +88,7 @@ public final class Backstab extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -163,7 +163,7 @@ public final class EnergyAttack extends AbstractEffect
damage = Math.max(0, damage);
// 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);
if (damageCap > 0)

View File

@ -119,7 +119,7 @@ public final class FatalBlow extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -115,6 +115,6 @@ public final class Lethal extends AbstractEffect
}
// No matter if lethal succeeded or not, its reflected.
Formulas.calcDamageReflected(effector, effected, skill, false);
Formulas.calcCounterAttack(effector, effected, skill, false);
}
}

View File

@ -173,7 +173,7 @@ public final class PhysicalAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)
{

View File

@ -135,7 +135,7 @@ public final class PhysicalAttackHpLink extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -145,7 +145,7 @@ public final class PhysicalAttackSaveHp extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -166,7 +166,7 @@ public final class PhysicalAttackWeaponBonus extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -160,7 +160,7 @@ public final class PhysicalSoulAttack extends AbstractEffect
}
// 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);
if (damageCap > 0)

View File

@ -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