/* * 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 . */ package net.sf.l2j.gameserver.model; import java.util.Collection; import net.sf.l2j.Config; import net.sf.l2j.gameserver.GeoData; import net.sf.l2j.gameserver.Olympiad; import net.sf.l2j.gameserver.ai.CtrlIntention; import net.sf.l2j.gameserver.ai.L2CharacterAI; import net.sf.l2j.gameserver.ai.L2SummonAI; import net.sf.l2j.gameserver.datatables.SkillTable; import net.sf.l2j.gameserver.model.L2Attackable.AggroInfo; import net.sf.l2j.gameserver.model.L2Skill.SkillTargetType; import net.sf.l2j.gameserver.model.actor.instance.L2DoorInstance; import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance; import net.sf.l2j.gameserver.model.actor.instance.L2PetInstance; import net.sf.l2j.gameserver.model.actor.instance.L2PlayableInstance; import net.sf.l2j.gameserver.model.actor.instance.L2SummonInstance; import net.sf.l2j.gameserver.model.actor.knownlist.SummonKnownList; import net.sf.l2j.gameserver.model.actor.stat.SummonStat; import net.sf.l2j.gameserver.model.actor.status.SummonStatus; import net.sf.l2j.gameserver.model.base.Experience; import net.sf.l2j.gameserver.network.SystemMessageId; import net.sf.l2j.gameserver.network.serverpackets.ActionFailed; import net.sf.l2j.gameserver.network.serverpackets.ExPartyPetWindowAdd; import net.sf.l2j.gameserver.network.serverpackets.ExPartyPetWindowDelete; import net.sf.l2j.gameserver.network.serverpackets.ExPartyPetWindowUpdate; import net.sf.l2j.gameserver.network.serverpackets.MyTargetSelected; import net.sf.l2j.gameserver.network.serverpackets.NpcInfo; import net.sf.l2j.gameserver.network.serverpackets.PartySpelled; import net.sf.l2j.gameserver.network.serverpackets.PetDelete; import net.sf.l2j.gameserver.network.serverpackets.PetInfo; import net.sf.l2j.gameserver.network.serverpackets.PetStatusShow; import net.sf.l2j.gameserver.network.serverpackets.PetStatusUpdate; import net.sf.l2j.gameserver.network.serverpackets.RelationChanged; import net.sf.l2j.gameserver.network.serverpackets.StatusUpdate; import net.sf.l2j.gameserver.network.serverpackets.SystemMessage; import net.sf.l2j.gameserver.taskmanager.DecayTaskManager; import net.sf.l2j.gameserver.templates.L2NpcTemplate; import net.sf.l2j.gameserver.templates.L2Weapon; public abstract class L2Summon extends L2PlayableInstance { //private static Logger _log = Logger.getLogger(L2Summon.class.getName()); protected int _pkKills; private byte _pvpFlag; private L2PcInstance _owner; private int _karma = 0; private int _attackRange = 36; //Melee range private boolean _follow = true; private boolean _previousFollowStatus = true; private int _maxLoad; private int _chargedSoulShot; private int _chargedSpiritShot; // TODO: currently, all servitors use 1 shot. However, this value // should vary depending on the servitor template (id and level)! private int _soulShotsPerHit = 1; private int _spiritShotsPerHit = 1; public class AIAccessor extends L2Character.AIAccessor { protected AIAccessor() {} public L2Summon getSummon() { return L2Summon.this; } public boolean isAutoFollow() { return L2Summon.this.getFollowStatus(); } public void doPickupItem(L2Object object) { L2Summon.this.doPickupItem(object); } } public L2Summon(int objectId, L2NpcTemplate template, L2PcInstance owner) { super(objectId, template); getKnownList(); // init knownlist getStat(); // init stats getStatus(); // init status _showSummonAnimation = true; _owner = owner; _ai = new L2SummonAI(new L2Summon.AIAccessor()); setXYZInvisible(owner.getX()+50, owner.getY()+100, owner.getZ()+100); } @Override public void onSpawn() { super.onSpawn(); this.setFollowStatus(true); setShowSummonAnimation(false); // addVisibleObject created the info packets with summon animation // if someone comes into range now, the animation shouldnt show any more this.getOwner().sendPacket(new PetInfo(this)); getOwner().sendPacket(new RelationChanged(this, getOwner().getRelation(getOwner()), false)); for (L2PcInstance player : getOwner().getKnownList().getKnownPlayersInRadius(800)) player.sendPacket(new RelationChanged(this, getOwner().getRelation(player), isAutoAttackable(player))); L2Party party = this.getOwner().getParty(); if (party != null) { party.broadcastToPartyMembers(this.getOwner(), new ExPartyPetWindowAdd(this)); } } @Override public final SummonKnownList getKnownList() { if(!(super.getKnownList() instanceof SummonKnownList)) setKnownList(new SummonKnownList(this)); return (SummonKnownList)super.getKnownList(); } @Override public SummonStat getStat() { if(!(super.getStat() instanceof SummonStat)) setStat(new SummonStat(this)); return (SummonStat)super.getStat(); } @Override public SummonStatus getStatus() { if(!(super.getStatus() instanceof SummonStatus)) setStatus(new SummonStatus(this)); return (SummonStatus)super.getStatus(); } @Override public L2CharacterAI getAI() { if (_ai == null) { synchronized(this) { if (_ai == null) _ai = new L2SummonAI(new L2Summon.AIAccessor()); } } return _ai; } @Override public L2NpcTemplate getTemplate() { return (L2NpcTemplate)super.getTemplate(); } // this defines the action buttons, 1 for Summon, 2 for Pets public abstract int getSummonType(); @Override public void updateAbnormalEffect() { Collection plrs = getKnownList().getKnownPlayers().values(); //synchronized (getKnownList().getKnownPlayers()) { for (L2PcInstance player : plrs) player.sendPacket(new NpcInfo(this, player)); } } /** * @return Returns the mountable. */ public boolean isMountable() { return false; } public boolean isMountableOverTime() { return false; } @Override public void onAction(L2PcInstance player) { if (player == _owner && player.getTarget() == this) { player.sendPacket(new PetStatusShow(this)); player.sendPacket(ActionFailed.STATIC_PACKET); } else if (player.getTarget() != this) { if (Config.DEBUG) _log.fine("new target selected:"+getObjectId()); player.setTarget(this); MyTargetSelected my = new MyTargetSelected(getObjectId(), player.getLevel() - getLevel()); player.sendPacket(my); //sends HP/MP status of the summon to other characters StatusUpdate su = new StatusUpdate(getObjectId()); su.addAttribute(StatusUpdate.CUR_HP, (int) getCurrentHp()); su.addAttribute(StatusUpdate.MAX_HP, getMaxHp()); player.sendPacket(su); } else if (player.getTarget() == this) { if (isAutoAttackable(player)) { if (Config.GEODATA > 0) { if (GeoData.getInstance().canSeeTarget(player, this)) { player.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, this); player.onActionRequest(); } } else { player.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, this); player.onActionRequest(); } } else { // This Action Failed packet avoids player getting stuck when clicking three or more times player.sendPacket(ActionFailed.STATIC_PACKET); if (Config.GEODATA > 0) { if (GeoData.getInstance().canSeeTarget(player, this)) player.getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, this); } else player.getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, this); } } } public long getExpForThisLevel() { if(getLevel() >= Experience.LEVEL.length) { return 0; } return Experience.LEVEL[getLevel()]; } public long getExpForNextLevel() { if(getLevel() >= Experience.LEVEL.length - 1) { return 0; } return Experience.LEVEL[getLevel()+1]; } public final int getKarma() { return _karma; } public void setKarma(int karma) { _karma = karma; } public final L2PcInstance getOwner() { return _owner; } public final int getNpcId() { return getTemplate().npcId; } public void setPvpFlag(byte pvpFlag) { _pvpFlag = pvpFlag; } public byte getPvpFlag() { return _pvpFlag; } public void setPkKills(int pkKills) { _pkKills = pkKills; } public final int getPkKills() { return _pkKills; } public final int getMaxLoad() { return _maxLoad; } public final int getSoulShotsPerHit() { return _soulShotsPerHit; } public final int getSpiritShotsPerHit() { return _spiritShotsPerHit; } public void setMaxLoad(int maxLoad) { _maxLoad = maxLoad; } public void setChargedSoulShot(int shotType) { _chargedSoulShot = shotType; } public void setChargedSpiritShot(int shotType) { _chargedSpiritShot = shotType; } public void followOwner() { setFollowStatus(true); } @Override public boolean doDie(L2Character killer) { if (!super.doDie(killer)) return false; L2PcInstance owner = getOwner(); if (owner != null) { Collection KnownTarget = this.getKnownList().getKnownCharacters(); for (L2Character TgMob : KnownTarget) { // get the mobs which have aggro on the this instance if (TgMob instanceof L2Attackable) { if (((L2Attackable) TgMob).isDead()) continue; AggroInfo info = ((L2Attackable) TgMob).getAggroListRP().get(this); if (info != null) ((L2Attackable) TgMob).addDamageHate(owner, info._damage, info._hate); } } } DecayTaskManager.getInstance().addDecayTask(this); return true; } public boolean doDie(L2Character killer, boolean decayed) { if (!super.doDie(killer)) return false; if (!decayed) { DecayTaskManager.getInstance().addDecayTask(this); } return true; } public void stopDecay() { DecayTaskManager.getInstance().cancelDecayTask(this); } @Override public void onDecay() { deleteMe(_owner); } @Override public void broadcastStatusUpdate() { super.broadcastStatusUpdate(); if (isVisible()) { getOwner().sendPacket(new PetStatusUpdate(this)); L2Party party = this.getOwner().getParty(); if (party != null) { party.broadcastToPartyMembers(this.getOwner(), new ExPartyPetWindowUpdate(this)); } } } @Override public void updateEffectIcons(boolean partyOnly) { PartySpelled ps = new PartySpelled(this); // Go through all effects if any L2Effect[] effects = getAllEffects(); if (effects != null && effects.length > 0) { for (int i = 0; i < effects.length; i++) { L2Effect effect = effects[i]; if (effect == null) continue; if (effect.getInUse()) { effect.addPartySpelledIcon(ps); } } } L2Party party = this.getOwner().getParty(); if (party != null) { // tell everyone about the summon effect party.broadcastToPartyMembers(ps); } else { // tell only the owner this.getOwner().sendPacket(ps); } } public void deleteMe(L2PcInstance owner) { getAI().stopFollow(); owner.sendPacket(new PetDelete(getObjectId(), 2)); //FIXME: I think it should really drop items to ground and only owner can take for a while giveAllToOwner(); decayMe(); getKnownList().removeAllKnownObjects(); owner.setPet(null); } public void onSummon() { } public void unSummon(L2PcInstance owner) { if (isVisible() && !isDead()) { getAI().stopFollow(); owner.sendPacket(new PetDelete(getObjectId(), 2)); L2Party party; if ((party = owner.getParty()) != null) { party.broadcastToPartyMembers(owner, new ExPartyPetWindowDelete(this)); } store(); giveAllToOwner(); L2WorldRegion oldRegion = getWorldRegion(); decayMe(); if (oldRegion != null) oldRegion.removeFromZones(this); getKnownList().removeAllKnownObjects(); owner.setPet(null); setTarget(null); if (this instanceof L2PetInstance) ((L2PetInstance)this).setIsMountableOverTime(false); } } public int getAttackRange() { return _attackRange; } public void setAttackRange(int range) { if (range < 36) range = 36; _attackRange = range; } public void setFollowStatus(boolean state) { _follow = state; if (_follow) getAI().setIntention(CtrlIntention.AI_INTENTION_FOLLOW, getOwner()); else getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE, null); } public boolean getFollowStatus() { return _follow; } @Override public boolean isAutoAttackable(L2Character attacker) { return _owner.isAutoAttackable(attacker); } public int getChargedSoulShot() { return _chargedSoulShot; } public int getChargedSpiritShot() { return _chargedSpiritShot; } public int getControlItemId() { return 0; } public L2Weapon getActiveWeapon() { return null; } @Override public PetInventory getInventory() { return null; } protected void doPickupItem(L2Object object) { return; } public void giveAllToOwner() { return; } public void store() { return; } @Override public L2ItemInstance getActiveWeaponInstance() { return null; } @Override public L2Weapon getActiveWeaponItem() { return null; } @Override public L2ItemInstance getSecondaryWeaponInstance() { return null; } @Override public L2Weapon getSecondaryWeaponItem() { return null; } /** * Return the L2Party object of its L2PcInstance owner or null.

*/ @Override public L2Party getParty() { if (_owner == null) return null; else return _owner.getParty(); } /** * Return True if the L2Character has a Party in progress.

*/ @Override public boolean isInParty() { if (_owner == null) return false; else return _owner.getParty() != null; } /** * Check if the active L2Skill can be casted.

* * Actions :

*
  • Check if the target is correct
  • *
  • Check if the target is in the skill cast range
  • *
  • Check if the summon owns enough HP and MP to cast the skill
  • *
  • Check if all skills are enabled and this skill is enabled


  • *
  • Check if the skill is active


  • *
  • Notify the AI with AI_INTENTION_CAST and target


  • * * @param skill The L2Skill to use * @param forceUse used to force ATTACK on players * @param dontMove used to prevent movement, if not in range * */ public void useMagic(L2Skill skill, boolean forceUse, boolean dontMove) { if (skill == null || isDead()) return; // Check if the skill is active if (skill.isPassive()) { // just ignore the passive skill request. why does the client send it anyway ?? return; } //************************************* Check Casting in Progress ******************************************* // If a skill is currently being used if (isCastingNow()) { return; } //************************************* Check Target ******************************************* // Get the target for the skill L2Object target = null; switch (skill.getTargetType()) { // OWNER_PET should be cast even if no target has been found case TARGET_OWNER_PET: target = getOwner(); break; // PARTY, AURA, SELF should be cast even if no target has been found case TARGET_PARTY: case TARGET_AURA: case TARGET_FRONT_AURA: case TARGET_BEHIND_AURA: case TARGET_SELF: target = this; break; default: // Get the first target of the list target = skill.getFirstOfTargetList(this); break; } // Check the validity of the target if (target == null) { if (getOwner() != null) getOwner().sendPacket(new SystemMessage(SystemMessageId.TARGET_CANT_FOUND)); return; } //************************************* Check skill availability ******************************************* // Check if this skill is enabled (ex : reuse time) if (isSkillDisabled(skill.getId()) && getOwner() != null && (!getOwner().getAccessLevel().allowPeaceAttack())) { SystemMessage sm = new SystemMessage(SystemMessageId.S1_PREPARED_FOR_REUSE); sm.addSkillName(skill); getOwner().sendPacket(sm); return; } // Check if all skills are disabled if (isAllSkillsDisabled() && getOwner() != null && (!getOwner().getAccessLevel().allowPeaceAttack())) { return; } //************************************* Check Consumables ******************************************* // Check if the summon has enough MP if (getCurrentMp() < getStat().getMpConsume(skill) + getStat().getMpInitialConsume(skill)) { // Send a System Message to the caster if (getOwner() != null) getOwner().sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_MP)); return; } // Check if the summon has enough HP if (getCurrentHp() <= skill.getHpConsume()) { // Send a System Message to the caster if (getOwner() != null) getOwner().sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_HP)); return; } //************************************* Check Summon State ******************************************* // Check if this is offensive magic skill if (skill.isOffensive()) { if (isInsidePeaceZone(this, target) && getOwner() != null && (!getOwner().getAccessLevel().allowPeaceAttack())) { // If summon or target is in a peace zone, send a system message TARGET_IN_PEACEZONE sendPacket(new SystemMessage(SystemMessageId.TARGET_IN_PEACEZONE)); return; } if (getOwner() != null && getOwner().isInOlympiadMode() && !getOwner().isOlympiadStart()){ // if L2PcInstance is in Olympia and the match isn't already start, send a Server->Client packet ActionFailed sendPacket(ActionFailed.STATIC_PACKET); return; } // Check if the target is attackable if (target instanceof L2DoorInstance) { if(!((L2DoorInstance)target).isAttackable(getOwner())) return; } else { if (!target.isAttackable() && getOwner() != null && (!getOwner().getAccessLevel().allowPeaceAttack())) { return; } // Check if a Forced ATTACK is in progress on non-attackable target if (!target.isAutoAttackable(this) && !forceUse && skill.getTargetType() != SkillTargetType.TARGET_AURA && skill.getTargetType() != SkillTargetType.TARGET_FRONT_AURA && skill.getTargetType() != SkillTargetType.TARGET_BEHIND_AURA && skill.getTargetType() != SkillTargetType.TARGET_CLAN && skill.getTargetType() != SkillTargetType.TARGET_ALLY && skill.getTargetType() != SkillTargetType.TARGET_PARTY && skill.getTargetType() != SkillTargetType.TARGET_SELF) { return; } } } // Notify the AI with AI_INTENTION_CAST and target getAI().setIntention(CtrlIntention.AI_INTENTION_CAST, skill, target); } @Override public void setIsImmobilized(boolean value) { super.setIsImmobilized(value); if (value) { _previousFollowStatus = getFollowStatus(); // if immobilized temporarly disable follow mode if (_previousFollowStatus) setFollowStatus(false); } else { // if not more immobilized restore previous follow mode setFollowStatus(_previousFollowStatus); } } public void setOwner(L2PcInstance newOwner) { _owner = newOwner; } @Override public final void sendDamageMessage(L2Character target, int damage, boolean mcrit, boolean pcrit, boolean miss) { if (miss) return; // Prevents the double spam of system messages, if the target is the owning player. if (target.getObjectId() != getOwner().getObjectId()) { if (pcrit || mcrit) if (this instanceof L2SummonInstance) getOwner().sendPacket(new SystemMessage(SystemMessageId.CRITICAL_HIT_BY_SUMMONED_MOB)); else getOwner().sendPacket(new SystemMessage(SystemMessageId.CRITICAL_HIT_BY_PET)); if (getOwner().isInOlympiadMode() && target instanceof L2PcInstance && ((L2PcInstance)target).isInOlympiadMode() && ((L2PcInstance)target).getOlympiadGameId() == getOwner().getOlympiadGameId()) { Olympiad.getInstance().notifyCompetitorDamage(getOwner().getObjectId(), damage, getOwner().getOlympiadGameId()); } SystemMessage sm; if (this instanceof L2SummonInstance) sm = new SystemMessage(SystemMessageId.SUMMON_GAVE_DAMAGE_S1); else sm = new SystemMessage(SystemMessageId.PET_HIT_FOR_S1_DAMAGE); sm.addNumber(damage); getOwner().sendPacket(sm); } } public void reduceCurrentHp(int damage, L2Character attacker) { super.reduceCurrentHp(damage, attacker); SystemMessage sm; if (this instanceof L2SummonInstance) sm = new SystemMessage(SystemMessageId.SUMMON_RECEIVED_DAMAGE_S2_BY_S1); else sm = new SystemMessage(SystemMessageId.PET_RECEIVED_S2_DAMAGE_BY_S1); sm.addCharName(attacker); sm.addNumber(damage); getOwner().sendPacket(sm); } /** * Servitors' skills automatically change their level based on the servitor's level. * Until level 70, the servitor gets 1 lv of skill per 10 levels. After that, it is 1 * skill level per 5 servitor levels. If the resulting skill level doesn't exist use * the max that does exist! * * @see net.sf.l2j.gameserver.model.L2Character#doCast(net.sf.l2j.gameserver.model.L2Skill) */ @Override public void doCast(L2Skill skill) { int petLevel = getLevel(); int skillLevel = petLevel/10; if(petLevel >= 70) skillLevel += (petLevel-65)/10; // adjust the level for servitors less than lv 10 if (skillLevel < 1) skillLevel = 1; L2Skill skillToCast = SkillTable.getInstance().getInfo(skill.getId(),skillLevel); if (skillToCast != null) super.doCast(skillToCast); else super.doCast(skill); } }