/* * 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 silentium.gameserver.model.actor; import java.util.Collection; import silentium.gameserver.ai.CharacterAI; import silentium.gameserver.ai.CtrlIntention; import silentium.gameserver.ai.SummonAI; import silentium.gameserver.configs.MainConfig; import silentium.gameserver.configs.NPCConfig; import silentium.gameserver.data.html.StaticHtmPath; import silentium.gameserver.geo.GeoData; import silentium.gameserver.model.L2ItemInstance; import silentium.gameserver.model.L2Object; import silentium.gameserver.model.L2Party; import silentium.gameserver.model.L2Skill; import silentium.gameserver.model.L2Skill.SkillTargetType; import silentium.gameserver.model.L2WorldRegion; import silentium.gameserver.model.actor.L2Attackable.AggroInfo; import silentium.gameserver.model.actor.instance.L2DoorInstance; import silentium.gameserver.model.actor.instance.L2PcInstance; import silentium.gameserver.model.actor.instance.L2PetInstance; import silentium.gameserver.model.actor.instance.L2SummonInstance; import silentium.gameserver.model.actor.knownlist.SummonKnownList; import silentium.gameserver.model.actor.stat.SummonStat; import silentium.gameserver.model.actor.status.SummonStatus; import silentium.gameserver.model.base.Experience; import silentium.gameserver.model.itemcontainer.PetInventory; import silentium.gameserver.model.olympiad.OlympiadGameManager; import silentium.gameserver.network.L2GameClient; import silentium.gameserver.network.SystemMessageId; import silentium.gameserver.network.serverpackets.AbstractNpcInfo.SummonInfo; import silentium.gameserver.network.serverpackets.ActionFailed; import silentium.gameserver.network.serverpackets.MoveToPawn; import silentium.gameserver.network.serverpackets.MyTargetSelected; import silentium.gameserver.network.serverpackets.NpcHtmlMessage; import silentium.gameserver.network.serverpackets.PetDelete; import silentium.gameserver.network.serverpackets.PetInfo; import silentium.gameserver.network.serverpackets.PetItemList; import silentium.gameserver.network.serverpackets.PetStatusShow; import silentium.gameserver.network.serverpackets.PetStatusUpdate; import silentium.gameserver.network.serverpackets.RelationChanged; import silentium.gameserver.network.serverpackets.SystemMessage; import silentium.gameserver.network.serverpackets.TeleportToLocation; import silentium.gameserver.network.serverpackets.ValidateLocation; import silentium.gameserver.tables.ItemTable; import silentium.gameserver.taskmanager.DecayTaskManager; import silentium.gameserver.templates.chars.L2NpcTemplate; import silentium.gameserver.templates.item.L2EtcItem; import silentium.gameserver.templates.item.L2Weapon; public abstract class L2Summon extends L2Playable { private L2PcInstance _owner; private boolean _follow = true; private boolean _previousFollowStatus = true; private int _chargedSoulShot; private int _chargedSpiritShot; public class AIAccessor extends L2Character.AIAccessor { protected AIAccessor() { } public L2Summon getSummon() { return L2Summon.this; } public boolean isAutoFollow() { return getFollowStatus(); } public void doPickupItem(L2Object object) { L2Summon.this.doPickupItem(object); } } public L2Summon(int objectId, L2NpcTemplate template, L2PcInstance owner) { super(objectId, template); _showSummonAnimation = true; _owner = owner; _ai = new SummonAI(new L2Summon.AIAccessor()); setXYZInvisible(owner.getX() + 50, owner.getY() + 100, owner.getZ() + 100); } @Override public void initKnownList() { setKnownList(new SummonKnownList(this)); } @Override public final SummonKnownList getKnownList() { return (SummonKnownList) super.getKnownList(); } @Override public void initCharStat() { setStat(new SummonStat(this)); } @Override public SummonStat getStat() { return (SummonStat) super.getStat(); } @Override public void initCharStatus() { setStatus(new SummonStatus(this)); } @Override public SummonStatus getStatus() { return (SummonStatus) super.getStatus(); } @Override public CharacterAI getAI() { CharacterAI ai = _ai; // copy handle if (ai == null) { synchronized (this) { if (_ai == null) _ai = new SummonAI(new L2Summon.AIAccessor()); return _ai; } } 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<L2PcInstance> plrs = getKnownList().getKnownPlayers().values(); for (L2PcInstance player : plrs) player.sendPacket(new SummonInfo(this, player, 1)); } /** * @return Returns the mountable. */ public boolean isMountable() { return false; } @Override public void onAction(L2PcInstance player) { if (!player.canTarget()) return; if (player.getTarget() != this) { player.setTarget(this); player.sendPacket(new ValidateLocation(this)); player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel() - getLevel())); } else if (player == _owner && player.getTarget() == this) { // Calculate the distance between the L2PcInstance and the L2Npc if (!canInteract(player)) { // Notify the L2PcInstance AI with AI_INTENTION_INTERACT player.getAI().setIntention(CtrlIntention.AI_INTENTION_INTERACT, this); } else { // Rotate the player to face the instance player.sendPacket(new MoveToPawn(player, this, L2Npc.INTERACTION_DISTANCE)); player.sendPacket(new PetStatusShow(this)); // Send ActionFailed to the player in order to avoid he stucks player.sendPacket(ActionFailed.STATIC_PACKET); } } else { if (isAutoAttackable(player)) { if (MainConfig.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 { // Rotate the player to face the instance player.sendPacket(new MoveToPawn(player, this, L2Npc.INTERACTION_DISTANCE)); // Send ActionFailed to the player in order to avoid he stucks player.sendPacket(ActionFailed.STATIC_PACKET); if (MainConfig.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); } } } @Override public void onActionShift(L2GameClient client) { L2PcInstance player = client.getActiveChar(); if (player == null) return; if (player.getAccessLevel().isGm()) { player.setTarget(this); player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel())); NpcHtmlMessage html = new NpcHtmlMessage(0); html.setFile(StaticHtmPath.AdminHtmPath + "petinfo.htm", player); String name = getName(); html.replace("%name%", name == null ? "N/A" : name); html.replace("%level%", Integer.toString(getLevel())); html.replace("%exp%", Long.toString(getStat().getExp())); String owner = getActingPlayer().getName(); html.replace("%owner%", " <a action=\"bypass -h admin_character_info " + owner + "\">" + owner + "</a>"); html.replace("%class%", getClass().getSimpleName()); html.replace("%ai%", hasAI() ? String.valueOf(getAI().getIntention().name()) : "NULL"); html.replace("%hp%", (int) getStatus().getCurrentHp() + "/" + getStat().getMaxHp()); html.replace("%mp%", (int) getStatus().getCurrentMp() + "/" + getStat().getMaxMp()); html.replace("%karma%", Integer.toString(getKarma())); html.replace("%undead%", isUndead() ? "yes" : "no"); if (this instanceof L2PetInstance) { int objId = getActingPlayer().getObjectId(); html.replace("%inv%", " <a action=\"bypass admin_show_pet_inv " + objId + "\">view</a>"); } else html.replace("%inv%", "none"); if (this instanceof L2PetInstance) { html.replace("%food%", ((L2PetInstance) this).getCurrentFed() + "/" + ((L2PetInstance) this).getPetLevelData().getPetMaxFeed()); html.replace("%load%", ((L2PetInstance) this).getInventory().getTotalWeight() + "/" + ((L2PetInstance) this).getMaxLoad()); } else { html.replace("%food%", "N/A"); html.replace("%load%", "N/A"); } player.sendPacket(html); } player.sendPacket(ActionFailed.STATIC_PACKET); } 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]; } @Override public final int getKarma() { return getOwner() != null ? getOwner().getKarma() : 0; } @Override public final byte getPvpFlag() { return getOwner() != null ? getOwner().getPvpFlag() : 0; } public final int getTeam() { return getOwner() != null ? getOwner().getTeam() : 0; } public final L2PcInstance getOwner() { return _owner; } public final int getNpcId() { return getTemplate().getNpcId(); } public int getMaxLoad() { return 0; } public short getSoulShotsPerHit() { if (getTemplate().getAIDataStatic().getSoulShot() > 0) return (short) getTemplate().getAIDataStatic().getSoulShot(); return 1; } public short getSpiritShotsPerHit() { if (getTemplate().getAIDataStatic().getSpiritShot() > 0) return (short) getTemplate().getAIDataStatic().getSpiritShot(); return 1; } 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<L2Character> knownTarget = getKnownList().getKnownCharacters(); for (L2Character mob : knownTarget) { // get the mobs which have aggro on the this instance if (mob instanceof L2Attackable) { if (((L2Attackable) mob).isDead()) continue; AggroInfo info = ((L2Attackable) mob).getAggroList().get(this); if (info != null) ((L2Attackable) mob).addDamageHate(owner, info.getDamage(), info.getHate()); } } } 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(); updateAndBroadcastStatus(1); } public void deleteMe(L2PcInstance owner) { owner.sendPacket(new PetDelete(getSummonType(), getObjectId())); decayMe(); getKnownList().removeAllKnownObjects(); owner.setPet(null); super.deleteMe(); } public void unSummon(L2PcInstance owner) { if (isVisible() && !isDead()) { abortCast(); abortAttack(); stopHpMpRegeneration(); getAI().stopFollow(); owner.sendPacket(new PetDelete(getSummonType(), getObjectId())); store(); owner.setPet(null); // Stop AI tasks if (hasAI()) getAI().stopAITask(); stopAllEffects(); L2WorldRegion oldRegion = getWorldRegion(); decayMe(); if (oldRegion != null) oldRegion.removeFromZones(this); getKnownList().removeAllKnownObjects(); setTarget(null); // Disable beastshots for (int itemId : owner.getAutoSoulShot()) { String handler = ((L2EtcItem) ItemTable.getInstance().getTemplate(itemId)).getHandlerName(); if (handler != null && handler.contains("Beast")) owner.disableAutoShot(itemId); } } } public int getAttackRange() { return 36; } 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) { } public void store() { } @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 True if the L2Summon is invulnerable or if the summoner is in spawn protection.<BR> * <BR> */ @Override public boolean isInvul() { return super.isInvul() || getOwner().isSpawnProtected(); } /** * Return the L2Party object of its L2PcInstance owner or null.<BR> * <BR> */ @Override public L2Party getParty() { if (_owner == null) return null; return _owner.getParty(); } /** * Return True if the L2Character has a Party in progress.<BR> * <BR> */ @Override public boolean isInParty() { if (_owner == null) return false; return _owner.getParty() != null; } /** * Check if the active L2Skill can be casted.<BR> * <BR> * <B><U> Actions</U> :</B><BR> * <BR> * <li>Check if the target is correct</li> <li>Check if the target is in the skill cast range</li> <li>Check if the summon owns enough HP and * MP to cast the skill</li> <li>Check if all skills are enabled and this skill is enabled</li><BR> * <BR> * <li>Check if the skill is active</li><BR> * <BR> * <li>Notify the AI with AI_INTENTION_CAST and target</li><BR> * <BR> * * @param skill * The L2Skill to use * @param forceUse * used to force ATTACK on players * @param dontMove * used to prevent movement, if not in range */ @Override public boolean useMagic(L2Skill skill, boolean forceUse, boolean dontMove) { if (skill == null || isDead()) return false; // Check if the skill is active and ignore the passive skill request if (skill.isPassive()) return false; // ************************************* Check Casting in Progress ******************************************* // If a skill is currently being used if (isCastingNow()) return false; // Set current pet skill getOwner().setCurrentPetSkill(skill, forceUse, dontMove); // ************************************* 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(SystemMessageId.TARGET_CANT_FOUND); return false; } // ************************************* Check skill availability ******************************************* // Check if this skill is enabled (e.g. reuse time) if (isSkillDisabled(skill)) { if (getOwner() != null) getOwner().sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_PREPARED_FOR_REUSE).addString(skill.getName())); return false; } // ************************************* 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(SystemMessageId.NOT_ENOUGH_MP); return false; } // Check if the summon has enough HP if (getCurrentHp() <= skill.getHpConsume()) { // Send a System Message to the caster if (getOwner() != null) getOwner().sendPacket(SystemMessageId.NOT_ENOUGH_HP); return false; } // ************************************* 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(SystemMessage.getSystemMessage(SystemMessageId.TARGET_IN_PEACEZONE)); return false; } 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 false; } // Check if the target is attackable if (target instanceof L2DoorInstance) { if (!((L2DoorInstance) target).isAttackable(getOwner())) return false; } else { if (!target.isAttackable() && getOwner() != null && !getOwner().getAccessLevel().allowPeaceAttack()) return false; // 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 false; } } } // Notify the AI with AI_INTENTION_CAST and target getAI().setIntention(CtrlIntention.AI_INTENTION_CAST, skill, target); return true; } @Override public void setIsImmobilized(boolean value) { super.setIsImmobilized(value); if (value) { _previousFollowStatus = getFollowStatus(); // if immobilized, disable follow mode if (_previousFollowStatus) setFollowStatus(false); } else { // if not more immobilized, restore follow mode setFollowStatus(_previousFollowStatus); } } public void setOwner(L2PcInstance newOwner) { _owner = newOwner; } @Override public void sendDamageMessage(L2Character target, int damage, boolean mcrit, boolean pcrit, boolean miss) { if (miss || getOwner() == null) 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(SystemMessageId.CRITICAL_HIT_BY_SUMMONED_MOB); else getOwner().sendPacket(SystemMessageId.CRITICAL_HIT_BY_PET); final SystemMessage sm; if (target.isInvul()) { if (target.isParalyzed()) sm = SystemMessage.getSystemMessage(SystemMessageId.OPPONENT_PETRIFIED); else sm = SystemMessage.getSystemMessage(SystemMessageId.ATTACK_WAS_BLOCKED); } else sm = SystemMessage.getSystemMessage(SystemMessageId.PET_HIT_FOR_S1_DAMAGE).addNumber(damage); getOwner().sendPacket(sm); if (getOwner().isInOlympiadMode() && target instanceof L2PcInstance && ((L2PcInstance) target).isInOlympiadMode() && ((L2PcInstance) target).getOlympiadGameId() == getOwner().getOlympiadGameId()) { OlympiadGameManager.getInstance().notifyCompetitorDamage(getOwner(), damage); } } } @Override public void reduceCurrentHp(double damage, L2Character attacker, L2Skill skill) { super.reduceCurrentHp(damage, attacker, skill); if (getOwner() != null && attacker != null) getOwner().sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SUMMON_RECEIVED_DAMAGE_S2_BY_S1).addCharName(attacker).addNumber((int) damage)); } @Override public void doCast(L2Skill skill) { final L2PcInstance actingPlayer = getActingPlayer(); if (!actingPlayer.checkPvpSkill(getTarget(), skill, true) && !actingPlayer.getAccessLevel().allowPeaceAttack()) { // Send a System Message to the L2PcInstance actingPlayer.sendPacket(SystemMessageId.TARGET_IS_INCORRECT); // Send a Server->Client packet ActionFailed to the L2PcInstance actingPlayer.sendPacket(ActionFailed.STATIC_PACKET); return; } super.doCast(skill); } @Override public boolean isInCombat() { return getOwner() != null ? getOwner().isInCombat() : false; } @Override public final boolean isAttackingNow() { return isInCombat(); } @Override public L2PcInstance getActingPlayer() { return getOwner(); } @Override public String toString() { return super.toString() + "(" + getNpcId() + ") Owner: " + getOwner(); } @Override public boolean isUndead() { return getTemplate().isUndead(); } public int getWeapon() { return 0; } public int getArmor() { return 0; } @Override public void onTeleported() { super.onTeleported(); getOwner().sendPacket(new TeleportToLocation(this, getPosition().getX(), getPosition().getY(), getPosition().getZ())); } public void updateAndBroadcastStatusAndInfos(int val) { getOwner().sendPacket(new PetInfo(this, val)); // The PetInfo packet wipes the PartySpelled (list of active spells' icons). Re-add them updateEffectIcons(true); updateAndBroadcastStatus(val); } public void sendPetInfosToOwner() { getOwner().sendPacket(new PetInfo(this, 2)); // The PetInfo packet wipes the PartySpelled (list of active spells' icons). Re-add them updateEffectIcons(true); } public void updateAndBroadcastStatus(int val) { getOwner().sendPacket(new PetStatusUpdate(this)); if (isVisible()) broadcastNpcInfo(val); } public void broadcastNpcInfo(int val) { Collection<L2PcInstance> plrs = getKnownList().getKnownPlayers().values(); for (L2PcInstance player : plrs) { if (player == null || player == getOwner()) continue; player.sendPacket(new SummonInfo(this, player, val)); } } public boolean isHungry() { return false; } @Override public void onSpawn() { super.onSpawn(); // Need it only for "crests on summons" custom. if (NPCConfig.SHOW_SUMMON_CREST) getOwner().sendPacket(new SummonInfo(this, getOwner(), 0)); sendPacket(new RelationChanged(this, getOwner().getRelation(getOwner()), false)); broadcastRelationsChanges(); } @Override public void broadcastRelationsChanges() { final Collection<L2PcInstance> knownlist = getOwner().getKnownList().getKnownPlayersInRadius(800); for (L2PcInstance player : knownlist) { if (player != null) player.sendPacket(new RelationChanged(this, getOwner().getRelation(player), isAutoAttackable(player))); } } @Override public void sendInfo(L2PcInstance activeChar) { // Check if the L2PcInstance is the owner of the Pet if (activeChar.equals(getOwner())) { activeChar.sendPacket(new PetInfo(this, 0)); // The PetInfo packet wipes the PartySpelled (list of active spells' icons). Re-add them updateEffectIcons(true); if (this instanceof L2PetInstance) activeChar.sendPacket(new PetItemList((L2PetInstance) this)); } else activeChar.sendPacket(new SummonInfo(this, activeChar, 0)); } }