/*
* This file is part of aion-unique <aion-unique.org>.
*
* aion-unique 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-unique 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-unique. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.gameserver.services;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import com.aionemu.gameserver.dataholders.ZoneData;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.templates.zone.ZoneTemplate;
import com.aionemu.gameserver.taskmanager.AbstractFIFOPeriodicTaskManager;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.world.MapRegion;
import com.aionemu.gameserver.world.WorldPosition;
import com.aionemu.gameserver.world.zone.ZoneInstance;
import com.aionemu.gameserver.world.zone.ZoneName;
import com.google.inject.Inject;
/**
* @author ATracer
*
*/
public final class ZoneService extends AbstractFIFOPeriodicTaskManager<Player>
{
private Map<ZoneName, ZoneInstance> zoneMap = new HashMap<ZoneName, ZoneInstance>();
private Map<Integer, Collection<ZoneInstance>> zoneByMapIdMap = new HashMap<Integer, Collection<ZoneInstance>>();
private ZoneData zoneData;
private static final long DROWN_PERIOD = 2000;
@Inject
public ZoneService(ZoneData zoneData)
{
super(4000);
this.zoneData = zoneData;
initializeZones();
}
@Override
protected void callTask(Player player)
{
if(player != null)
{
for(byte mask; (mask = player.getController().getZoneUpdateMask()) != 0;)
{
for(ZoneUpdateMode mode : VALUES)
{
mode.tryUpdateZone(player, mask);
}
}
}
}
private static final ZoneUpdateMode[] VALUES = ZoneUpdateMode.values();
/**
* Zone update can be either partial (ZONE_UPDATE) or complete (ZONE_REFRESH)
*/
public static enum ZoneUpdateMode
{
ZONE_UPDATE {
@Override
public void zoneTask(Player player)
{
player.getController().updateZoneImpl();
player.getController().checkWaterLevel();
}
},
ZONE_REFRESH {
@Override
public void zoneTask(Player player)
{
player.getController().refreshZoneImpl();
}
}
;
private final byte MASK;
private ZoneUpdateMode()
{
MASK = (byte) (1 << ordinal());
}
public byte mask()
{
return MASK;
}
protected abstract void zoneTask(Player player);
protected final void tryUpdateZone(final Player player, byte mask)
{
if((mask & mask()) == mask())
{
zoneTask(player);
player.getController().removeZoneUpdateMask(this);
}
}
}
/**
* Initializes zone instances using zone templates from xml
* Adds neighbors to each zone instance using lookup by ZoneName
*/
public void initializeZones()
{
Iterator<ZoneTemplate> iterator = zoneData.iterator();
while(iterator.hasNext())
{
ZoneTemplate template = iterator.next();
ZoneInstance instance = new ZoneInstance(template);
zoneMap.put(template.getName(), instance);
Collection<ZoneInstance> zoneListForMap = zoneByMapIdMap.get(template.getMapid());
if(zoneListForMap == null)
{
zoneListForMap = createZoneSetCollection();
zoneByMapIdMap.put(template.getMapid(), zoneListForMap);
}
zoneListForMap.add(instance);
}
for(ZoneInstance zoneInstance : zoneMap.values())
{
ZoneTemplate template = zoneInstance.getTemplate();
Collection<ZoneInstance> neighbors = createZoneSetCollection();
for(ZoneName zone : template.getLink())
{
neighbors.add(zoneMap.get(zone));
}
zoneInstance.setNeighbors(neighbors);
}
}
/**
* Collection that sorts zone instances according to the template priority
* Zone with lower priority has higher importance
*
* @return
*/
public Collection<ZoneInstance> createZoneSetCollection()
{
SortedSet<ZoneInstance> collection = new TreeSet<ZoneInstance>(new Comparator<ZoneInstance>(){
@Override
public int compare(ZoneInstance o1, ZoneInstance o2)
{
return o1.getPriority() > o2.getPriority() ? 1 : -1;
}
});
return collection;
}
/**
* Will check current zone of player and call corresponding controller methods
*
* @param player
*/
public void checkZone(Player player)
{
ZoneInstance currentInstance = player.getZoneInstance();
if(currentInstance == null)
{
return;
}
Collection<ZoneInstance> neighbors = currentInstance.getNeighbors();
if(neighbors == null)
return;
for(ZoneInstance zone : neighbors)
{
if(checkPointInZone(zone, player.getPosition()))
{
player.setZoneInstance(zone);
player.getController().onEnterZone(zone);
player.getController().onLeaveZone(currentInstance);
return;
}
}
}
/**
* @param player
*/
public void findZoneInCurrentMap(Player player)
{
MapRegion mapRegion = player.getActiveRegion();
if(mapRegion == null)
return;
Collection<ZoneInstance> zones = zoneByMapIdMap.get(mapRegion.getMapId());
if(zones == null)
{
player.getController().resetZone();
return;
}
for(ZoneInstance zone : zones)
{
if(checkPointInZone(zone, player.getPosition()))
{
player.setZoneInstance(zone);
player.getController().onEnterZone(zone);
return;
}
}
}
/**
* Checks whether player is inside specific zone
*
* @param player
* @param zoneName
* @return true if player is inside specified zone
*/
public boolean isInsideZone(Player player, ZoneName zoneName)
{
ZoneInstance zoneInstance = zoneMap.get(zoneName);
if(zoneInstance == null)
return false;
return checkPointInZone(zoneInstance, player.getPosition());
}
/**
* Main algorithm that analyzes point-in-polygon
*
* @param zone
* @param position
* @return
*/
private boolean checkPointInZone(ZoneInstance zone, WorldPosition position)
{
int corners = zone.getCorners();
float[] xCoords = zone.getxCoordinates();
float[] yCoords = zone.getyCoordinates();
float top = zone.getTop();
float bottom = zone.getBottom();
float x = position.getX();
float y = position.getY();
float z = position.getZ();
//first z coordinate is checked
if(top != 0 || bottom != 0)//not defined
{
if(z > top || z < bottom)
return false;
}
int i, j = corners-1;
boolean inside = false;
for (i=0; i<corners; i++)
{
if (yCoords[i] < y && yCoords[j] >= y || yCoords[j] < y && yCoords[i] >= y)
{
if (xCoords[i]+(y-yCoords[i])/(yCoords[j]-yCoords[i])*(xCoords[j]-xCoords[i])<x)
{
inside = !inside;
}
}
j=i;
}
return inside;
}
/**
* Drowning / immediate death in maps related functionality
*/
/**
*
* @param player
*/
public void startDrowning(Player player)
{
if(!isDrowning(player))
scheduleDrowningTask(player);
}
/**
*
* @param player
*/
public void stopDrowning(Player player)
{
if(isDrowning(player))
{
player.getController().cancelTask(TaskId.DROWN);
}
}
/**
*
* @param player
* @return
*/
private boolean isDrowning(Player player)
{
return player.getController().getTask(TaskId.DROWN) == null ? false : true;
}
/**
*
* @param player
*/
private void scheduleDrowningTask(final Player player)
{
player.getController().addTask(TaskId.DROWN, ThreadPoolManager.getInstance().scheduleAtFixedRate(new Runnable(){
@Override
public void run()
{
int value = Math.round(player.getLifeStats().getMaxHp() / 10);
//TODO retail emotion, attack_status packets sending
if(!player.getLifeStats().isAlreadyDead())
{
player.getLifeStats().reduceHp(value, null);
player.getLifeStats().sendHpPacketUpdate();
}
else
{
stopDrowning(player);
}
}
}, 0, DROWN_PERIOD));
}
/* (non-Javadoc)
* @see com.aionemu.gameserver.taskmanager.AbstractFIFOPeriodicTaskManager#getCalledMethodName()
*/
@Override
protected String getCalledMethodName()
{
return "zoneService()";
}
}