package com.aionemu.gameserver.spawnengine;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aionemu.gameserver.ai.AILogger;
import com.aionemu.gameserver.ai.AIState;
import com.aionemu.gameserver.ai.AISubState;
import com.aionemu.gameserver.ai.NpcAI;
import com.aionemu.gameserver.ai.manager.WalkManager;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.templates.spawns.SpawnTemplate;
import com.aionemu.gameserver.model.templates.walker.RouteStep;
import com.aionemu.gameserver.model.templates.zone.Point2D;
/**
* @author vlog, Rolandas
*/
public class WalkerGroup {
private static final Logger log = LoggerFactory.getLogger(WalkerGroup.class);
private List<ClusteredNpc> members;
private WalkerGroupType type;
private final float walkerXpos;
private final float walkerYpos;
private int[] memberSteps;
private volatile int groupStep;
private final String versionId;
private boolean isSpawned;
public WalkerGroup(List<ClusteredNpc> members) {
this.members = members.stream().sorted(Comparator.comparing(ClusteredNpc::getWalkerIndex, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
memberSteps = new int[members.size()];
walkerXpos = members.get(0).getX();
walkerYpos = members.get(0).getY();
type = members.get(0).getWalkTemplate().getType();
versionId = members.get(0).getWalkTemplate().getVersionId();
}
public void form() {
if (getWalkType() == WalkerGroupType.SQUARE) {
int[] rows = members.get(0).getWalkTemplate().getRows();
if (IntStream.of(rows).sum() != members.size()) {
log.warn("Invalid row sizes for walk cluster " + members.get(0).getWalkTemplate().getRouteId());
}
if (rows.length == 1) {
// Line formation: distance 2 meters from each other (divide by 2 and multiple by 2)
// negative at left hand and positive at the right hand
float bounds = (float) members.stream().mapToDouble(cNpc -> cNpc.getNpc().getObjectTemplate().getBoundRadius().getSide()).sum();
float distance = (1 - members.size()) / 2f * (WalkerGroupShift.DISTANCE + bounds);
Point2D origin = new Point2D(walkerXpos, walkerYpos);
Point2D destination = new Point2D(members.get(0).getWalkTemplate().getRouteStep(1).getX(),
members.get(0).getWalkTemplate().getRouteStep(1).getY());
for (int i = 0; i < members.size(); i++, distance += WalkerGroupShift.DISTANCE) {
WalkerGroupShift shift = new WalkerGroupShift(distance, 0);
Point2D loc = getLinePoint(origin, destination, shift);
members.get(i).setX(loc.getX());
members.get(i).setY(loc.getY());
Npc member = members.get(i).getNpc();
member.setWalkerGroup(this);
member.setWalkerGroupShift(shift);
// distance += npc.getObjectTemplate().getBoundRadius().getSide();
}
} else if (rows.length != 0) {
float rowDistances[] = new float[rows.length - 1];
float coronalDist = 0;
for (int i = 0; i < rows.length - 1; i++) {
if (rows[i] % 2 != rows[i + 1] % 2)
rowDistances[i] = 0.86602540378443864676372317075294f * WalkerGroupShift.DISTANCE;
else
rowDistances[i] = WalkerGroupShift.DISTANCE;
coronalDist -= rowDistances[i];
}
Point2D origin = new Point2D(walkerXpos, walkerYpos);
Point2D destination = new Point2D(members.get(0).getWalkTemplate().getRouteStep(1).getX(),
members.get(0).getWalkTemplate().getRouteStep(1).getY());
int index = 0;
for (int i = 0; i < rows.length; i++) {
float sagittalDist = (1 - rows[i]) / 2f * WalkerGroupShift.DISTANCE;
for (int j = 0; j < rows[i]; j++, sagittalDist += WalkerGroupShift.DISTANCE) {
if (index > members.size() - 1)
break;
WalkerGroupShift shift = new WalkerGroupShift(sagittalDist, coronalDist);
Point2D loc = getLinePoint(origin, destination, shift);
ClusteredNpc cnpc = members.get(index++);
cnpc.setX(loc.getX());
cnpc.setY(loc.getY());
cnpc.getNpc().setWalkerGroup(this);
cnpc.getNpc().setWalkerGroupShift(shift);
}
if (i < rows.length - 1)
coronalDist += rowDistances[i];
}
}
} else if (getWalkType() == WalkerGroupType.CIRCLE) {
// TODO: if needed
} else if (getWalkType() == WalkerGroupType.POINT) {
log.warn("No formation specified for walk cluster " + members.get(0).getWalkTemplate().getRouteId());
}
}
@SuppressWarnings("unused")
private float getSidesExtra(int[] rows, int startIndex, int endIndex) {
return 0;
}
/**
* Returns coordinates of NPC in 2D from the initial spawn location
*
* @param origin
* - initial spawn location
* @param destination
* - point of next move
* @param shift
* - distance from origin located in lines perpendicular to destination; for SagittalShift if negative then located to the left from
* origin, otherwise, to the right for CoronalShift if negative then located to back, otherwise to the front
*/
public static Point2D getLinePoint(Point2D origin, Point2D destination, WalkerGroupShift shift) {
// TODO: implement angle shift
WalkerGroupShift dir = getShiftSigns(origin, destination);
Point2D result = null;
if (origin.getY() - destination.getY() == 0) {
return new Point2D(origin.getX() + dir.getCoronalShift() * shift.getCoronalShift(),
origin.getY() - dir.getSagittalShift() * shift.getSagittalShift());
} else if (origin.getX() - destination.getX() == 0) {
return new Point2D(origin.getX() + dir.getCoronalShift() * shift.getSagittalShift(),
origin.getY() + dir.getCoronalShift() * shift.getCoronalShift());
} else {
double slope = (origin.getX() - destination.getX()) / (origin.getY() - destination.getY());
double dx = Math.abs(shift.getSagittalShift()) / Math.sqrt(1 + slope * slope);
if (shift.getSagittalShift() * dir.getCoronalShift() < 0)
result = new Point2D((float) (origin.getX() - dx), (float) (origin.getY() + dx * slope));
else
result = new Point2D((float) (origin.getX() + dx), (float) (origin.getY() - dx * slope));
}
if (shift.getCoronalShift() != 0) {
Point2D rotatedShift = null;
if (shift.getSagittalShift() != 0) {
rotatedShift = getLinePoint(origin, destination,
new WalkerGroupShift(Math.signum(shift.getSagittalShift()) * Math.abs(shift.getCoronalShift()), 0));
} else {
rotatedShift = getLinePoint(origin, destination, new WalkerGroupShift(Math.abs(shift.getCoronalShift()), 0));
}
// since it's rotated, and perpendicular, dx and dy are reciprocal when not rotated
float dx = Math.abs(origin.getX() - rotatedShift.getX());
float dy = Math.abs(origin.getY() - rotatedShift.getY());
if (shift.getCoronalShift() < 0) {
if (dir.getSagittalShift() < 0 && dir.getCoronalShift() < 0) {
result = new Point2D(result.getX() + dy, result.getY() + dx);
} else if (dir.getSagittalShift() > 0 && dir.getCoronalShift() > 0) {
result = new Point2D(result.getX() - dy, result.getY() - dx);
} else if (dir.getSagittalShift() < 0 && dir.getCoronalShift() > 0) {
result = new Point2D(result.getX() + dy, result.getY() - dx);
} else if (dir.getSagittalShift() > 0 && dir.getCoronalShift() < 0) {
result = new Point2D(result.getX() - dy, result.getY() + dx);
}
} else {
if (dir.getSagittalShift() < 0 && dir.getCoronalShift() < 0) {
result = new Point2D(result.getX() - dy, result.getY() - dx);
} else if (dir.getSagittalShift() > 0 && dir.getCoronalShift() > 0) {
result = new Point2D(result.getX() + dy, result.getY() + dx);
} else if (dir.getSagittalShift() < 0 && dir.getCoronalShift() > 0) {
result = new Point2D(result.getX() - dy, result.getY() + dx);
} else if (dir.getSagittalShift() > 0 && dir.getCoronalShift() < 0) {
result = new Point2D(result.getX() + dy, result.getY() - dx);
}
}
}
return result;
}
/*
* Return a normalized direction vector
*/
private static WalkerGroupShift getShiftSigns(Point2D origin, Point2D destination) {
float dx = Math.signum(destination.getX() - origin.getX());
float dy = Math.signum(destination.getY() - origin.getY());
return new WalkerGroupShift(dx, dy);
}
public void setStep(Npc member, int step) {
int currentStep = 0;
for (int i = 0; i < members.size(); i++) {
if (memberSteps[i] > currentStep)
currentStep = memberSteps[i];
if (members.get(i).getNpc().equals(member)) {
AILogger.info(members.get(i).getNpc().getAi(), "Setting step to " + step);
memberSteps[i] = step;
}
}
if (step > currentStep || step == 0)
groupStep = step;
}
public void targetReached(NpcAI npcAI) {
synchronized (members) {
npcAI.setSubStateIfNot(AISubState.WALK_WAIT_GROUP);
boolean allArrived = true;
for (ClusteredNpc snpc : members) {
Npc npc = snpc.getNpc();
allArrived &= npc.isDead() || npc.getAi().getSubState() == AISubState.WALK_WAIT_GROUP;
if (!allArrived)
break;
}
for (int i = 0; i < members.size(); i++) {
ClusteredNpc snpc = members.get(i);
if ((memberSteps[i] == groupStep) && !allArrived) {
npcAI.getOwner().getMoveController().abortMove();
npcAI.setStateIfNot(AIState.WALKING);
npcAI.setSubStateIfNot(AISubState.WALK_WAIT_GROUP);
continue;
}
npcAI = (NpcAI) (snpc.getNpc().getAi());
if (npcAI.getSubState() == AISubState.WALK_WAIT_GROUP)
WalkManager.targetReached(npcAI);
}
}
}
public boolean isSpawned() {
return isSpawned;
}
public void spawn() {
for (ClusteredNpc snpc : members) {
float height = getHeight(snpc.getX(), snpc.getY(), snpc.getNpc().getSpawn());
snpc.spawn(height);
}
isSpawned = true;
}
public void respawn(Npc npc) {
for (int index = 0; index < members.size(); index++) {
ClusteredNpc snpc = members.get(index);
if (snpc.getNpc().getNpcId() == npc.getNpcId() && (npc.getSpawn().getWalkerIndex() == null && snpc.getNpc().isDead()
|| npc.getSpawn().getWalkerIndex() != null && npc.getSpawn().getWalkerIndex() == snpc.getWalkerIndex())) {
synchronized (members) {
memberSteps[index] = Math.max(0, groupStep - 1);
RouteStep step = snpc.getWalkTemplate().getRouteStep(memberSteps[index]);
npc.getMoveController().setWalkerTemplate(snpc.getWalkTemplate(), memberSteps[index]);
snpc.setNpc(npc, step);
snpc.spawn(step.getZ());
}
break;
}
}
}
public void despawn() {
for (ClusteredNpc snpc : members) {
snpc.despawn();
// reset positions
form();
for (int index = 0; index < memberSteps.length; index++)
memberSteps[index] = 0;
groupStep = 0;
}
isSpawned = false;
}
public ClusteredNpc getClusterData(Npc npc) {
for (ClusteredNpc snpc : members) {
if (snpc.getNpc().equals(npc))
return snpc;
}
return null;
}
private float getHeight(float x, float y, SpawnTemplate template) {
/*
* if (GeoService.getInstance().isGeoOn()) { return GeoService.getInstance().getZ(template.getWorldId(), x, y, z, ); }
*/
return template.getZ();
}
public int getPool() {
return members.size();
}
public WalkerGroupType getWalkType() {
return type;
}
public boolean isLinearlyPositioned(Npc npc) {
if (type != WalkerGroupType.SQUARE)
return false;
for (ClusteredNpc snpc : members) {
if (snpc.getNpc().equals(npc))
return snpc.getWalkTemplate().getRows().length == 1;
}
return false;
}
public int getGroupStep() {
return groupStep;
}
public String getVersionId() {
return versionId;
}
}