package org.reunionemu.jreunion.game; import java.util.Iterator; import java.util.List; import java.util.TimerTask; import java.util.Vector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.reunionemu.jcommon.ParsedItem; import org.reunionemu.jcommon.Parser; import org.reunionemu.jreunion.game.Player.Race; import org.reunionemu.jreunion.game.items.equipment.*; import org.reunionemu.jreunion.game.npc.Mob; import org.reunionemu.jreunion.game.npc.NpcShop; import org.reunionemu.jreunion.server.Area; import org.reunionemu.jreunion.server.ItemManager; import org.reunionemu.jreunion.server.LocalMap; import org.reunionemu.jreunion.server.Area.Field; import org.reunionemu.jreunion.server.PacketFactory.Type; import org.reunionemu.jreunion.server.Client; import org.reunionemu.jreunion.server.Reference; import org.reunionemu.jreunion.server.Server; import org.reunionemu.jreunion.server.Session; import org.reunionemu.jreunion.server.SessionList; /** * @author Aidamina * @license http://reunion.googlecode.com/svn/trunk/license.txt */ public class Npc<T extends NpcType> extends LivingObject { private T type; private NpcSpawn spawn; private boolean isMoving; private boolean isAttacking; private boolean isBoss; private int mutantType; private int unknown1; private int unknown2; private int unknown3; private NpcShop shop; private long areaRadius; private long attackRadius; public Npc(T type) { super(); setType(type); shop = null; setMaxHp(type.getMaxHp()); setHp(type.getMaxHp()); setAreaRadius(Server.getInstance().getWorld().getServerSetings().getMobRadiusArea()); setLevel(getType().getLevel()); if(this.getType() instanceof Mob){ if(((Mob)this.getType()).getAttackType() == AttackType.CLOSE_MELEE.value){ setAttackRadius(Server.getInstance().getWorld().getServerSetings().getCloseAttackRadius()); } else { setAttackRadius(Server.getInstance().getWorld().getServerSetings().getRangeAttackRadius()); } } } @Override public void setHp(long hp){ super.setHp(hp); } @Override public void setMaxHp(long hp){ super.setMaxHp(this.getType().getMaxHp()); } @Override public void setName(String livingObjectName){ super.setName(this.getType().getName()); } @Override public void setDmgType(int dmgType) { super.setDmgType(this.getType().getDmgType()); } public T getType() { return type; } private void setType(T npcType){ this.type = npcType; } public NpcSpawn getSpawn() { return spawn; } public void setSpawn(NpcSpawn spawn) { this.spawn = spawn; } public boolean isMoving() { return isMoving; } public void setMoving(boolean isMoving) { this.isMoving = isMoving; } public boolean isAttacking() { return isAttacking; } public void setAttacking(boolean isAttacking) { this.isAttacking = isAttacking; } public boolean isBoss() { return isBoss; } public void setBoss(boolean isBoss) { this.isBoss = isBoss; } public void setUnknown1(int unknown1) { this.unknown1 = unknown1; } public void setUnknown2(int unknown2) { this.unknown2 = unknown2; } public int getUnknown1() { return unknown1; } public int getUnknown2() { return unknown2; } public int getUnknown3() { return unknown3; } public void setUnknown3(int unknown3) { this.unknown3 = unknown3; //this seems to be 9 or 10 for certain npcs and 1 for mobs } public NpcShop getShop() { return shop; } public void setShop(NpcShop shop) { this.shop = shop; } public void loadShop() { shop = new NpcShop(this); } public long getAreaRadius() { return areaRadius; } public void setAreaRadius(long areaRadius) { this.areaRadius = areaRadius; } @Override public void enter(Session session) { session.getOwner().getClient().sendPacket(Type.IN_NPC, this); } @Override public void exit(Session session) { session.getOwner().getClient().sendPacket(Type.OUT, this); } public long getAttackRadius() { return attackRadius; } public void setAttackRadius(long attackRadius) { this.attackRadius = attackRadius; } public void setBoss(){ if(this.getType() instanceof Mob){ //Mob mob = ((Mob)this.getType()); if(!isBoss()){ setBoss(true); setMaxHp(getMaxHp()*2); setHp(getMaxHp()); //mob.setExp(mob.getExp()*2); //mob.setLime(mob.getLime()*2); //mob.setDmg(mob.getDmg()*2); } } } public int getMutantType() { return mutantType; } public void setMutantType(int mutantType) { this.mutantType = mutantType; } /**Returns a random mutant type: * 1 - Red - Resistance against short-range physical attack * 2 - Blue - Resistance against magical attack * 3 - Green - Resistance power against summon attack * 4 - Yellow - Resistance power against long-range physical attack * * @return mutantType */ public int getRandomMutantType() { int mutantType = 0; while(mutantType < 1 || mutantType > 4){ mutantType = (int)(Server.getRand().nextFloat()*10); } return mutantType; } public boolean isMutant(){ return getMutantType() == 0 ? false : true; } public float getMutantResistance(LivingObject livingObject){ float mobMutantModifier = Server.getInstance().getWorld().getServerSetings().getMobMutantModifier(); float returnvalue = 0.5f; /* 1(red) - Resistance against short-range physical attack * 2(blue) - Resistance against magical attack * 3(green) - Resistance against summon attack * 4(yellow) - Resistance against long-range physical attack */ if(getMutantType() == livingObject.getLastAttackType()){ // Full resistance to specific damage type returnvalue = 1 - mobMutantModifier; } else { // Generally half resistance returnvalue = 1 - (mobMutantModifier / 2 ); } if (returnvalue > 0.9) { returnvalue = 0.9f; } if (returnvalue < 0.1) { returnvalue = 0.1f; } return (float) (returnvalue); } public int getMutantGemStoneType(boolean full){ if(getType().getLevel() <= 30){ return (full) ? 215 : 535; } else if(getLevel() <= 60){ return (full) ? 216 : 536; } else if(getLevel() <= 90){ return (full) ? 217 : 537; } else if(getLevel() <= 120){ return (full) ? 218 : 538; } else if(getLevel() <= 150){ return (full) ? 219 : 539; } else if(getLevel() <= 180){ return (full) ? 220 : 540; } else { return (full) ? 221 : 541; } } public void kill(Player player) { LocalMap localMap = getPosition().getLocalMap(); SessionList<Session> list = localMap.GetSessions(getPosition()); long serverXpRate = Server.getInstance().getWorld().getServerSetings().getXp(); long serverLimeRate = Server.getInstance().getWorld().getServerSetings().getLime(); float mutantModifier = Server.getInstance().getWorld().getServerSetings().getMobMutantModifier(); int expPlayerMobDifference = Server.getInstance().getWorld().getServerSetings().getExpPlayerMobDifference(); int expLowerStep = Server.getInstance().getWorld().getServerSetings().getExpLowerStep(); long npcLime = 0; long npcExp = 0; float modifier = 1; if(this.getLevel()/player.getLevel() >=1) modifier = 1; else if((player.getLevel()-this.getLevel()) > expPlayerMobDifference) { modifier = (((player.getLevel()-this.getLevel()-expPlayerMobDifference)*expLowerStep)-100)*(-1); modifier /= 100; } if(this.getType() instanceof Mob){ npcLime = (long)(((Mob)this.getType()).getLime()*serverLimeRate); //on lime no modifier is needed npcExp = (long)(((Mob)this.getType()).getExp()*serverXpRate*modifier); } if(isMutant()){ npcLime = (long) (npcLime * (1 + mutantModifier)); npcExp = (long) (npcExp * (1 + mutantModifier)); } if(isBoss()){ npcLime = (long) (npcLime * 2); npcExp = (long) (npcLime * 2); } npcExp = (npcExp <= 0) ? 1 : npcExp; //check that player will receive a minimum amount of exp npcLime = (npcLime <= 0) ? 1 : npcLime; //check that player will receive a minimum amount of lime setHp(0); localMap.removeEntity(this); list.exit(this, false); NpcSpawn spawn = this.getSpawn(); if (spawn != null) { spawn.kill(); } synchronized(player){ //Distribute exp and lime to players from mob kill List<Player> playerList = new Vector<Player> (); if(player.getParty() == null){ playerList.add(player); } else{ npcExp = (long)(npcExp / player.getParty().getMembers().size()); npcLime = (long)(npcLime / player.getParty().getMembers().size()); playerList = player.getParty().getMembers(); } for(Player member : playerList){ member.setLime(member.getLime()+(npcLime)); member.setTotalExp(member.getTotalExp()+(npcExp)); member.setLevelUpExp(member.getLevelUpExp()-(npcExp)); member.getClient().sendPacket(Type.SAY, "Experience"+ ((serverXpRate != 1) ? "(x"+serverXpRate+")" : "")+": " + (npcExp) + " Lime"+ ((serverLimeRate != 1) ? "(x"+serverLimeRate+")" : "")+": " + (npcLime)); if(member.getQuestState() != null){ player.getQuestState().handleProgress(this, player); } } } ItemManager itemManager = player.getClient().getWorld().getItemManager(); List<Item<?>> itemList = new Vector<Item<?>> (); //Handle with the mutant Item drop if(isMutant()){ itemList.add(itemManager.create(getMutantGemStoneType(false), 0, 0, 0, 0, 0)); } float r = Server.getRand().nextFloat(); //Rough Gem Drop if(r <= .01 && Server.getRand().nextFloat() <= .20) { itemList.add(itemManager.create(getMutantGemStoneType(true), 0, 0, 0, 0, 0)); } //Handle with the Item drop chance Parser dropListParser = Reference.getInstance().getDropListReference(); Iterator<ParsedItem> iter = dropListParser.getItemListIterator(); List<ItemType> dropList = new Vector<ItemType> (); while(iter.hasNext()) { ParsedItem parsedItem = iter.next(); if(Integer.parseInt(parsedItem.getMemberValue("Mob")) == getType().getTypeId()){ float rate = Float.parseFloat(parsedItem.getMemberValue("Rate")); if( r < rate){ int itemTypeId = Integer.parseInt(parsedItem.getMemberValue("Item")); dropList.add(itemManager.getItemType(itemTypeId)); } } } if(dropList.size() > 0){ int dropListRandomPos = dropList.size()==1 ? 0 : dropList.size(); if(dropListRandomPos > 0) { //randomly select one item from the item list. while(dropListRandomPos >= dropList.size()){ dropListRandomPos = Server.getRand().nextInt(dropList.size()-1); } } ItemType itemType = dropList.get(dropListRandomPos); //handles with the luck of drop a plus item. float gemLuck = Server.getRand().nextFloat(); float itemPlusByOne = getPosition().getLocalMap().getWorld().getServerSetings().getItemPlusByOne(); float itemPlusByTwo = getPosition().getLocalMap().getWorld().getServerSetings().getItemPlusByTwo(); int gemNumber = 0; if(itemType.isUpgradable() && itemType.getLevel() <= 180){ gemNumber = (gemLuck < itemPlusByOne ? (gemLuck < itemPlusByTwo ? 3 : 1) : 0); } itemList.add(itemManager.create(itemType.getTypeId(), gemNumber, (int)itemType.getMaxExtraStats(), itemType.getMaxDurability(), 0, 0)); } //handles the items drop command and packets. for(Item<?> item : itemList){ RoamingItem roamingItem = getPosition().getLocalMap().getWorld(). getCommand().dropItem(this.getPosition(), item, player); roamingItem.setOwner(player); roamingItem.setDropExclusivity(player); LoggerFactory.getLogger(Npc.class).info("Mob "+this+" droped roaming item "+roamingItem); } } public void moveFree() { if(isRunning()) return; setIsRunning(true); //Area npcArea = getPosition().getLocalMap().getArea(); Position newPos = getRandomPosition(); int newPositionTries = 10; //while((!npcArea.get(newPos.getX() / 10, newPos.getY() / 10,Field.MOB))){ while(!isPathWalkable(newPos) && newPositionTries-- > 0) { newPos = getRandomPosition(); } if(newPositionTries < 0){ //LoggerFactory.getLogger(this.getClass()).warn("Mob %s couldn't move.",this); setIsRunning(false); return; } walk(newPos, isRunning()); setIsRunning(false); } /** * Checks if Position is within other Npc Radius Area. * * @param position * @return true or false */ public boolean isNpcCollision(Position position){ for(Entity entity : getPosition().getLocalMap().getEntities()){ if(!(entity instanceof Npc)){ continue; } Npc<?> npc = (Npc<?>)entity; if(position.within(npc.getPosition(), getAreaRadius())){ return true; } } return false; } public int getNewPosX(Position position, double distance){ //distance: r (player) //x = a + r * cos(t) double nSpeed = ((Mob)this.getType()).getSpeed(); //r (mob) //double percent = ((100*nSpeed)/distance); double pPosX = position.getX(); //x double nPosX = getPosition().getX(); //a double directionAngle = Math.acos((pPosX-nPosX)/distance); //return (int) ((((pPosX-nPosX)*percent)/100)+nPosX); return (int)(nPosX + (nSpeed * Math.cos(directionAngle))); } public int getNewPosY(Position position, double distance){ //distance: r (player) //y = b + r * sin(t) double nSpeed = ((Mob)this.getType()).getSpeed(); //r (mob) //double percent = ((100*nSpeed)/distance); double pPosY = position.getY(); //y double nPosY = getPosition().getY(); //b double directionAngle = Math.asin((pPosY-nPosY)/distance); //return (int) ((((pPosY-nPosY)*percent)/100)+nPosY); return (int)(nPosY + (nSpeed * Math.sin(directionAngle))); } public Position getRandomPosition(){ Position newPosition = this.getPosition().clone(); double directionAngle = 360; while(directionAngle >= 360){ directionAngle = (int)(Server.getRand().nextFloat()*100); } newPosition.setX(getRandomPosX(directionAngle)); newPosition.setY(getRandomPosY(directionAngle)); return newPosition; } public int getRandomPosX(double directionAngle){ //x = a + r * cos(t) double nPosX = getPosition().getX(); //a int speed = ((Mob)this.getType()).getSpeed(); //r return (int)(nPosX + (speed * Math.cos(directionAngle))); //x } public int getRandomPosY(double directionAngle){ //y = b + r * sin(t) double nPosY = getPosition().getY(); //b int speed = ((Mob)this.getType()).getSpeed(); //r return (int)(nPosY + (speed * Math.sin(directionAngle))); //y } public void moveToPlayer(Player player) { Client client = player.getClient(); if(client==null || isRunning()) return; setIsRunning(true); Position newPosition = getPosition().clone(); double distance = this.getPosition().distance(player.getPosition()); int newPosX = getNewPosX(player.getPosition().clone(), distance); int newPosY = getNewPosY(player.getPosition().clone(), distance); newPosX = (newPosX == 0) ? newPosition.getX() : newPosX; newPosY = (newPosY == 0) ? newPosition.getY() : newPosY; newPosition.setX(newPosX); newPosition.setY(newPosY); if(!isPathWalkable(newPosition)){ setIsRunning(false); moveFree(); return; } walk(newPosition, isRunning()); setIsRunning(false); } public boolean isPathWalkable(Position position){ Area mobArea = this.getPosition().getLocalMap().getArea(); double distance = this.getPosition().distance(position); Position newPos = getPosition().clone(); if(distance > this.getSpawn().getRadius()) return false; while(distance > 0){ int posX = getNewPosX(position.clone(), distance); int posY = getNewPosY(position.clone(), distance); posX = (posX == 0) ? newPos.getX() : posX; posY = (posY == 0) ? newPos.getY() : posY; newPos.setX(posX); newPos.setY(posY); if(!mobArea.get(posX / 10, posY / 10,Field.MOB) || isNpcCollision(newPos)){ return false; } distance -= 0.1; } return true; } public void attackPlayer(Player player) { Client client = player.getClient(); if(client==null || isAttacking()) return; setAttacking(true); List<Skill> defensiveSkills = player.getDefensiveSkills(); int npcDmg = getDamage((int) player.getDef()); npcDmg = (isBoss() ? npcDmg*2 : npcDmg); //check if npc is boss. //npcDmg = (npcDmg < 1) ? 1 : npcDmg; //make sure the npc will have a minimum damage value. if (defensiveSkills.size() == 0) { player.setHp(player.getHp() - npcDmg); } else { for (Skill skill : defensiveSkills) { if (player.getSkillLevel(skill) > 0) { if(!skill.work(player, this)){ player.setHp(player.getHp() - npcDmg); } } else { player.setHp(player.getHp() - npcDmg); } } } this.getInterested().sendPacket(Type.ATTACK,this,player,0); setAttacking(false); } public int getDamage(int playerDef){ if(!(getType() instanceof Mob)){ return 0; } Mob mob = (Mob)this.getType(); float mobMutantModifier = isMutant() ? Server.getInstance().getWorld().getServerSetings().getMobMutantModifier()+1 : 1; int damage = (int)((mob.getDmg()*mobMutantModifier) - (playerDef/2 + ((playerDef/2)*Server.getRand().nextFloat()))); return damage < 1 ? 1 : damage; } public void work() { try { int isMovementEnabled = Server.getInstance().getWorld().getServerSetings().getMobsMovement(); if(isRunning() || getHp() == 0 || isMovementEnabled == 0) return; Iterator<Player> iterPlayer = Server.getInstance().getWorld().getPlayerManager().getPlayerListIterator(); double smallestDistance = 150; Player closestPlayer = null; Area mobArea = getPosition().getLocalMap().getArea(); boolean moveFree = false; while (iterPlayer.hasNext()) { //for(Player player : getPosition().getLocalMap().getPlayerList()) { Player player = iterPlayer.next(); if(this.getPosition().getMap().getId() != player.getPosition().getMap().getId()){ continue; } Position position = player.getPosition(); double distance = getPosition().distance(player.getPosition()); if(moveFree == false){ moveFree = player.getSession().contains(this.getPosition()) ? true : false; } if (player.getClient() == null) { continue; } else if (player.getClient().getState() != Client.State.INGAME || this.getPosition().getLocalMap() != player.getPosition().getLocalMap() || !player.getSession().contains(this.getPosition()) || !mobArea.get(position.getX() / 10, position.getY() / 10,Field.MOB) || player.getHp() <= 0) { player.update(); continue; } if(distance < smallestDistance){ smallestDistance = distance; closestPlayer = player; } } // Condition that verify if the mob can move freely or not. // If the distance between the mob and the player is less or equal // then 150 (distance that makes the mob move to the player // direction) and if the player position is a walkable position // for mob then the mob will chase or attack the player, else the mob will // move freely. if (smallestDistance < 150) { if(smallestDistance > getAttackRadius()) { moveToPlayer(closestPlayer); } else { attackPlayer(closestPlayer); } } else if(moveFree){ moveFree(); } } catch (Exception e) { LoggerFactory.getLogger(this.getClass()).info("Mob Bug "+e); //TODO: Fix Mob move bug } } public String toString(){ StringBuffer buffer = new StringBuffer(); buffer.append("{"); buffer.append("id:"); buffer.append(getEntityId()); buffer.append(", "); buffer.append("type:"); buffer.append(getType().getTypeId()); buffer.append(", "); buffer.append("name:"); buffer.append(getName()); buffer.append(", "); buffer.append("spawn:"); buffer.append(getSpawn().getId()); buffer.append("}"); return buffer.toString(); } }