Jelajahi Sumber

BETA: '''Skills rework''':
* Reworked `MagicSkillLaunched` packet.
* Fixed wrong packet structure of `MagicSkillUse`
* Thanks to: Nos
* Re-implemented signet skills handling as it has to be.
* Re-implemented fusion skills handling as it has to be.
* Implemented new effects group called channelingEffects which is applied for CA1, CA5 operate types as long as skill is casting (by ticks specified in skill)
* Fixed minor visual glitch that appears when you try to cast a ground type skill your toon is like jumping when u don't match requirements.
* `writeLoc()` method in `L2GameServerPacket` is using `IPositionable` now.
* Moved `getSummoner()`, `setSummoner()` methods from `L2Npc` to `L2Character`.
* Reviewed by: Zoey76, Nos, Adry76, nBd

Rumen Nikiforov 11 tahun lalu
induk
melakukan
96f5e3d52e
22 mengubah file dengan 698 tambahan dan 680 penghapusan
  1. 16 2
      L2J_Server_BETA/java/com/l2jserver/gameserver/engines/DocumentBase.java
  2. 4 0
      L2J_Server_BETA/java/com/l2jserver/gameserver/engines/skills/DocumentSkill.java
  3. 5 9
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/ChanceSkillList.java
  4. 0 11
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/CharEffectList.java
  5. 0 119
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/FusionSkill.java
  6. 4 7
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Party.java
  7. 96 186
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Character.java
  8. 4 35
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Npc.java
  9. 6 57
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java
  10. 0 3
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/effects/L2EffectType.java
  11. 85 49
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2Skill.java
  12. 15 0
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2SkillOpType.java
  13. 0 7
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2SkillType.java
  14. 87 0
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/SkillChannelized.java
  15. 311 0
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/SkillChannelizer.java
  16. 0 75
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/l2skills/L2SkillSignet.java
  17. 0 46
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/l2skills/L2SkillSignetCasttime.java
  18. 2 2
      L2J_Server_BETA/java/com/l2jserver/gameserver/model/stats/Formulas.java
  19. 3 3
      L2J_Server_BETA/java/com/l2jserver/gameserver/network/clientpackets/RequestExMagicSkillUseGround.java
  20. 2 2
      L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/L2GameServerPacket.java
  21. 17 32
      L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/MagicSkillLaunched.java
  22. 41 35
      L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/MagicSkillUse.java

+ 16 - 2
L2J_Server_BETA/java/com/l2jserver/gameserver/engines/DocumentBase.java

@@ -179,6 +179,11 @@ public abstract class DocumentBase
 	}
 	
 	protected void parseTemplate(Node n, Object template)
+	{
+		parseTemplate(n, template, false);
+	}
+	
+	protected void parseTemplate(Node n, Object template, boolean isChanneling)
 	{
 		Condition condition = null;
 		n = n.getFirstChild();
@@ -250,7 +255,7 @@ public abstract class DocumentBase
 				{
 					throw new RuntimeException("Nested effects");
 				}
-				attachEffect(n, template, condition);
+				attachEffect(n, template, condition, isChanneling);
 			}
 		}
 	}
@@ -289,6 +294,11 @@ public abstract class DocumentBase
 	}
 	
 	protected void attachEffect(Node n, Object template, Condition attachCond)
+	{
+		attachEffect(n, template, attachCond, false);
+	}
+	
+	protected void attachEffect(Node n, Object template, Condition attachCond, boolean isChanneling)
 	{
 		final NamedNodeMap attrs = n.getAttributes();
 		final StatsSet set = new StatsSet();
@@ -310,7 +320,11 @@ public abstract class DocumentBase
 		else if (template instanceof L2Skill)
 		{
 			final L2Skill sk = (L2Skill) template;
-			if (set.getInt("self", 0) == 1)
+			if (isChanneling)
+			{
+				sk.attachChanneling(effectTemplate);
+			}
+			else if (set.getInt("self", 0) == 1)
 			{
 				sk.attachSelf(effectTemplate);
 			}

+ 4 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/engines/skills/DocumentSkill.java

@@ -502,6 +502,10 @@ public class DocumentSkill extends DocumentBase
 				{
 					parseTemplate(n, _currentSkill.currentSkills.get(i));
 				}
+				else if ("channelingEffects".equalsIgnoreCase(n.getNodeName()))
+				{
+					parseTemplate(n, _currentSkill.currentSkills.get(i), true);
+				}
 			}
 		}
 		for (int i = lastLvl; i < (lastLvl + enchantLevels1); i++)

+ 5 - 9
L2J_Server_BETA/java/com/l2jserver/gameserver/model/ChanceSkillList.java

@@ -162,13 +162,13 @@ public class ChanceSkillList extends FastMap<IChanceSkillTrigger, ChanceConditio
 				return;
 			}
 			
-			L2Skill triggered = SkillTable.getInstance().getInfo(effect.getTriggeredChanceId(), effect.getTriggeredChanceLevel());
+			final L2Skill triggered = SkillTable.getInstance().getInfo(effect.getTriggeredChanceId(), effect.getTriggeredChanceLevel());
 			if (triggered == null)
 			{
 				return;
 			}
-			L2Character caster = triggered.getTargetType() == L2TargetType.SELF ? _owner : effect.getEffector();
 			
+			final L2Character caster = triggered.getTargetType() == L2TargetType.SELF ? _owner : effect.getEffector();
 			if ((caster == null) || (triggered.getSkillType() == L2SkillType.NOTDONE) || caster.isSkillDisabled(triggered))
 			{
 				return;
@@ -179,22 +179,18 @@ public class ChanceSkillList extends FastMap<IChanceSkillTrigger, ChanceConditio
 				caster.disableSkill(triggered, triggered.getReuseDelay());
 			}
 			
-			L2Object[] targets = triggered.getTargetList(caster, false, target);
-			
+			final L2Object[] targets = triggered.getTargetList(caster, false, target);
 			if (targets.length == 0)
 			{
 				return;
 			}
 			
-			L2Character firstTarget = (L2Character) targets[0];
-			
-			ISkillHandler handler = SkillHandler.getInstance().getHandler(triggered.getSkillType());
-			
 			_owner.broadcastPacket(new MagicSkillLaunched(_owner, triggered.getDisplayId(), triggered.getDisplayLevel(), targets));
-			_owner.broadcastPacket(new MagicSkillUse(_owner, firstTarget, triggered.getDisplayId(), triggered.getDisplayLevel(), 0, 0));
+			_owner.broadcastPacket(new MagicSkillUse(_owner, target, triggered.getDisplayId(), triggered.getDisplayLevel(), 0, 0));
 			
 			// Launch the magic skill and calculate its effects
 			// TODO: once core will support all possible effects, use effects (not handler)
+			final ISkillHandler handler = SkillHandler.getInstance().getHandler(triggered.getSkillType());
 			if (handler != null)
 			{
 				handler.useSkill(caster, triggered, targets);

+ 0 - 11
L2J_Server_BETA/java/com/l2jserver/gameserver/model/CharEffectList.java

@@ -1006,11 +1006,6 @@ public final class CharEffectList
 					continue;
 				}
 				
-				if (e.getEffectType() == L2EffectType.SIGNET_GROUND)
-				{
-					continue;
-				}
-				
 				if (e.isInUse())
 				{
 					if (mi != null)
@@ -1070,12 +1065,6 @@ public final class CharEffectList
 					continue;
 				}
 				
-				switch (e.getEffectType())
-				{
-					case SIGNET_GROUND:
-						continue;
-				}
-				
 				if (e.isInUse())
 				{
 					if (mi != null)

+ 0 - 119
L2J_Server_BETA/java/com/l2jserver/gameserver/model/FusionSkill.java

@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2004-2013 L2J Server
- * 
- * This file is part of L2J Server.
- * 
- * L2J Server 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.
- * 
- * L2J Server 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.l2jserver.gameserver.model;
-
-import java.util.concurrent.Future;
-import java.util.logging.Logger;
-
-import com.l2jserver.gameserver.GeoData;
-import com.l2jserver.gameserver.ThreadPoolManager;
-import com.l2jserver.gameserver.datatables.SkillTable;
-import com.l2jserver.gameserver.model.actor.L2Character;
-import com.l2jserver.gameserver.model.effects.L2Effect;
-import com.l2jserver.gameserver.model.skills.L2Skill;
-import com.l2jserver.gameserver.util.Util;
-
-/**
- * @author kombat, Forsaiken
- */
-public final class FusionSkill
-{
-	protected static final Logger _log = Logger.getLogger(FusionSkill.class.getName());
-	
-	protected int _skillCastRange;
-	protected int _fusionId;
-	protected int _fusionLevel;
-	protected L2Character _caster;
-	protected L2Character _target;
-	protected Future<?> _geoCheckTask;
-	
-	public L2Character getCaster()
-	{
-		return _caster;
-	}
-	
-	public L2Character getTarget()
-	{
-		return _target;
-	}
-	
-	public FusionSkill(L2Character caster, L2Character target, L2Skill skill)
-	{
-		_skillCastRange = skill.getCastRange();
-		_caster = caster;
-		_target = target;
-		_fusionId = skill.getTriggeredId();
-		_fusionLevel = skill.getTriggeredLevel();
-		
-		L2Effect effect = _target.getFirstEffect(_fusionId);
-		if (effect != null)
-		{
-			effect.increaseEffect();
-		}
-		else
-		{
-			L2Skill force = SkillTable.getInstance().getInfo(_fusionId, _fusionLevel);
-			if (force != null)
-			{
-				force.getEffects(_caster, _target, null);
-			}
-			else
-			{
-				_log.warning("Triggered skill [" + _fusionId + ";" + _fusionLevel + "] not found!");
-			}
-		}
-		_geoCheckTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new GeoCheckTask(), 1000, 1000);
-	}
-	
-	public void onCastAbort()
-	{
-		_caster.setFusionSkill(null);
-		L2Effect effect = _target.getFirstEffect(_fusionId);
-		if (effect != null)
-		{
-			effect.decreaseForce();
-		}
-		
-		_geoCheckTask.cancel(true);
-	}
-	
-	public class GeoCheckTask implements Runnable
-	{
-		@Override
-		public void run()
-		{
-			try
-			{
-				if (!Util.checkIfInRange(_skillCastRange, _caster, _target, true))
-				{
-					_caster.abortCast();
-				}
-				
-				if (!GeoData.getInstance().canSeeTarget(_caster, _target))
-				{
-					_caster.abortCast();
-				}
-			}
-			catch (Exception e)
-			{
-				// ignore
-			}
-		}
-	}
-}

+ 4 - 7
L2J_Server_BETA/java/com/l2jserver/gameserver/model/L2Party.java

@@ -416,17 +416,14 @@ public class L2Party extends AbstractPlayerGroup
 			
 			try
 			{
-				if (player.getFusionSkill() != null)
+				// Channeling a player!
+				if (player.isChanneling() && (player.getSkillChannelizer().hasChannelized()))
 				{
 					player.abortCast();
 				}
-				
-				for (L2Character character : player.getKnownList().getKnownCharacters())
+				else if (player.isChannelized())
 				{
-					if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == player))
-					{
-						character.abortCast();
-					}
+					player.getSkillChannelized().abortChannelization();
 				}
 			}
 			catch (Exception e)

+ 96 - 186
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Character.java

@@ -60,7 +60,6 @@ import com.l2jserver.gameserver.instancemanager.TerritoryWarManager;
 import com.l2jserver.gameserver.instancemanager.TownManager;
 import com.l2jserver.gameserver.model.ChanceSkillList;
 import com.l2jserver.gameserver.model.CharEffectList;
-import com.l2jserver.gameserver.model.FusionSkill;
 import com.l2jserver.gameserver.model.L2AccessLevel;
 import com.l2jserver.gameserver.model.L2Object;
 import com.l2jserver.gameserver.model.L2Party;
@@ -107,6 +106,8 @@ import com.l2jserver.gameserver.model.options.OptionsSkillType;
 import com.l2jserver.gameserver.model.quest.Quest;
 import com.l2jserver.gameserver.model.skills.L2Skill;
 import com.l2jserver.gameserver.model.skills.L2SkillType;
+import com.l2jserver.gameserver.model.skills.SkillChannelized;
+import com.l2jserver.gameserver.model.skills.SkillChannelizer;
 import com.l2jserver.gameserver.model.skills.funcs.Func;
 import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSummon;
 import com.l2jserver.gameserver.model.skills.targets.L2TargetType;
@@ -205,9 +206,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 	/** Map containing the active chance skills on this character */
 	private volatile ChanceSkillList _chanceSkills;
 	
-	/** Current force buff this caster is casting to a target */
-	protected FusionSkill _fusionSkill;
-	
 	private final byte[] _zones = new byte[ZoneId.getZoneCount()];
 	protected byte _zoneValidateCounter = 4;
 	
@@ -225,6 +223,13 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 	
 	private final CharEffectList _effectList = new CharEffectList(this);
 	
+	/** The character that summons this character. */
+	private L2Character _summoner = null;
+	
+	private SkillChannelizer _channelizer = null;
+	
+	private SkillChannelized _channelized = null;
+	
 	public final CharEffectList getEffectList()
 	{
 		return _effectList;
@@ -1750,9 +1755,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		// Get the Base Casting Time of the Skills.
 		int skillTime = (skill.getHitTime() + skill.getCoolTime());
 		
-		boolean effectWhileCasting = (skill.getSkillType() == L2SkillType.FUSION) || (skill.getSkillType() == L2SkillType.SIGNET_CASTTIME);
-		// Don't modify the skill time for FORCE_BUFF skills. The skill time for those skills represent the buff time.
-		if (!effectWhileCasting)
+		if (!skill.isChanneling())
 		{
 			// Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd).
 			if (!skill.isStatic())
@@ -1860,60 +1863,30 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 			setHeading(Util.calculateHeadingFrom(this, target));
 		}
 		
-		// For force buff skills, start the effect as long as the player is casting.
-		if (effectWhileCasting)
+		if (isPlayable())
 		{
-			// Consume Items if necessary and Send the Server->Client packet InventoryUpdate with Item modification to all the L2Character
 			if (skill.getItemConsumeId() > 0)
 			{
 				if (!destroyItemByItemId("Consume", skill.getItemConsumeId(), skill.getItemConsume(), null, true))
 				{
-					sendPacket(SystemMessageId.NOT_ENOUGH_ITEMS);
-					if (simultaneously)
-					{
-						setIsCastingSimultaneouslyNow(false);
-					}
-					else
-					{
-						setIsCastingNow(false);
-					}
-					
-					if (isPlayer())
-					{
-						getAI().setIntention(AI_INTENTION_ACTIVE);
-					}
+					getActingPlayer().sendPacket(SystemMessageId.NOT_ENOUGH_ITEMS);
+					abortCast();
 					return;
 				}
 			}
 			
-			// Consume Souls if necessary
-			if (skill.getMaxSoulConsumeCount() > 0)
+			// reduce talisman mana on skill use
+			if ((skill.getReferenceItemId() > 0) && (ItemTable.getInstance().getTemplate(skill.getReferenceItemId()).getBodyPart() == L2Item.SLOT_DECO))
 			{
-				if (isPlayer())
+				for (L2ItemInstance item : getInventory().getItemsByItemId(skill.getReferenceItemId()))
 				{
-					if (!getActingPlayer().decreaseSouls(skill.getMaxSoulConsumeCount(), skill))
+					if (item.isEquipped())
 					{
-						if (simultaneously)
-						{
-							setIsCastingSimultaneouslyNow(false);
-						}
-						else
-						{
-							setIsCastingNow(false);
-						}
-						return;
+						item.decreaseMana(false, item.useSkillDisTime());
+						break;
 					}
 				}
 			}
-			
-			if (skill.getSkillType() == L2SkillType.FUSION)
-			{
-				startFusionSkill(target, skill);
-			}
-			else
-			{
-				callSkill(skill, targets);
-			}
 		}
 		
 		// Send a Server->Client packet MagicSkillUser with target, displayId, level, skillTime, reuseDelay
@@ -1946,32 +1919,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 			sendPacket(sm);
 		}
 		
-		if (isPlayable())
-		{
-			if (!effectWhileCasting && (skill.getItemConsumeId() > 0))
-			{
-				if (!destroyItemByItemId("Consume", skill.getItemConsumeId(), skill.getItemConsume(), null, true))
-				{
-					getActingPlayer().sendPacket(SystemMessageId.NOT_ENOUGH_ITEMS);
-					abortCast();
-					return;
-				}
-			}
-			
-			// reduce talisman mana on skill use
-			if ((skill.getReferenceItemId() > 0) && (ItemTable.getInstance().getTemplate(skill.getReferenceItemId()).getBodyPart() == L2Item.SLOT_DECO))
-			{
-				for (L2ItemInstance item : getInventory().getItemsByItemId(skill.getReferenceItemId()))
-				{
-					if (item.isEquipped())
-					{
-						item.decreaseMana(false, item.useSkillDisTime());
-						break;
-					}
-				}
-			}
-		}
-		
 		// Before start AI Cast Broadcast Fly Effect is Need
 		if (skill.getFlyType() != null)
 		{
@@ -1984,19 +1931,14 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		if (skillTime > 0)
 		{
 			// Send a Server->Client packet SetupGauge with the color of the gauge and the casting time
-			if (isPlayer() && !effectWhileCasting)
+			if (isPlayer())
 			{
 				sendPacket(new SetupGauge(SetupGauge.BLUE, skillTime));
 			}
 			
-			if (skill.getHitCounts() > 0)
-			{
-				skillTime = (skillTime * skill.getHitTimings()[0]) / 100;
-			}
-			
-			if (effectWhileCasting)
+			if (skill.isChanneling())
 			{
-				mut.setPhase(2);
+				getSkillChannelizer().startChanneling(skill);
 			}
 			
 			if (simultaneously)
@@ -2095,7 +2037,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		}
 		
 		// prevent casting signets to peace zone
-		if ((skill.getSkillType() == L2SkillType.SIGNET) || (skill.getSkillType() == L2SkillType.SIGNET_CASTTIME))
+		if (skill.isChanneling())
 		{
 			L2WorldRegion region = getWorldRegion();
 			if (region == null)
@@ -2216,19 +2158,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		return -1;
 	}
 	
-	public void startFusionSkill(L2Character target, L2Skill skill)
-	{
-		if (skill.getSkillType() != L2SkillType.FUSION)
-		{
-			return;
-		}
-		
-		if (_fusionSkill == null)
-		{
-			_fusionSkill = new FusionSkill(this, target, skill);
-		}
-	}
-	
 	/**
 	 * Kill the L2Character.<br>
 	 * <B><U>Actions</U>:</B>
@@ -2324,9 +2253,9 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		// a resurrection popup will show up
 		if (isSummon())
 		{
-			if (((L2Summon) this).isPhoenixBlessed() && (((L2Summon) this).getOwner() != null))
+			if (getSummon().isPhoenixBlessed() && (getActingPlayer() != null))
 			{
-				((L2Summon) this).getOwner().reviveRequest(((L2Summon) this).getOwner(), null, true, 0);
+				getActingPlayer().reviveRequest(getActingPlayer(), null, true, 0);
 			}
 		}
 		if (isPlayer())
@@ -2340,24 +2269,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 				getActingPlayer().reviveRequest(getActingPlayer(), null, false, 0);
 			}
 		}
-		try
-		{
-			if (_fusionSkill != null)
-			{
-				abortCast();
-			}
-			
-			for (L2Character character : getKnownList().getKnownCharacters())
-			{
-				if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == this))
-				{
-					character.abortCast();
-				}
-			}
-		}
-		catch (Exception e)
+		
+		if (isChannelized())
 		{
-			_log.log(Level.SEVERE, "deleteMe()", e);
+			getSkillChannelized().abortChannelization();
 		}
 		return true;
 	}
@@ -4028,15 +3943,11 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 				_skillCast2 = null;
 			}
 			
-			if (getFusionSkill() != null)
-			{
-				getFusionSkill().onCastAbort();
-			}
+			// TODO: Handle removing spawned npc.
 			
-			L2Effect mog = getFirstEffect(L2EffectType.SIGNET_GROUND);
-			if (mog != null)
+			if (isChanneling())
 			{
-				mog.exit();
+				getSkillChannelizer().stopChanneling();
 			}
 			
 			if (_allSkillsDisabled)
@@ -5897,8 +5808,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 			return;
 		}
 		
-		// Send a Server->Client packet MagicSkillLaunched to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character
-		if (!skill.isStatic())
+		if (!skill.isChanneling())
 		{
 			broadcastPacket(new MagicSkillLaunched(this, skill.getDisplayId(), skill.getDisplayLevel(), targets));
 		}
@@ -5926,40 +5836,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 			return;
 		}
 		
-		if (getFusionSkill() != null)
-		{
-			if (mut.isSimultaneous())
-			{
-				_skillCast2 = null;
-				setIsCastingSimultaneouslyNow(false);
-			}
-			else
-			{
-				_skillCast = null;
-				setIsCastingNow(false);
-			}
-			getFusionSkill().onCastAbort();
-			notifyQuestEventSkillFinished(skill, targets[0]);
-			return;
-		}
-		L2Effect mog = getFirstEffect(L2EffectType.SIGNET_GROUND);
-		if (mog != null)
-		{
-			if (mut.isSimultaneous())
-			{
-				_skillCast2 = null;
-				setIsCastingSimultaneouslyNow(false);
-			}
-			else
-			{
-				_skillCast = null;
-				setIsCastingNow(false);
-			}
-			mog.exit();
-			notifyQuestEventSkillFinished(skill, targets[0]);
-			return;
-		}
-		
 		try
 		{
 			// Go through targets table
@@ -5994,7 +5870,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 				}
 			}
 			
-			StatusUpdate su = new StatusUpdate(this);
+			final StatusUpdate su = new StatusUpdate(this);
 			boolean isSendStatus = false;
 			
 			// Consume MP of the L2Character and Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform
@@ -6085,19 +5961,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		if (mut.getSkillTime() > 0)
 		{
 			mut.setCount(mut.getCount() + 1);
-			if (mut.getCount() < skill.getHitCounts())
-			{
-				int skillTime = (mut.getSkillTime() * skill.getHitTimings()[mut.getCount()]) / 100;
-				if (mut.isSimultaneous())
-				{
-					_skillCast2 = ThreadPoolManager.getInstance().scheduleEffect(mut, skillTime);
-				}
-				else
-				{
-					_skillCast = ThreadPoolManager.getInstance().scheduleEffect(mut, skillTime);
-				}
-				return;
-			}
 		}
 		
 		mut.setPhase(3);
@@ -6185,6 +6048,11 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 				ThreadPoolManager.getInstance().executeTask(new QueuedMagicUseTask(currPlayer, queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed()));
 			}
 		}
+		
+		if (isChanneling())
+		{
+			getSkillChannelizer().stopChanneling();
+		}
 	}
 	
 	// Quest event ON_SPELL_FNISHED
@@ -6388,7 +6256,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 					}
 					
 					// Static skills not trigger any chance skills
-					if (!skill.isStatic())
+					if (!skill.isStatic() && (target.getObjectId() != getObjectId()))
 					{
 						// Launch weapon Special ability skill effect if available
 						if ((activeWeapon != null) && !target.isDead())
@@ -7046,16 +6914,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		
 	}
 	
-	public FusionSkill getFusionSkill()
-	{
-		return _fusionSkill;
-	}
-	
-	public void setFusionSkill(FusionSkill fb)
-	{
-		_fusionSkill = fb;
-	}
-	
 	public byte getAttackElement()
 	{
 		return getStat().getAttackElement();
@@ -7167,7 +7025,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 			{
 				if (_triggerSkills == null)
 				{
-					_triggerSkills = new FastMap<Integer, OptionsSkillHolder>().shared();
+					_triggerSkills = new ConcurrentHashMap<>();
 				}
 			}
 		}
@@ -7215,7 +7073,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 				}
 				
 				final L2Object[] targets = skill.getTargetList(this, false, target);
-				
 				if (targets.length == 0)
 				{
 					return;
@@ -7232,12 +7089,12 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 					}
 				}
 				
-				final ISkillHandler handler = SkillHandler.getInstance().getHandler(skill.getSkillType());
-				
-				broadcastPacket(new MagicSkillLaunched(this, skill.getDisplayId(), skill.getLevel(), targets));
 				broadcastPacket(new MagicSkillUse(this, firstTarget, skill.getDisplayId(), skill.getLevel(), 0, 0));
+				broadcastPacket(new MagicSkillLaunched(this, skill.getDisplayId(), skill.getLevel(), targets));
+				
 				// Launch the magic skill and calculate its effects
 				// TODO: once core will support all possible effects, use effects (not handler)
+				final ISkillHandler handler = SkillHandler.getInstance().getHandler(skill.getSkillType());
 				if (handler != null)
 				{
 					handler.useSkill(this, skill, targets);
@@ -7320,6 +7177,9 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		attacker.getEvents().onDamageDealt(damage, this, skill, critical);
 	}
 	
+	/**
+	 * @return {@link L2WeaponType} of current character's weapon or basic weapon type.
+	 */
 	public final L2WeaponType getAttackType()
 	{
 		final L2Weapon weapon = getActiveWeaponItem();
@@ -7343,9 +7203,59 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
 		return false;
 	}
 	
+	/**
+	 * @return the character that summoned this NPC.
+	 */
+	public L2Character getSummoner()
+	{
+		return _summoner;
+	}
+	
+	/**
+	 * @param summoner the summoner of this NPC.
+	 */
+	public void setSummoner(L2Character summoner)
+	{
+		_summoner = summoner;
+	}
+	
 	@Override
 	public boolean isCharacter()
 	{
 		return true;
 	}
+	
+	/**
+	 * @return {@code true} if current character is casting channeling skill, {@code false} otherwise.
+	 */
+	public final boolean isChanneling()
+	{
+		return (_channelizer != null) && _channelizer.isChanneling();
+	}
+	
+	public final SkillChannelizer getSkillChannelizer()
+	{
+		if (_channelizer == null)
+		{
+			_channelizer = new SkillChannelizer(this);
+		}
+		return _channelizer;
+	}
+	
+	/**
+	 * @return {@code true} if current character is affected by channeling skill, {@code false} otherwise.
+	 */
+	public final boolean isChannelized()
+	{
+		return (_channelized != null) && !_channelized.isChannelized();
+	}
+	
+	public final SkillChannelized getSkillChannelized()
+	{
+		if (_channelized == null)
+		{
+			_channelized = new SkillChannelized();
+		}
+		return _channelized;
+	}
 }

+ 4 - 35
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/L2Npc.java

@@ -129,8 +129,6 @@ public class L2Npc extends L2Character
 	private int _spiritshotamount = 0;
 	private int _displayEffect = 0;
 	private int _scriptVal = 0;
-	/** The character that summons this NPC. */
-	private L2Character _summoner = null;
 	
 	private final L2NpcAIData _staticAIData = getTemplate().getAIDataStatic();
 	
@@ -1519,25 +1517,12 @@ public class L2Npc extends L2Character
 		{
 			_log.log(Level.SEVERE, "Failed decayMe().", e);
 		}
-		try
-		{
-			if (_fusionSkill != null)
-			{
-				abortCast();
-			}
-			
-			for (L2Character character : getKnownList().getKnownCharacters())
-			{
-				if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == this))
-				{
-					character.abortCast();
-				}
-			}
-		}
-		catch (Exception e)
+		
+		if (isChannelized())
 		{
-			_log.log(Level.SEVERE, "deleteMe()", e);
+			getSkillChannelized().abortChannelization();
 		}
+		
 		if (oldRegion != null)
 		{
 			oldRegion.removeFromZones(this);
@@ -1778,22 +1763,6 @@ public class L2Npc extends L2Character
 		return 0;
 	}
 	
-	/**
-	 * @return the character that summoned this NPC.
-	 */
-	public L2Character getSummoner()
-	{
-		return _summoner;
-	}
-	
-	/**
-	 * @param summoner the summoner of this NPC.
-	 */
-	public void setSummoner(L2Character summoner)
-	{
-		_summoner = summoner;
-	}
-	
 	@Override
 	public boolean isNpc()
 	{

+ 6 - 57
L2J_Server_BETA/java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java

@@ -5590,17 +5590,9 @@ public final class L2PcInstance extends L2Playable
 			_cubics.clear();
 		}
 		
-		if (_fusionSkill != null)
+		if (isChannelized())
 		{
-			abortCast();
-		}
-		
-		for (L2Character character : getKnownList().getKnownCharacters())
-		{
-			if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == this))
-			{
-				character.abortCast();
-			}
+			getSkillChannelized().abortChannelization();
 		}
 		
 		if (isInParty() && getParty().isInDimensionalRift())
@@ -10521,13 +10513,9 @@ public final class L2PcInstance extends L2Playable
 			// abort any kind of cast.
 			abortCast();
 			
-			// Stop casting for any player that may be casting a force buff on this l2pcinstance.
-			for (L2Character character : getKnownList().getKnownCharacters())
+			if (isChannelized())
 			{
-				if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == this))
-				{
-					character.abortCast();
-				}
+				getSkillChannelized().abortChannelization();
 			}
 			
 			// 1. Call store() before modifying _classIndex to avoid skill effects rollover.
@@ -11600,48 +11588,9 @@ public final class L2PcInstance extends L2Playable
 			_log.log(Level.SEVERE, "deleteMe()", e);
 		}
 		
-		try
-		{
-			if (_fusionSkill != null)
-			{
-				abortCast();
-			}
-			
-			for (L2Character character : getKnownList().getKnownCharacters())
-			{
-				if ((character.getFusionSkill() != null) && (character.getFusionSkill().getTarget() == this))
-				{
-					character.abortCast();
-				}
-			}
-		}
-		catch (Exception e)
+		if (isChannelized())
 		{
-			_log.log(Level.SEVERE, "deleteMe()", e);
-		}
-		
-		try
-		{
-			for (L2Effect effect : getAllEffects())
-			{
-				if (effect.getSkill().isToggle())
-				{
-					effect.exit();
-					continue;
-				}
-				
-				switch (effect.getEffectType())
-				{
-					case SIGNET_GROUND:
-					case SIGNET_EFFECT:
-						effect.exit();
-						break;
-				}
-			}
-		}
-		catch (Exception e)
-		{
-			_log.log(Level.SEVERE, "deleteMe()", e);
+			getSkillChannelized().abortChannelization();
 		}
 		
 		// Remove from world regions zones

+ 0 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/model/effects/L2EffectType.java

@@ -48,7 +48,6 @@ public enum L2EffectType
 	FAKE_DEATH,
 	FATAL_BLOW,
 	FEAR,
-	FUSION,
 	HATE,
 	HEAL,
 	HEAL_OVER_TIME,
@@ -81,8 +80,6 @@ public enum L2EffectType
 	RELAXING,
 	RESURRECTION,
 	ROOT,
-	SIGNET_EFFECT,
-	SIGNET_GROUND,
 	SLEEP,
 	STATIC_DAMAGE,
 	STUN,

+ 85 - 49
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2Skill.java

@@ -101,6 +101,7 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	private final int _mpConsume;
 	/** Initial MP consumption. */
 	private final int _mpInitialConsume;
+	private final int _mpPerChanneling;
 	/** HP consumption. */
 	private final int _hpConsume;
 	/** Amount of items consumed by this skill from caster. */
@@ -127,7 +128,6 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	private final int _refId;
 	// all times in milliseconds
 	private final int _hitTime;
-	private final int[] _hitTimings;
 	// private final int _skillInterruptTime;
 	private final int _coolTime;
 	private final int _reuseHashCode;
@@ -154,9 +154,6 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	private final int[] _affectLimit = new int[2];
 	
 	private final L2SkillType _skillType;
-	private final int _effectId;
-	private final int _effectLvl; // normal effect level
-	
 	private final boolean _nextActionIsAttack;
 	
 	private final boolean _removedOnAnyActionExceptMove;
@@ -196,6 +193,7 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	private List<EffectTemplate> _effectTemplates;
 	private List<EffectTemplate> _effectTemplatesSelf;
 	private List<EffectTemplate> _effectTemplatesPassive;
+	private List<EffectTemplate> _effectTemplatesChanneling;
 	
 	protected ChanceCondition _chanceCondition = null;
 	
@@ -225,6 +223,10 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	
 	private byte[] _effectTypes;
 	
+	private final int _channelingSkillId;
+	private final int _channelingTickInitialDelay;
+	private final int _channelingTickInterval;
+	
 	protected L2Skill(StatsSet set)
 	{
 		_isAbnormalInstant = set.getBoolean("abnormalInstant", false);
@@ -240,6 +242,7 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		_staticReuse = set.getBoolean("staticReuse", false);
 		_mpConsume = set.getInt("mpConsume", 0);
 		_mpInitialConsume = set.getInt("mpInitialConsume", 0);
+		_mpPerChanneling = set.getInt("mpPerChanneling", _mpConsume);
 		_hpConsume = set.getInt("hpConsume", 0);
 		_itemConsumeCount = set.getInt("itemConsumeCount", 0);
 		_itemConsumeId = set.getInt("itemConsumeId", 0);
@@ -271,28 +274,6 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		_stayOnSubclassChange = set.getBoolean("stayOnSubclassChange", true);
 		
 		_hitTime = set.getInt("hitTime", 0);
-		String hitTimings = set.getString("hitTimings", null);
-		if (hitTimings != null)
-		{
-			try
-			{
-				String[] valuesSplit = hitTimings.split(",");
-				_hitTimings = new int[valuesSplit.length];
-				for (int i = 0; i < valuesSplit.length; i++)
-				{
-					_hitTimings[i] = Integer.parseInt(valuesSplit[i]);
-				}
-			}
-			catch (Exception e)
-			{
-				throw new IllegalArgumentException("SkillId: " + _id + " invalid hitTimings value: " + hitTimings + ", \"percent,percent,...percent\" required");
-			}
-		}
-		else
-		{
-			_hitTimings = new int[0];
-		}
-		
 		_coolTime = set.getInt("coolTime", 0);
 		_isDebuff = set.getBoolean("isDebuff", false);
 		_feed = set.getInt("feed", 0);
@@ -339,8 +320,6 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		_maxChance = set.getInt("maxChance", Config.MAX_ABNORMAL_STATE_SUCCESS_RATE);
 		_ignoreShield = set.getBoolean("ignoreShld", false);
 		_skillType = set.getEnum("skillType", L2SkillType.class, L2SkillType.DUMMY);
-		_effectId = set.getInt("effectId", 0);
-		_effectLvl = set.getInt("effectLevel", 0);
 		
 		_nextActionIsAttack = set.getBoolean("nextActionAttack", false);
 		
@@ -400,6 +379,9 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		}
 		_npcId = set.getInt("npcId", 0);
 		_icon = set.getString("icon", "icon.skill0000");
+		_channelingSkillId = set.getInt("channelingSkillId", 0);
+		_channelingTickInterval = set.getInt("channelingTickInterval", 2) * 1000;
+		_channelingTickInitialDelay = set.getInt("channelingTickInitialDelay", _channelingTickInterval / 1000) * 1000;
 	}
 	
 	public abstract void useSkill(L2Character caster, L2Object[] targets);
@@ -562,18 +544,9 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	 * Return the additional effect Id.
 	 * @return
 	 */
-	public final int getEffectId()
-	{
-		return _effectId;
-	}
-	
-	/**
-	 * Return the additional effect level.
-	 * @return
-	 */
-	public final int getEffectLvl()
+	public final int getChannelingSkillId()
 	{
-		return _effectLvl;
+		return _channelingSkillId;
 	}
 	
 	/**
@@ -741,6 +714,14 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return _mpInitialConsume;
 	}
 	
+	/**
+	 * @return Mana consumption per channeling tick.
+	 */
+	public final int getMpPerChanneling()
+	{
+		return _mpPerChanneling;
+	}
+	
 	/**
 	 * @return the skill name
 	 */
@@ -767,16 +748,6 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return _hitTime;
 	}
 	
-	public final int getHitCounts()
-	{
-		return _hitTimings.length;
-	}
-	
-	public final int[] getHitTimings()
-	{
-		return _hitTimings;
-	}
-	
 	/**
 	 * @return the cool time
 	 */
@@ -815,6 +786,11 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return (_chanceCondition != null) && isPassive();
 	}
 	
+	public final boolean isChanneling()
+	{
+		return (_operateType != null) && _operateType.isChanneling();
+	}
+	
 	public final boolean isTriggeredSkill()
 	{
 		return _isTriggeredSkill;
@@ -1204,6 +1180,11 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return _effectTemplatesPassive;
 	}
 	
+	public List<EffectTemplate> getEffectTemplateChanneling()
+	{
+		return _effectTemplatesChanneling;
+	}
+	
 	public boolean hasSelfEffects()
 	{
 		return (_effectTemplatesSelf != null) && !_effectTemplatesSelf.isEmpty();
@@ -1214,6 +1195,11 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return (_effectTemplatesPassive != null) && !_effectTemplatesPassive.isEmpty();
 	}
 	
+	public boolean hasChannelingEffects()
+	{
+		return (_effectTemplatesChanneling != null) && !_effectTemplatesChanneling.isEmpty();
+	}
+	
 	/**
 	 * Env is used to pass parameters for secondary effects (shield and ss/bss/bsss)
 	 * @param effector
@@ -1396,6 +1382,37 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		return effects;
 	}
 	
+	public final List<L2Effect> getChannelingEffects(L2Character effector, L2Character effected)
+	{
+		if ((effector == null) || (effected == null) || !hasChannelingEffects())
+		{
+			return EMPTY_EFFECT_LIST;
+		}
+		
+		final Env env = new Env();
+		env.setCharacter(effector);
+		env.setTarget(effected);
+		env.setSkill(this);
+		
+		boolean addContinuousEffects = _operateType.isToggle() || (_operateType.isContinuous() && Formulas.calcEffectSuccess(env));
+		
+		final List<L2Effect> effects = new ArrayList<>(_effectTemplatesChanneling.size());
+		for (EffectTemplate et : _effectTemplatesChanneling)
+		{
+			final L2Effect e = et.getEffect(env);
+			if (e != null)
+			{
+				if ((e.isInstant() && e.calcSuccess()) || (!e.isInstant() && addContinuousEffects))
+				{
+					e.scheduleEffect();
+					effects.add(e);
+				}
+			}
+		}
+		effector.getEffectList().add(effects);
+		return effects;
+	}
+	
 	public final void attach(FuncTemplate f)
 	{
 		if (_funcTemplates == null)
@@ -1432,6 +1449,15 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 		_effectTemplatesPassive.add(effect);
 	}
 	
+	public final void attachChanneling(EffectTemplate effectTemplate)
+	{
+		if (_effectTemplatesChanneling == null)
+		{
+			_effectTemplatesChanneling = new ArrayList<>(1);
+		}
+		_effectTemplatesChanneling.add(effectTemplate);
+	}
+	
 	public final void attach(Condition c, boolean itemOrWeapon)
 	{
 		if (itemOrWeapon)
@@ -1665,4 +1691,14 @@ public abstract class L2Skill implements IChanceSkillTrigger, IIdentifiable
 	{
 		return _icon;
 	}
+	
+	public int getChannelingTickInterval()
+	{
+		return _channelingTickInterval;
+	}
+	
+	public int getChannelingTickInitialDelay()
+	{
+		return _channelingTickInitialDelay;
+	}
 }

+ 15 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2SkillOpType.java

@@ -140,4 +140,19 @@ public enum L2SkillOpType
 	{
 		return (this == T);
 	}
+	
+	/**
+	 * @return {@code true} if the operative skill type is channeling, {@code false} otherwise.
+	 */
+	public boolean isChanneling()
+	{
+		switch (this)
+		{
+			case CA1:
+			case CA5:
+				return true;
+			default:
+				return false;
+		}
+	}
 }

+ 0 - 7
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/L2SkillType.java

@@ -23,8 +23,6 @@ import java.lang.reflect.Constructor;
 import com.l2jserver.gameserver.model.StatsSet;
 import com.l2jserver.gameserver.model.skills.l2skills.L2SkillDefault;
 import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSiegeFlag;
-import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSignet;
-import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSignetCasttime;
 import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSummon;
 
 /**
@@ -33,9 +31,6 @@ import com.l2jserver.gameserver.model.skills.l2skills.L2SkillSummon;
  */
 public enum L2SkillType
 {
-	// Damage
-	SIGNET(L2SkillSignet.class),
-	SIGNET_CASTTIME(L2SkillSignetCasttime.class),
 	// reco
 	GIVE_RECO,
 	// Fishing
@@ -64,8 +59,6 @@ public enum L2SkillType
 	BUFF,
 	DEBUFF,
 	CONT,
-	FUSION,
-	
 	DETECT_TRAP,
 	REMOVE_TRAP,
 	

+ 87 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/SkillChannelized.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2004-2013 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server 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.
+ * 
+ * L2J Server 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.l2jserver.gameserver.model.skills;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.l2jserver.gameserver.model.actor.L2Character;
+
+/**
+ * @author UnAfraid
+ */
+public final class SkillChannelized
+{
+	private final Map<Integer, Map<Integer, L2Character>> _channelizers = new ConcurrentHashMap<>();
+	
+	public void addChannelizer(int skillId, L2Character channelizer)
+	{
+		if (!_channelizers.containsKey(skillId))
+		{
+			_channelizers.put(skillId, new ConcurrentHashMap<Integer, L2Character>());
+		}
+		_channelizers.get(skillId).put(channelizer.getObjectId(), channelizer);
+	}
+	
+	public void removeChannelizer(int skillId, L2Character channelizer)
+	{
+		if (_channelizers.containsKey(skillId))
+		{
+			_channelizers.get(skillId).remove(channelizer.getObjectId());
+		}
+	}
+	
+	public int getChannerlizersSize(int skillId)
+	{
+		if (_channelizers.containsKey(skillId))
+		{
+			return _channelizers.get(skillId).size();
+		}
+		return 0;
+	}
+	
+	public Map<Integer, L2Character> getChannelizers(int skillId)
+	{
+		return _channelizers.get(skillId);
+	}
+	
+	public void abortChannelization()
+	{
+		for (Map<Integer, L2Character> map : _channelizers.values())
+		{
+			for (L2Character channelizer : map.values())
+			{
+				channelizer.abortCast();
+			}
+		}
+		_channelizers.clear();
+	}
+	
+	public boolean isChannelized()
+	{
+		for (Map<Integer, L2Character> map : _channelizers.values())
+		{
+			if (!map.isEmpty())
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+}

+ 311 - 0
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/SkillChannelizer.java

@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2004-2013 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server 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.
+ * 
+ * L2J Server 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.l2jserver.gameserver.model.skills;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.l2jserver.gameserver.GeoData;
+import com.l2jserver.gameserver.ThreadPoolManager;
+import com.l2jserver.gameserver.datatables.SkillTable;
+import com.l2jserver.gameserver.enums.ShotType;
+import com.l2jserver.gameserver.model.actor.L2Character;
+import com.l2jserver.gameserver.model.effects.L2Effect;
+import com.l2jserver.gameserver.network.SystemMessageId;
+import com.l2jserver.gameserver.network.serverpackets.MagicSkillLaunched;
+import com.l2jserver.gameserver.util.Point3D;
+import com.l2jserver.gameserver.util.Util;
+
+/**
+ * @author UnAfraid
+ */
+public class SkillChannelizer implements Runnable
+{
+	private static final Logger _log = Logger.getLogger(SkillChannelizer.class.getName());
+	
+	private final L2Character _channelizer;
+	private L2Character _channelized;
+	
+	private L2Skill _skill;
+	private volatile ScheduledFuture<?> _task = null;
+	
+	public SkillChannelizer(L2Character channelizer)
+	{
+		_channelizer = channelizer;
+	}
+	
+	public L2Character getChannelizer()
+	{
+		return _channelizer;
+	}
+	
+	public L2Character getChannelized()
+	{
+		return _channelized;
+	}
+	
+	public boolean hasChannelized()
+	{
+		return _channelized != null;
+	}
+	
+	public void startChanneling(L2Skill skill)
+	{
+		// Verify for same status.
+		if (isChanneling())
+		{
+			_log.log(Level.WARNING, "Character: " + toString() + " is attempting to channel skill but he already does!");
+			return;
+		}
+		
+		// Start channeling.
+		_skill = skill;
+		_task = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(this, skill.getChannelingTickInitialDelay(), skill.getChannelingTickInterval());
+	}
+	
+	public void stopChanneling()
+	{
+		// Verify for same status.
+		if (!isChanneling())
+		{
+			_log.log(Level.WARNING, "Character: " + toString() + " is attempting to stop channel skill but he does not!");
+			return;
+		}
+		
+		// Cancel the task and unset it.
+		_task.cancel(false);
+		_task = null;
+		
+		// Cancel target channelization and unset it.
+		if (_channelized != null)
+		{
+			_channelized.getSkillChannelized().removeChannelizer(_skill.getChannelingSkillId(), getChannelizer());
+			_channelized = null;
+		}
+		
+		// unset skill.
+		_skill = null;
+	}
+	
+	public L2Skill getSkill()
+	{
+		return _skill;
+	}
+	
+	public boolean isChanneling()
+	{
+		return _task != null;
+	}
+	
+	@Override
+	public void run()
+	{
+		if (!isChanneling())
+		{
+			return;
+		}
+		
+		if (_skill.getMpPerChanneling() > 0)
+		{
+			// Validate mana per tick.
+			if (_channelizer.getCurrentMp() < _skill.getMpPerChanneling())
+			{
+				if (_channelizer.isPlayer())
+				{
+					_channelizer.sendPacket(SystemMessageId.SKILL_REMOVED_DUE_LACK_MP);
+				}
+				_channelizer.abortCast();
+				return;
+			}
+			
+			// Reduce mana per tick
+			_channelizer.reduceCurrentMp(_skill.getMpPerChanneling());
+		}
+		
+		// Apply channeling skills on the targets.
+		if (_skill.getChannelingSkillId() > 0)
+		{
+			final L2Skill baseSkill = SkillTable.getInstance().getInfo(_skill.getChannelingSkillId(), 1);
+			if (baseSkill == null)
+			{
+				_log.log(Level.WARNING, getClass().getSimpleName() + ": skill " + _skill + " couldn't find effect id skill: " + _skill.getChannelingSkillId() + " !");
+				_channelizer.abortCast();
+				return;
+			}
+			
+			if (_channelized == null)
+			{
+				final List<L2Character> targets = getTargetList();
+				if (targets.isEmpty())
+				{
+					_log.log(Level.WARNING, getClass().getSimpleName() + ": skill " + _skill + " couldn't find proper target!");
+					_channelizer.abortCast();
+					return;
+				}
+				
+				_channelized = targets.get(0);
+				_channelized.getSkillChannelized().addChannelizer(_skill.getChannelingSkillId(), getChannelizer());
+			}
+			
+			if (!Util.checkIfInRange(_skill.getEffectRange(), _channelizer, _channelized, true))
+			{
+				_channelizer.abortCast();
+				_channelizer.sendPacket(SystemMessageId.CANT_SEE_TARGET);
+			}
+			else if (!GeoData.getInstance().canSeeTarget(_channelizer, _channelized))
+			{
+				_channelizer.abortCast();
+				_channelizer.sendPacket(SystemMessageId.CANT_SEE_TARGET);
+			}
+			else
+			{
+				final int maxSkillLevel = SkillTable.getInstance().getMaxLevel(_skill.getChannelingSkillId());
+				final int skillLevel = Math.min(_channelized.getSkillChannelized().getChannerlizersSize(_skill.getChannelingSkillId()), maxSkillLevel);
+				
+				final L2Effect currentEffect = _channelized.getFirstEffect(_skill.getChannelingSkillId());
+				if ((currentEffect == null) || (currentEffect.getSkill().getLevel() < skillLevel))
+				{
+					final L2Skill skill = SkillTable.getInstance().getInfo(_skill.getChannelingSkillId(), skillLevel);
+					skill.getEffects(getChannelizer(), _channelized);
+				}
+				_channelizer.broadcastPacket(new MagicSkillLaunched(_channelizer, _skill.getId(), _skill.getLevel(), _channelized));
+			}
+		}
+		else
+		{
+			final List<L2Character> targets = getTargetList();
+			final Iterator<L2Character> it = targets.iterator();
+			while (it.hasNext())
+			{
+				final L2Character target = it.next();
+				if (!GeoData.getInstance().canSeeTarget(_channelizer, target))
+				{
+					it.remove();
+					continue;
+				}
+				
+				if (_channelizer.isPlayable() && target.isPlayable() && _skill.isBad())
+				{
+					// Validate pvp conditions.
+					if (_channelizer.isPlayable() && _channelizer.getActingPlayer().canAttackCharacter(target))
+					{
+						// Apply channeling skill effects on the target.
+						_skill.getChannelingEffects(_channelizer, target);
+						
+						// Update the pvp flag of the caster.
+						_channelizer.getActingPlayer().updatePvPStatus(target);
+					}
+					else
+					{
+						it.remove();
+					}
+				}
+				else
+				{
+					// Apply channeling skill effects on the target.
+					_skill.getChannelingEffects(_channelizer, target);
+				}
+			}
+			
+			// Broadcast MagicSkillLaunched on every cast.
+			_channelizer.broadcastPacket(new MagicSkillLaunched(_channelizer, _skill.getId(), _skill.getLevel(), targets.toArray(new L2Character[0])));
+			
+			// Reduce shots.
+			if (_skill.useSpiritShot())
+			{
+				_channelizer.setChargedShot(_channelizer.isChargedShot(ShotType.BLESSED_SPIRITSHOTS) ? ShotType.BLESSED_SPIRITSHOTS : ShotType.SPIRITSHOTS, false);
+			}
+			else
+			{
+				_channelizer.setChargedShot(ShotType.SOULSHOTS, false);
+			}
+			
+			// Shots are re-charged every cast.
+			_channelizer.rechargeShots(_skill.useSoulShot(), _skill.useSpiritShot());
+		}
+	}
+	
+	public List<L2Character> getTargetList()
+	{
+		// Get possible targets
+		final List<L2Character> targets = new ArrayList<>();
+		switch (_skill.getTargetType())
+		{
+			case GROUND:
+			{
+				int x = _channelizer.getX();
+				int y = _channelizer.getY();
+				int z = _channelizer.getZ();
+				
+				if (_channelizer.isPlayer())
+				{
+					final Point3D wordPosition = _channelizer.getActingPlayer().getCurrentSkillWorldPosition();
+					if (wordPosition != null)
+					{
+						x = wordPosition.getX();
+						y = wordPosition.getY();
+						z = wordPosition.getZ();
+					}
+				}
+				
+				for (L2Character cha : _channelizer.getKnownList().getKnownCharacters())
+				{
+					// Null target or caster himself is not valid target.
+					if ((cha == null) || (cha == _channelizer))
+					{
+						continue;
+					}
+					
+					// Target is too far.
+					if (Util.calculateDistance(x, y, z, cha.getX(), cha.getY(), cha.getZ(), true) > _skill.getAffectRange())
+					{
+						continue;
+					}
+					
+					// Only attackable creatures can be attacked.
+					if (cha.isL2Attackable() || cha.isPlayable())
+					{
+						if (cha.isAlikeDead())
+						{
+							continue;
+						}
+						
+						// Valid target, registering it.
+						targets.add(cha);
+					}
+				}
+				break;
+			}
+			default:
+			{
+				// Null target, not L2Character or caster himself is not valid target.
+				if ((_channelizer.getTarget() != null) && _channelizer.getTarget().isCharacter() && (_channelizer.getTarget() != _channelizer))
+				{
+					targets.add((L2Character) _channelizer.getTarget());
+				}
+				break;
+			}
+		}
+		return targets;
+	}
+}

+ 0 - 75
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/l2skills/L2SkillSignet.java

@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2004-2013 L2J Server
- * 
- * This file is part of L2J Server.
- * 
- * L2J Server 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.
- * 
- * L2J Server 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.l2jserver.gameserver.model.skills.l2skills;
-
-import com.l2jserver.gameserver.datatables.NpcTable;
-import com.l2jserver.gameserver.idfactory.IdFactory;
-import com.l2jserver.gameserver.model.L2Object;
-import com.l2jserver.gameserver.model.StatsSet;
-import com.l2jserver.gameserver.model.actor.L2Character;
-import com.l2jserver.gameserver.model.actor.instance.L2EffectPointInstance;
-import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
-import com.l2jserver.gameserver.model.skills.L2Skill;
-import com.l2jserver.gameserver.model.skills.targets.L2TargetType;
-import com.l2jserver.gameserver.util.Point3D;
-
-/**
- * @author Forsaiken
- */
-public final class L2SkillSignet extends L2Skill
-{
-	public L2SkillSignet(StatsSet set)
-	{
-		super(set);
-	}
-	
-	@Override
-	public void useSkill(L2Character caster, L2Object[] targets)
-	{
-		if (caster.isAlikeDead())
-		{
-			return;
-		}
-		
-		L2NpcTemplate template = NpcTable.getInstance().getTemplate(getNpcId());
-		L2EffectPointInstance effectPoint = new L2EffectPointInstance(IdFactory.getInstance().getNextId(), template, caster);
-		effectPoint.setCurrentHp(effectPoint.getMaxHp());
-		effectPoint.setCurrentMp(effectPoint.getMaxMp());
-		
-		int x = caster.getX();
-		int y = caster.getY();
-		int z = caster.getZ();
-		
-		if (caster.isPlayer() && (getTargetType() == L2TargetType.GROUND))
-		{
-			Point3D wordPosition = caster.getActingPlayer().getCurrentSkillWorldPosition();
-			
-			if (wordPosition != null)
-			{
-				x = wordPosition.getX();
-				y = wordPosition.getY();
-				z = wordPosition.getZ();
-			}
-		}
-		getEffects(caster, effectPoint);
-		
-		effectPoint.setIsInvul(true);
-		effectPoint.spawnMe(x, y, z);
-	}
-}

+ 0 - 46
L2J_Server_BETA/java/com/l2jserver/gameserver/model/skills/l2skills/L2SkillSignetCasttime.java

@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2004-2013 L2J Server
- * 
- * This file is part of L2J Server.
- * 
- * L2J Server 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.
- * 
- * L2J Server 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.l2jserver.gameserver.model.skills.l2skills;
-
-import com.l2jserver.gameserver.model.L2Object;
-import com.l2jserver.gameserver.model.StatsSet;
-import com.l2jserver.gameserver.model.actor.L2Character;
-import com.l2jserver.gameserver.model.skills.L2Skill;
-
-/**
- * @author Forsaiken
- */
-public final class L2SkillSignetCasttime extends L2Skill
-{
-	public L2SkillSignetCasttime(StatsSet set)
-	{
-		super(set);
-	}
-	
-	@Override
-	public void useSkill(L2Character caster, L2Object[] targets)
-	{
-		if (caster.isAlikeDead())
-		{
-			return;
-		}
-		
-		getEffectsSelf(caster);
-	}
-}

+ 2 - 2
L2J_Server_BETA/java/com/l2jserver/gameserver/model/stats/Formulas.java

@@ -1099,9 +1099,9 @@ public final class Formulas
 	 */
 	public static final boolean calcAtkBreak(L2Character target, double dmg)
 	{
-		if (target.getFusionSkill() != null)
+		if (target.isChanneling())
 		{
-			return true;
+			return false;
 		}
 		
 		double init = 0;

+ 3 - 3
L2J_Server_BETA/java/com/l2jserver/gameserver/network/clientpackets/RequestExMagicSkillUseGround.java

@@ -23,6 +23,7 @@ import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
 import com.l2jserver.gameserver.model.skills.L2Skill;
 import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
 import com.l2jserver.gameserver.network.serverpackets.ValidateLocation;
+import com.l2jserver.gameserver.util.Broadcast;
 import com.l2jserver.gameserver.util.Point3D;
 import com.l2jserver.gameserver.util.Util;
 
@@ -56,8 +57,7 @@ public final class RequestExMagicSkillUseGround extends L2GameClientPacket
 	protected void runImpl()
 	{
 		// Get the current L2PcInstance of the player
-		L2PcInstance activeChar = getClient().getActiveChar();
-		
+		final L2PcInstance activeChar = getClient().getActiveChar();
 		if (activeChar == null)
 		{
 			return;
@@ -81,7 +81,7 @@ public final class RequestExMagicSkillUseGround extends L2GameClientPacket
 			
 			// normally magicskilluse packet turns char client side but for these skills, it doesn't (even with correct target)
 			activeChar.setHeading(Util.calculateHeadingFrom(activeChar.getX(), activeChar.getY(), _x, _y));
-			activeChar.broadcastPacket(new ValidateLocation(activeChar));
+			Broadcast.toKnownPlayers(activeChar, new ValidateLocation(activeChar));
 			
 			activeChar.useMagic(skill, _ctrlPressed, _shiftPressed);
 		}

+ 2 - 2
L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/L2GameServerPacket.java

@@ -24,7 +24,7 @@ import java.util.logging.Logger;
 import org.mmocore.network.SendablePacket;
 
 import com.l2jserver.Config;
-import com.l2jserver.gameserver.model.Location;
+import com.l2jserver.gameserver.model.interfaces.IPositionable;
 import com.l2jserver.gameserver.model.itemcontainer.Inventory;
 import com.l2jserver.gameserver.network.L2GameClient;
 
@@ -89,7 +89,7 @@ public abstract class L2GameServerPacket extends SendablePacket<L2GameClient>
 	 * Writes 3 D (int32) with current location x, y, z
 	 * @param loc
 	 */
-	protected void writeLoc(Location loc)
+	protected void writeLoc(IPositionable loc)
 	{
 		writeD(loc.getX());
 		writeD(loc.getY());

+ 17 - 32
L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/MagicSkillLaunched.java

@@ -18,48 +18,40 @@
  */
 package com.l2jserver.gameserver.network.serverpackets;
 
+import java.util.Arrays;
+import java.util.List;
+
 import com.l2jserver.gameserver.model.L2Object;
 import com.l2jserver.gameserver.model.actor.L2Character;
 
+/**
+ * @author Unknown, UnAfraid
+ */
 public class MagicSkillLaunched extends L2GameServerPacket
 {
 	private final int _charObjId;
 	private final int _skillId;
 	private final int _skillLevel;
-	private int _numberOfTargets;
-	private L2Object[] _targets;
-	private final int _singleTargetId;
+	private final List<L2Object> _targets;
 	
-	public MagicSkillLaunched(L2Character cha, int skillId, int skillLevel, L2Object[] targets)
+	public MagicSkillLaunched(L2Character cha, int skillId, int skillLevel, L2Object... targets)
 	{
 		_charObjId = cha.getObjectId();
 		_skillId = skillId;
 		_skillLevel = skillLevel;
 		
-		if (targets != null)
+		//@formatter:off
+		if (targets == null)
 		{
-			_numberOfTargets = targets.length;
-			_targets = targets;
+			targets = new L2Object[] { cha };
 		}
-		else
-		{
-			_numberOfTargets = 1;
-			L2Object[] objs =
-			{
-				cha
-			};
-			_targets = objs;
-		}
-		_singleTargetId = 0;
+		//@formatter:on
+		_targets = Arrays.asList(targets);
 	}
 	
 	public MagicSkillLaunched(L2Character cha, int skillId, int skillLevel)
 	{
-		_charObjId = cha.getObjectId();
-		_skillId = skillId;
-		_skillLevel = skillLevel;
-		_numberOfTargets = 1;
-		_singleTargetId = cha.getTargetId();
+		this(cha, skillId, skillId, cha);
 	}
 	
 	@Override
@@ -69,17 +61,10 @@ public class MagicSkillLaunched extends L2GameServerPacket
 		writeD(_charObjId);
 		writeD(_skillId);
 		writeD(_skillLevel);
-		writeD(_numberOfTargets); // also failed or not?
-		if ((_singleTargetId != 0) || (_numberOfTargets == 0))
-		{
-			writeD(_singleTargetId);
-		}
-		else
+		writeD(_targets.size());
+		for (L2Object target : _targets)
 		{
-			for (L2Object target : _targets)
-			{
-				writeD(target.getObjectId());
-			}
+			writeD(target.getObjectId());
 		}
 	}
 }

+ 41 - 35
L2J_Server_BETA/java/com/l2jserver/gameserver/network/serverpackets/MagicSkillUse.java

@@ -18,69 +18,75 @@
  */
 package com.l2jserver.gameserver.network.serverpackets;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 import com.l2jserver.gameserver.model.actor.L2Character;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.model.interfaces.IPositionable;
+import com.l2jserver.gameserver.util.Point3D;
 
+/**
+ * @author Unknown, UnAfraid, Nos
+ */
 public final class MagicSkillUse extends L2GameServerPacket
 {
-	private final int _targetId, _tx, _ty, _tz;
 	private final int _skillId;
 	private final int _skillLevel;
 	private final int _hitTime;
 	private final int _reuseDelay;
-	private final int _charObjId, _x, _y, _z;
-	
-	// private int _flags;
+	private final L2Character _activeChar;
+	private final L2Character _target;
+	private final List<Integer> _unknown = Collections.emptyList();
+	private final List<Point3D> _groundLocations;
 	
 	public MagicSkillUse(L2Character cha, L2Character target, int skillId, int skillLevel, int hitTime, int reuseDelay)
 	{
-		_charObjId = cha.getObjectId();
-		_targetId = target.getObjectId();
+		_activeChar = cha;
+		_target = target;
 		_skillId = skillId;
 		_skillLevel = skillLevel;
 		_hitTime = hitTime;
 		_reuseDelay = reuseDelay;
-		_x = cha.getX();
-		_y = cha.getY();
-		_z = cha.getZ();
-		_tx = target.getX();
-		_ty = target.getY();
-		_tz = target.getZ();
-		// _flags |= 0x20;
+		Point3D skillWorldPos = null;
+		if (cha.isPlayer())
+		{
+			final L2PcInstance player = cha.getActingPlayer();
+			if (player.getCurrentSkillWorldPosition() != null)
+			{
+				skillWorldPos = player.getCurrentSkillWorldPosition();
+			}
+		}
+		_groundLocations = skillWorldPos != null ? Arrays.asList(skillWorldPos) : Collections.<Point3D> emptyList();
 	}
 	
 	public MagicSkillUse(L2Character cha, int skillId, int skillLevel, int hitTime, int reuseDelay)
 	{
-		_charObjId = cha.getObjectId();
-		_targetId = cha.getTargetId();
-		_skillId = skillId;
-		_skillLevel = skillLevel;
-		_hitTime = hitTime;
-		_reuseDelay = reuseDelay;
-		_x = cha.getX();
-		_y = cha.getY();
-		_z = cha.getZ();
-		_tx = cha.getX();
-		_ty = cha.getY();
-		_tz = cha.getZ();
-		// _flags |= 0x20;
+		this(cha, cha, skillId, skillLevel, hitTime, reuseDelay);
 	}
 	
 	@Override
 	protected final void writeImpl()
 	{
 		writeC(0x48);
-		writeD(_charObjId);
-		writeD(_targetId);
+		writeD(_activeChar.getObjectId());
+		writeD(_target.getObjectId());
 		writeD(_skillId);
 		writeD(_skillLevel);
 		writeD(_hitTime);
 		writeD(_reuseDelay);
-		writeD(_x);
-		writeD(_y);
-		writeD(_z);
-		writeD(0x00); // unknown
-		writeD(_tx);
-		writeD(_ty);
-		writeD(_tz);
+		writeLoc(_activeChar);
+		writeH(_unknown.size()); // TODO: Implement me!
+		for (int unknown : _unknown)
+		{
+			writeH(unknown);
+		}
+		writeH(_groundLocations.size());
+		for (IPositionable target : _groundLocations)
+		{
+			writeLoc(target);
+		}
+		writeLoc(_target);
 	}
 }