/*
* This file is part of aion-emu <aion-emu.com>.
*
* aion-emu 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.
*
* aion-emu 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 aion-emu. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.gameserver.controllers;
import java.util.List;
import java.util.concurrent.Future;
import com.aionemu.gameserver.controllers.attack.AttackResult;
import com.aionemu.gameserver.controllers.attack.AttackUtil;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Gatherable;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.StaticObject;
import com.aionemu.gameserver.model.gameobjects.Summon;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.player.SkillListEntry;
import com.aionemu.gameserver.model.gameobjects.state.CreatureState;
import com.aionemu.gameserver.model.gameobjects.state.CreatureVisualState;
import com.aionemu.gameserver.model.gameobjects.stats.PlayerGameStats;
import com.aionemu.gameserver.model.templates.stats.PlayerStatsTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS;
import com.aionemu.gameserver.network.aion.serverpackets.SM_DELETE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_DIE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_EMOTION;
import com.aionemu.gameserver.network.aion.serverpackets.SM_GATHERABLE_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_LEVEL_UPDATE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_NEARBY_QUESTS;
import com.aionemu.gameserver.network.aion.serverpackets.SM_NPC_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_PLAYER_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_PLAYER_STATE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_PRIVATE_STORE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SKILL_CANCEL;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SKILL_LIST;
import com.aionemu.gameserver.network.aion.serverpackets.SM_STATS_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SUMMON_PANEL;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE;
import com.aionemu.gameserver.questEngine.model.QuestEnv;
import com.aionemu.gameserver.restrictions.RestrictionsManager;
import com.aionemu.gameserver.services.ClassChangeService;
import com.aionemu.gameserver.services.ZoneService.ZoneUpdateMode;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.model.HealType;
import com.aionemu.gameserver.skillengine.model.Skill;
import com.aionemu.gameserver.taskmanager.tasks.PacketBroadcaster.BroadcastMode;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.world.World;
import com.aionemu.gameserver.world.zone.ZoneInstance;
import com.google.inject.internal.Nullable;
/**
* This class is for controlling players.
*
* @author -Nemesiss-, ATracer (2009-09-29), xavier
*
*/
public class PlayerController extends CreatureController<Player>
{
private boolean isInShutdownProgress;
/**
* Zone update mask
*/
private volatile byte zoneUpdateMask;
/**
* {@inheritDoc}
*/
@Override
public void see(VisibleObject object)
{
super.see(object);
if(object instanceof Player)
{
PacketSendUtility.sendPacket(getOwner(), new SM_PLAYER_INFO((Player) object, getOwner().isEnemyPlayer((Player)object)));
getOwner().getEffectController().sendEffectIconsTo((Player) object);
}
else if(object instanceof Npc)
{
boolean update = false;
Npc npc = ((Npc) object);
PacketSendUtility.sendPacket(getOwner(), new SM_NPC_INFO(npc, getOwner()));
for(int questId : sp.getQuestEngine().getNpcQuestData(npc.getNpcId()).getOnQuestStart())
{
if(sp.getQuestService().checkStartCondition(new QuestEnv(object, getOwner(), questId, 0)))
{
if(!getOwner().getNearbyQuests().contains(questId))
{
update = true;
getOwner().getNearbyQuests().add(questId);
}
}
}
if(update)
updateNearbyQuestList();
}
else if(object instanceof Summon)
{
Summon npc = ((Summon) object);
PacketSendUtility.sendPacket(getOwner(), new SM_NPC_INFO(npc));
}
else if(object instanceof Gatherable || object instanceof StaticObject)
{
PacketSendUtility.sendPacket(getOwner(), new SM_GATHERABLE_INFO(object));
}
}
/**
* {@inheritDoc}
*/
@Override
public void notSee(VisibleObject object, boolean isOutOfRange)
{
super.notSee(object, isOutOfRange);
if(object instanceof Npc)
{
boolean update = false;
for(int questId : sp.getQuestEngine().getNpcQuestData(((Npc) object).getNpcId()).getOnQuestStart())
{
if(sp.getQuestService().checkStartCondition(new QuestEnv(object, getOwner(), questId, 0)))
{
if(getOwner().getNearbyQuests().contains(questId))
{
update = true;
getOwner().getNearbyQuests().remove(getOwner().getNearbyQuests().indexOf(questId));
}
}
}
if(update)
updateNearbyQuestList();
}
PacketSendUtility.sendPacket(getOwner(), new SM_DELETE(object, isOutOfRange ? 0 : 15));
}
public void updateNearbyQuests()
{
getOwner().getNearbyQuests().clear();
for(VisibleObject obj : getOwner().getKnownList())
{
if(obj instanceof Npc)
{
for(int questId : sp.getQuestEngine().getNpcQuestData(((Npc) obj).getNpcId()).getOnQuestStart())
{
if(sp.getQuestService().checkStartCondition(new QuestEnv(obj, getOwner(), questId, 0)))
{
if(!getOwner().getNearbyQuests().contains(questId))
{
getOwner().getNearbyQuests().add(questId);
}
}
}
}
}
updateNearbyQuestList();
}
/**
* Will be called by ZoneManager when player enters specific zone
*
* @param zoneInstance
*/
public void onEnterZone(ZoneInstance zoneInstance)
{
sp.getQuestEngine()
.onEnterZone(new QuestEnv(null, this.getOwner(), 0, 0), zoneInstance.getTemplate().getName());
}
/**
* Will be called by ZoneManager when player leaves specific zone
*
* @param zoneInstance
*/
public void onLeaveZone(ZoneInstance zoneInstance)
{
}
/**
* Set zone instance as null (Where no zones defined)
*/
public void resetZone()
{
getOwner().setZoneInstance(null);
}
/**
* {@inheritDoc}
*
* Should only be triggered from one place (life stats)
*/
@Override
public void onDie(@Nullable Creature lastAttacker)
{
Player player = this.getOwner();
Creature master = null;
if(lastAttacker != null)
master = lastAttacker.getMaster();
if(master instanceof Player)
{
if(isDueling((Player) master))
{
sp.getDuelService().onDie(player);
return;
}
else
{
doReward(master);
}
}
super.onDie(lastAttacker);
if(master instanceof Npc)
{
if(player.getLevel() > 4)
player.getCommonData().calculateExpLoss();
}
PacketSendUtility.broadcastPacket(player, new SM_EMOTION(player, 13, 0, lastAttacker == null ? 0 : lastAttacker
.getObjectId()), true);
PacketSendUtility.sendPacket(player, new SM_DIE(ReviveType.BIND_REVIVE));
PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.DIE);
sp.getQuestEngine().onDie(new QuestEnv(null, player, 0, 0));
}
@Override
public void doReward(Creature creature)
{
sp.getAbyssService().doReward(getOwner(), (Player) creature);
}
@Override
public void attackTarget(Creature target)
{
Player player = getOwner();
/**
* Check all prerequisites
*/
if(target == null || !player.canAttack())
return;
PlayerGameStats gameStats = player.getGameStats();
// check player attack Z distance
if(Math.abs(player.getZ() - target.getZ()) > 6)
return;
if(!RestrictionsManager.canAttack(player, target))
return;
/**
* notify attack observers
*/
super.attackTarget(target);
/**
* Calculate and apply damage
*/
List<AttackResult> attackResult = AttackUtil.calculateAttackResult(player, target);
int damage = 0;
for(AttackResult result : attackResult)
{
damage += result.getDamage();
}
long time = System.currentTimeMillis();
int attackType = 0; // TODO investigate attack types
PacketSendUtility.broadcastPacket(player, new SM_ATTACK(player, target, gameStats.getAttackCounter(),
(int) time, attackType, attackResult), true);
target.getController().onAttack(player, damage);
gameStats.increaseAttackCounter();
}
@Override
public void onAttack(Creature creature, int skillId, TYPE type, int damage)
{
if(getOwner().getLifeStats().isAlreadyDead())
return;
super.onAttack(creature, skillId, type, damage);
if(getOwner().isInvul())
damage = 0;
getOwner().getLifeStats().reduceHp(damage, creature);
PacketSendUtility.broadcastPacket(getOwner(), new SM_ATTACK_STATUS(getOwner(), type, skillId, damage), true);
}
/**
*
* @param skillId
*/
@Override
public void useSkill(int skillId)
{
Player player = getOwner();
Skill skill = SkillEngine.getInstance().getSkillFor(player, skillId, player.getTarget());
if(skill != null)
{
if(!RestrictionsManager.canUseSkill(player, skill))
return;
skill.useSkill();
}
}
@Override
public void onMove()
{
super.onMove();
addZoneUpdateMask(ZoneUpdateMode.ZONE_UPDATE);
}
@Override
public void onStopMove()
{
super.onStopMove();
}
@Override
public void onStartMove()
{
if(this.getOwner().isCasting())
{
this.getOwner().setCasting(null);
PacketSendUtility.sendPacket(this.getOwner(), new SM_SKILL_CANCEL(this.getOwner()));
PacketSendUtility.sendPacket(this.getOwner(), SM_SYSTEM_MESSAGE.STR_SKILL_CANCELED());
}
super.onStartMove();
}
/**
*
*/
public void updatePassiveStats()
{
Player player = getOwner();
for(SkillListEntry skillEntry : player.getSkillList().getAllSkills())
{
Skill skill = SkillEngine.getInstance().getSkillFor(player, skillEntry.getSkillId(), player.getTarget());
if(skill != null && skill.isPassive())
{
skill.useSkill();
}
}
}
@Override
public Player getOwner()
{
return (Player) super.getOwner();
}
@Override
public void onRestore(HealType healType, int value)
{
super.onRestore(healType, value);
switch(healType)
{
case DP:
getOwner().getCommonData().addDp(value);
break;
}
}
/**
*
* @param player
* @return
*/
public boolean isDueling(Player player)
{
return sp.getDuelService().isDueling(player.getObjectId(), getOwner().getObjectId());
}
public void updateNearbyQuestList()
{
getOwner().addPacketBroadcastMask(BroadcastMode.UPDATE_NEARBY_QUEST_LIST);
}
public void updateNearbyQuestListImpl()
{
PacketSendUtility.sendPacket(getOwner(), new SM_NEARBY_QUESTS(getOwner().getNearbyQuests()));
}
public boolean isInShutdownProgress()
{
return isInShutdownProgress;
}
public void setInShutdownProgress(boolean isInShutdownProgress)
{
this.isInShutdownProgress = isInShutdownProgress;
}
/**
* Handle dialog
*/
public void onDialogSelect(int dialogId, Player player, int questId)
{
switch(dialogId)
{
case 2:
PacketSendUtility.sendPacket(player, new SM_PRIVATE_STORE(getOwner().getStore()));
break;
}
}
/**
* @param level
*/
public void upgradePlayer(int level)
{
Player player = getOwner();
PlayerStatsTemplate statsTemplate = sp.getPlayerService().getPlayerStatsData().getTemplate(player);
player.setPlayerStatsTemplate(statsTemplate);
// update stats after setting new template
player.getGameStats().doLevelUpgrade();
player.getLifeStats().synchronizeWithMaxStats();
PacketSendUtility.broadcastPacket(player, new SM_LEVEL_UPDATE(player.getObjectId(), 0, level), true);
// Temporal
ClassChangeService.showClassChangeDialog(player);
sp.getQuestEngine().onLvlUp(new QuestEnv(null, player, 0, 0));
updateNearbyQuests();
PacketSendUtility.sendPacket(player, new SM_STATS_INFO(player));
if(level == 10 && player.getSkillList().getSkillEntry(30001) != null)
{
int skillLevel = player.getSkillList().getSkillLevel(30001);
player.getSkillList().removeSkill(30001);
PacketSendUtility.sendPacket(player, new SM_SKILL_LIST(player));
player.getSkillList().addSkill(player, 30002, skillLevel, true);
}
// add new skills
sp.getSkillLearnService().addNewSkills(player, false);
/** update member list packet if player is legion member **/
if(player.isLegionMember())
sp.getLegionService().updateMemberInfo(player);
}
/**
* After entering game player char is "blinking" which means that it's in under some protection, after making an
* action char stops blinking. - Starts protection active - Schedules task to end protection
*/
public void startProtectionActiveTask()
{
getOwner().setVisualState(CreatureVisualState.BLINKING);
PacketSendUtility.broadcastPacket(getOwner(), new SM_PLAYER_STATE(getOwner()), true);
Future<?> task = ThreadPoolManager.getInstance().schedule(new Runnable(){
@Override
public void run()
{
stopProtectionActiveTask();
}
}, 60000);
addTask(TaskId.PROTECTION_ACTIVE, task);
}
/**
* Stops protection active task after first move or use skill
*/
public void stopProtectionActiveTask()
{
cancelTask(TaskId.PROTECTION_ACTIVE);
Player player = getOwner();
if(player != null && player.isSpawned())
{
player.unsetVisualState(CreatureVisualState.BLINKING);
PacketSendUtility.broadcastPacket(player, new SM_PLAYER_STATE(player), true);
}
}
/**
* When player arrives at destination point of flying teleport
*/
public void onFlyTeleportEnd()
{
Player player = getOwner();
player.unsetState(CreatureState.FLIGHT_TELEPORT);
player.setFlightTeleportId(0);
player.setFlightDistance(0);
player.setState(CreatureState.ACTIVE);
addZoneUpdateMask(ZoneUpdateMode.ZONE_REFRESH);
}
/**
* Zone update mask management
*
* @param mode
*/
public final void addZoneUpdateMask(ZoneUpdateMode mode)
{
zoneUpdateMask |= mode.mask();
sp.getZoneService().add(getOwner());
}
public final void removeZoneUpdateMask(ZoneUpdateMode mode)
{
zoneUpdateMask &= ~mode.mask();
}
public final byte getZoneUpdateMask()
{
return zoneUpdateMask;
}
/**
* Update zone taking into account the current zone
*/
public void updateZoneImpl()
{
sp.getZoneService().checkZone(getOwner());
}
/**
* Refresh completely zone irrespective of the current zone
*/
public void refreshZoneImpl()
{
sp.getZoneService().findZoneInCurrentMap(getOwner());
}
/**
*
*/
public void ban()
{
// sp.getTeleportService().teleportTo(this.getOwner(), 510010000, 256f, 256f, 49f, 0);
}
/**
* Check water level (start drowning) and map death level (die)
*/
public void checkWaterLevel()
{
Player player = getOwner();
World world = sp.getWorld();
float z = player.getZ();
if(player.getLifeStats().isAlreadyDead())
return;
if(z < world.getWorldMap(player.getWorldId()).getDeathLevel())
{
die();
return;
}
ZoneInstance currentZone = player.getZoneInstance();
if(currentZone != null && currentZone.isBreath())
return;
//TODO need fix character height
float playerheight = player.getPlayerAppearance().getHeight() * 1.6f;
if(z < world.getWorldMap(player.getWorldId()).getWaterLevel() - playerheight)
sp.getZoneService().startDrowning(player);
else
sp.getZoneService().stopDrowning(player);
}
@Override
public void createSummon(int npcId, int skillLvl)
{
Player master = getOwner();
Summon summon = sp.getSpawnEngine().spawnSummon(master, npcId, skillLvl);
master.setSummon(summon);
PacketSendUtility.sendPacket(master, new SM_SUMMON_PANEL(summon));
PacketSendUtility.broadcastPacket(master, new SM_EMOTION(summon, 30));
}
}