package com.aionemu.gameserver.dataholders;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aionemu.commons.taskmanager.AbstractLockManager;
import com.aionemu.gameserver.model.Race;
import com.aionemu.gameserver.model.gameobjects.Gatherable;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.templates.event.EventTemplate;
import com.aionemu.gameserver.model.templates.spawns.*;
import com.aionemu.gameserver.model.templates.spawns.basespawns.BaseSpawn;
import com.aionemu.gameserver.model.templates.spawns.mercenaries.MercenaryRace;
import com.aionemu.gameserver.model.templates.spawns.mercenaries.MercenarySpawn;
import com.aionemu.gameserver.model.templates.spawns.mercenaries.MercenaryZone;
import com.aionemu.gameserver.model.templates.spawns.panesterra.AhserionsFlightSpawn;
import com.aionemu.gameserver.model.templates.spawns.riftspawns.RiftSpawn;
import com.aionemu.gameserver.model.templates.spawns.siegespawns.SiegeSpawn;
import com.aionemu.gameserver.model.templates.spawns.vortexspawns.VortexSpawn;
import com.aionemu.gameserver.model.templates.world.WorldMapTemplate;
import com.aionemu.gameserver.spawnengine.SpawnHandlerType;
import com.aionemu.gameserver.utils.PositionUtil;
import com.aionemu.gameserver.utils.xml.JAXBUtil;
import com.aionemu.gameserver.world.WorldPosition;
import com.aionemu.gameserver.world.WorldType;
/**
* @author xTz, Rolandas, Neon
*/
@XmlRootElement(name = "spawns")
@XmlType(namespace = "", name = "SpawnsData")
@XmlAccessorType(XmlAccessType.NONE)
public class SpawnsData extends AbstractLockManager {
private static final Logger log = LoggerFactory.getLogger(SpawnsData.class);
@XmlElement(name = "spawn_map", type = SpawnMap.class)
private List<SpawnMap> templates;
@XmlTransient
private final Map<Integer, Map<Integer, List<SpawnGroup>>> allSpawnMaps = new HashMap<>();
@XmlTransient
private final Map<Integer, List<SpawnGroup>> baseSpawnMaps = new HashMap<>();
@XmlTransient
private final Map<Integer, List<SpawnGroup>> riftSpawnMaps = new HashMap<>();
@XmlTransient
private final Map<Integer, List<SpawnGroup>> siegeSpawnMaps = new HashMap<>();
@XmlTransient
private final Map<Integer, List<SpawnGroup>> vortexSpawnMaps = new HashMap<>();
@XmlTransient
private final Map<Integer, MercenarySpawn> mercenarySpawns = new HashMap<>();
@XmlTransient
private final Map<Integer, List<SpawnGroup>> ahserionSpawnMaps = new HashMap<>(); // Ahserion's flight
@XmlTransient
private Set<Integer> allNpcIds;
public void afterUnmarshal(Unmarshaller u, Object parent) {
for (SpawnMap map : templates) {
Map<Integer, List<SpawnGroup>> mapSpawns = allSpawnMaps.get(map.getMapId());
writeLock();
try {
if (mapSpawns == null) {
mapSpawns = new HashMap<>();
allSpawnMaps.put(map.getMapId(), mapSpawns);
}
List<Integer> customs = new ArrayList<>();
for (Spawn spawn : map.getSpawns()) {
if (customs.contains(spawn.getNpcId()))
continue;
if (spawn.isCustom() && spawn.getEventTemplate() == null) { // custom event spawns are handled in Event class
mapSpawns.remove(spawn.getNpcId());
customs.add(spawn.getNpcId());
}
List<SpawnGroup> spawnGroups = mapSpawns.get(spawn.getNpcId());
if (spawnGroups == null) {
spawnGroups = new ArrayList<>(1);
mapSpawns.put(spawn.getNpcId(), spawnGroups);
}
spawnGroups.add(new SpawnGroup(map.getMapId(), spawn));
}
} finally {
writeUnlock();
}
for (BaseSpawn baseSpawn : map.getBaseSpawns()) {
int baseId = baseSpawn.getId();
if (!baseSpawnMaps.containsKey(baseId)) {
baseSpawnMaps.put(baseId, new ArrayList<>());
}
for (BaseSpawn.BaseOccupierTemplate simpleRace : baseSpawn.getOccupierTemplates()) {
for (Spawn spawn : simpleRace.getSpawns()) {
SpawnGroup spawnGroup = new SpawnGroup(map.getMapId(), spawn, baseId, simpleRace.getOccupier());
baseSpawnMaps.get(baseId).add(spawnGroup);
}
}
}
for (RiftSpawn rift : map.getRiftSpawns()) {
int id = rift.getId();
if (!riftSpawnMaps.containsKey(id)) {
riftSpawnMaps.put(id, new ArrayList<>());
}
for (Spawn spawn : rift.getSpawns()) {
SpawnGroup spawnGroup = new SpawnGroup(map.getMapId(), spawn, id);
riftSpawnMaps.get(id).add(spawnGroup);
}
}
for (SiegeSpawn SiegeSpawn : map.getSiegeSpawns()) {
int siegeId = SiegeSpawn.getSiegeId();
if (!siegeSpawnMaps.containsKey(siegeId)) {
siegeSpawnMaps.put(siegeId, new ArrayList<>());
}
for (SiegeSpawn.SiegeRaceTemplate race : SiegeSpawn.getSiegeRaceTemplates()) {
for (SiegeSpawn.SiegeRaceTemplate.SiegeModTemplate mod : race.getSiegeModTemplates()) {
if (mod == null || mod.getSpawns() == null) {
continue;
}
for (Spawn spawn : mod.getSpawns()) {
SpawnGroup spawnGroup = new SpawnGroup(map.getMapId(), spawn, siegeId, race.getSiegeRace(), mod.getSiegeModType());
siegeSpawnMaps.get(siegeId).add(spawnGroup);
}
}
}
}
for (VortexSpawn VortexSpawn : map.getVortexSpawns()) {
int id = VortexSpawn.getId();
if (!vortexSpawnMaps.containsKey(id)) {
vortexSpawnMaps.put(id, new ArrayList<>());
}
for (VortexSpawn.VortexStateTemplate type : VortexSpawn.getSiegeModTemplates()) {
if (type == null || type.getSpawns() == null) {
continue;
}
for (Spawn spawn : type.getSpawns()) {
SpawnGroup spawnGroup = new SpawnGroup(map.getMapId(), spawn, id, type.getStateType());
vortexSpawnMaps.get(id).add(spawnGroup);
}
}
}
for (MercenarySpawn mercenarySpawn : map.getMercenarySpawns()) {
int id = mercenarySpawn.getSiegeId();
mercenarySpawns.put(id, mercenarySpawn);
for (MercenaryRace mrace : mercenarySpawn.getMercenaryRaces()) {
for (MercenaryZone mzone : mrace.getMercenaryZones()) {
mzone.setWorldId(map.getMapId());
mzone.setSiegeId(mercenarySpawn.getSiegeId());
}
}
}
for (AhserionsFlightSpawn ahserionSpawn : map.getAhserionSpawns()) {
int teamId = ahserionSpawn.getFaction().ordinal();
if (!ahserionSpawnMaps.containsKey(teamId)) {
ahserionSpawnMaps.put(teamId, new ArrayList<>());
}
for (AhserionsFlightSpawn.AhserionStageSpawnTemplate stageTemplate : ahserionSpawn.getStageSpawnTemplate()) {
if (stageTemplate == null || stageTemplate.getSpawns() == null)
continue;
for (Spawn spawn : stageTemplate.getSpawns()) {
SpawnGroup spawnGroup = new SpawnGroup(map.getMapId(), spawn, stageTemplate.getStage(), ahserionSpawn.getFaction());
ahserionSpawnMaps.get(teamId).add(spawnGroup);
}
}
}
}
allNpcIds = allSpawnMaps.values().stream().flatMap(spawn -> spawn.keySet().stream()).collect(Collectors.toSet());
allNpcIds.addAll(baseSpawnMaps.values().stream().flatMap(group -> group.stream().map(SpawnGroup::getNpcId)).collect(Collectors.toSet()));
allNpcIds.addAll(siegeSpawnMaps.values().stream().flatMap(group -> group.stream().map(SpawnGroup::getNpcId)).collect(Collectors.toSet()));
allNpcIds.addAll(riftSpawnMaps.values().stream().flatMap(group -> group.stream().map(SpawnGroup::getNpcId)).collect(Collectors.toSet()));
allNpcIds.addAll(vortexSpawnMaps.values().stream().flatMap(group -> group.stream().map(SpawnGroup::getNpcId)).collect(Collectors.toSet()));
allNpcIds.addAll(ahserionSpawnMaps.values().stream().flatMap(group -> group.stream().map(SpawnGroup::getNpcId)).collect(Collectors.toSet()));
}
public void clearTemplates() {
templates = null;
}
public List<SpawnGroup> getSpawnsByWorldId(int worldId) {
readLock();
try {
Map<Integer, List<SpawnGroup>> spawnGroupsByNpcId = allSpawnMaps.get(worldId);
if (spawnGroupsByNpcId == null)
return Collections.emptyList();
return spawnGroupsByNpcId.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
} finally {
readUnlock();
}
}
public List<SpawnGroup> getSpawnsForNpc(int worldId, int npcId) {
readLock();
try {
Map<Integer, List<SpawnGroup>> spawnGroupsByNpcId = allSpawnMaps.get(worldId);
List<SpawnGroup> spawnGroups = spawnGroupsByNpcId == null ? null : spawnGroupsByNpcId.get(npcId);
return spawnGroups == null ? Collections.emptyList() : spawnGroups;
} finally {
readUnlock();
}
}
public List<SpawnGroup> getBaseSpawnsByLocId(int id) {
return baseSpawnMaps.get(id);
}
public List<SpawnGroup> getRiftSpawnsByLocId(int id) {
return riftSpawnMaps.get(id);
}
public List<SpawnGroup> getSiegeSpawnsByLocId(int siegeId) {
return siegeSpawnMaps.get(siegeId);
}
public List<SpawnGroup> getVortexSpawnsByLocId(int id) {
return vortexSpawnMaps.get(id);
}
public MercenarySpawn getMercenarySpawnBySiegeId(int id) {
return mercenarySpawns.get(id);
}
public synchronized boolean saveSpawn(VisibleObject visibleObject, boolean delete) {
SpawnTemplate spawn = visibleObject.getSpawn();
if (spawn == null) // some objects like house objects have no spawn template
return false;
if (!spawn.getClass().equals(SpawnTemplate.class)) // do not save special/temporary spawns (siege, base, rift spawn, ...) as world spawns
return false;
if (spawn.getRespawnTime() <= 0) // do not save single time spawns (world raid, handler spawn, ...) as world spawns
return false;
if (spawn.isTemporarySpawn()) // spawn start and end times of temporary world spawns (shugos, agrints, ...) would get lost
return false;
String folder = "./data/static_data/spawns/" + getRelativePath(visibleObject);
String fileName = visibleObject.getWorldId() + "_" + visibleObject.getPosition().getWorldMapInstance().getParent().getName().replace(' ', '_')
+ ".xml";
File xml = new File(folder + "/New/" + fileName);
String schema = "./data/static_data/spawns/spawns.xsd";
SpawnsData data = xml.isFile() ? JAXBUtil.deserialize(xml, SpawnsData.class, schema) : new SpawnsData();
SpawnMap spawnMap = data.templates == null ? null
: data.templates.stream().filter(m -> m.getMapId() == visibleObject.getWorldId()).findFirst().orElse(null);
if (spawnMap == null) {
spawnMap = new SpawnMap(visibleObject.getWorldId());
if (data.templates == null)
data.templates = Collections.singletonList(spawnMap);
else
data.templates.add(spawnMap);
}
Spawn oldGroup = findSpawnTemplate(spawnMap, spawn, delete); // find in new file
if (oldGroup == null) {
oldGroup = loadSpawnsFromTemplateFiles(folder, schema, spawn, delete); // load from old files
if (oldGroup != null)
spawnMap.getSpawns().add(oldGroup);
}
if (oldGroup == null) {
oldGroup = new Spawn(spawn.getNpcId(), spawn.getRespawnTime(), spawn.getHandlerType());
spawnMap.getSpawns().add(oldGroup);
}
oldGroup.setCustom(true);
SpawnSpotTemplate spot = new SpawnSpotTemplate(visibleObject.getX(), visibleObject.getY(), visibleObject.getZ(), visibleObject.getHeading(),
visibleObject.getSpawn().getRandomWalkRange(), visibleObject.getSpawn().getWalkerId(), visibleObject.getSpawn().getWalkerIndex());
int oldSpotIndex = -1;
for (int i = 0; i < oldGroup.getSpawnSpotTemplates().size(); i++) {
SpawnSpotTemplate s = oldGroup.getSpawnSpotTemplates().get(i);
if (positionMatches(spawn, s)) {
oldSpotIndex = i;
break;
}
}
if (oldSpotIndex >= 0) {
if (delete)
oldGroup.getSpawnSpotTemplates().remove(oldSpotIndex);
else
oldGroup.getSpawnSpotTemplates().set(oldSpotIndex, spot);
} else if (!delete)
oldGroup.getSpawnSpotTemplates().add(spot);
xml.getParentFile().mkdir();
try {
Files.writeString(xml.toPath(), JAXBUtil.serialize(data, schema));
} catch (Exception e) {
log.error("Could not save XML file!", e);
return false;
}
// update spawn coords at the end, because we need previous coords above to find the old spawn template
spawn.setX(spot.getX());
spawn.setY(spot.getY());
spawn.setZ(spot.getZ());
spawn.setHeading(spot.getHeading());
templates = data.templates;
afterUnmarshal(null, null);
clearTemplates();
return true;
}
private Spawn loadSpawnsFromTemplateFiles(String folder, String schema, SpawnTemplate spawn, boolean exactMatch) {
AtomicReference<Spawn> match = new AtomicReference<>();
try {
Files.walkFileTree(Paths.get(folder), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isRegularFile() && file.toString().toLowerCase().endsWith(".xml")) {
for (SpawnMap spawnMap : JAXBUtil.deserialize(file.toFile(), SpawnsData.class, schema).templates) {
Spawn s = findSpawnTemplate(spawnMap, spawn, exactMatch);
if (s != null) {
match.set(s);
return FileVisitResult.TERMINATE;
}
}
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
log.error("", e);
}
return match.get();
}
private Spawn findSpawnTemplate(SpawnMap spawnMap, SpawnTemplate spawn, boolean exactMatch) {
if (spawnMap.getMapId() != spawn.getWorldId())
return null;
return spawnMap.getSpawns().stream()
.filter(
s -> s.getNpcId() == spawn.getNpcId() && (!exactMatch || s.getSpawnSpotTemplates().stream().anyMatch(spot -> positionMatches(spawn, spot))))
.findFirst().orElse(null);
}
private boolean positionMatches(SpawnTemplate spawn, SpawnSpotTemplate spawnSpotTemplate) {
return spawnSpotTemplate.getX() == spawn.getX() && spawnSpotTemplate.getY() == spawn.getY() && spawnSpotTemplate.getZ() == spawn.getZ()
&& spawnSpotTemplate.getHeading() == spawn.getHeading();
}
private static String getRelativePath(VisibleObject visibleObject) {
if (visibleObject.getSpawn().getHandlerType() == SpawnHandlerType.RIFT)
return "Rifts";
else if (visibleObject instanceof Gatherable)
return "Gather";
else if (visibleObject.getPosition().getWorldMapInstance().getParent().isInstanceType())
return "Instances";
else
return "Npcs";
}
public int size() {
return allSpawnMaps.size();
}
/**
* first search: current map
* second search: all maps of players race
* third search: all other maps
*/
public SpawnSearchResult getNearestSpawnByNpcId(Player player, int npcId, int worldId) {
List<SpawnGroup> spawns = getSpawnsForNpc(worldId, npcId);
if (spawns.isEmpty()) { // -> there are no spawns for this npcId on the current map
// search all maps of players race
for (WorldMapTemplate template : DataManager.WORLD_MAPS_DATA) {
if (template.getMapId() == worldId)
continue;
if ((template.getWorldType() == WorldType.ELYSEA && player.getRace() == Race.ELYOS)
|| (template.getWorldType() == WorldType.ASMODAE && player.getRace() == Race.ASMODIANS)) {
spawns = getSpawnsForNpc(template.getMapId(), npcId);
if (!spawns.isEmpty()) {
worldId = template.getMapId();
break;
}
}
}
// -> there are no spawns for this npcId on all maps of players race
// search all other maps
if (spawns.isEmpty()) {
for (WorldMapTemplate template : DataManager.WORLD_MAPS_DATA) {
if ((template.getMapId() == worldId) || (template.getWorldType() == WorldType.ELYSEA && player.getRace() == Race.ELYOS)
|| (template.getWorldType() == WorldType.ASMODAE && player.getRace() == Race.ASMODIANS)) {
continue;
}
spawns = getSpawnsForNpc(template.getMapId(), npcId);
if (!spawns.isEmpty()) {
worldId = template.getMapId();
break;
}
}
}
}
return getNearestSpawn((player != null ? player.getPosition() : null), spawns, worldId);
}
private SpawnSearchResult getNearestSpawn(WorldPosition position, List<SpawnGroup> spawnGroups, int worldId) {
if (position == null || spawnGroups.isEmpty()) {
return null;
}
if (worldId != position.getMapId()) {
SpawnGroup spawnGroup = spawnGroups.get(0);
return spawnGroup.getSpawnTemplates().isEmpty() ? null : toSpawnSearchResult(worldId, spawnGroup.getSpawnTemplates().get(0));
}
SpawnTemplate temp = null;
float distance = 0;
outerLoop:
for (SpawnGroup spawnGroup : spawnGroups) {
for (SpawnTemplate spot : spawnGroup.getSpawnTemplates()) {
if (temp == null) {
temp = spot;
distance = (float) PositionUtil.getDistance(position.getX(), position.getY(), position.getZ(), spot.getX(), spot.getY(), spot.getZ());
if (distance <= 1f)
break outerLoop;
} else {
float dist = (float) PositionUtil.getDistance(position.getX(), position.getY(), position.getZ(), spot.getX(), spot.getY(), spot.getZ());
if (dist < distance) {
distance = dist;
temp = spot;
if (distance <= 1f)
break outerLoop;
}
}
}
}
return temp == null ? null : toSpawnSearchResult(worldId, temp);
}
private SpawnSearchResult toSpawnSearchResult(int worldId, SpawnTemplate spot) {
return new SpawnSearchResult(worldId, new SpawnSpotTemplate(spot.getX(), spot.getY(), spot.getZ(), spot.getHeading(), spot.getRandomWalkRange(),
spot.getWalkerId(), spot.getWalkerIndex()));
}
/**
* @param worldId
* Optional. If provided, searches in this world first
* @param npcId
* @return template for the spot
*/
public SpawnSearchResult getFirstSpawnByNpcId(int worldId, int npcId) {
List<SpawnGroup> spawnGroups = getSpawnsForNpc(worldId, npcId);
if (spawnGroups.isEmpty()) {
for (WorldMapTemplate template : DataManager.WORLD_MAPS_DATA) {
if (template.getMapId() == worldId)
continue;
spawnGroups = getSpawnsForNpc(template.getMapId(), npcId);
if (!spawnGroups.isEmpty()) {
worldId = template.getMapId();
break;
}
}
if (spawnGroups.isEmpty())
return null;
}
List<SpawnTemplate> spawnSpots = spawnGroups.get(0).getSpawnTemplates();
return spawnSpots.isEmpty() ? null : toSpawnSearchResult(worldId, spawnSpots.get(0));
}
/**
* Used by Event Service to add additional spawns
*
* @param spawnMap
* templates to add
*/
public void addNewSpawnMap(SpawnMap spawnMap) {
if (templates == null)
templates = new ArrayList<>();
templates.add(spawnMap);
}
public void removeEventSpawnObjects(EventTemplate eventTemplate) {
writeLock();
try {
allSpawnMaps.values().forEach(spawnGroupsByNpcId -> {
Collection<List<SpawnGroup>> allSpawnGroups = spawnGroupsByNpcId.values();
allSpawnGroups.forEach(spawnGroups -> spawnGroups.removeIf(spawnGroup -> eventTemplate.equals(spawnGroup.getEventTemplate())));
allSpawnGroups.removeIf(List::isEmpty);
});
} finally {
writeUnlock();
}
}
public List<SpawnMap> getTemplates() {
return templates;
}
public List<SpawnGroup> getAhserionSpawnByTeamId(int id) {
return ahserionSpawnMaps.get(id);
}
/**
* @return All npc ids which appear in the spawn templates.
*/
public Set<Integer> getAllNpcIds() {
return allNpcIds;
}
/**
* @param npcId
* @return True, if the given npc appears in any of the spawn templates (world, instance, siege, base, ...)
*/
public boolean containsAnySpawnForNpc(int npcId) {
return allNpcIds.contains(npcId);
}
}