Files
Holocore/src/services/objects/ObjectAwareness.java
T

398 lines
15 KiB
Java

/***********************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore 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 Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package services.objects;
import intents.PlayerEventIntent;
import intents.RequestZoneInIntent;
import intents.network.GalacticPacketIntent;
import intents.object.DestroyObjectIntent;
import intents.object.MoveObjectIntent;
import intents.object.ObjectCreatedIntent;
import intents.object.ObjectTeleportIntent;
import intents.player.PlayerTransformedIntent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import main.ProjectSWG;
import network.packets.Packet;
import network.packets.swg.zone.CmdSceneReady;
import network.packets.swg.zone.object_controller.DataTransform;
import network.packets.swg.zone.object_controller.DataTransformWithParent;
import resources.Location;
import resources.Terrain;
import resources.buildout.BuildoutArea;
import resources.control.Intent;
import resources.control.Service;
import resources.objects.SWGObject;
import resources.objects.SWGObject.ObjectClassification;
import resources.objects.creature.CreatureObject;
import resources.objects.quadtree.QuadTree;
import resources.objects.tangible.TangibleObject;
import resources.player.Player;
import resources.server_info.Log;
public class ObjectAwareness extends Service {
private static final double AWARE_RANGE = 1024;
private static final int DEFAULT_LOAD_RANGE = (int) square(200); // Squared for speed
private static final Location GONE_LOCATION = new Location(0, 0, 0, null);
private final Map <Terrain, QuadTree <SWGObject>> quadTree;
private final Map<Long, Location> locations;
public ObjectAwareness() {
quadTree = new HashMap<>();
locations = new HashMap<>();
registerForIntent(PlayerEventIntent.TYPE);
registerForIntent(ObjectCreatedIntent.TYPE);
registerForIntent(DestroyObjectIntent.TYPE);
registerForIntent(ObjectTeleportIntent.TYPE);
registerForIntent(GalacticPacketIntent.TYPE);
registerForIntent(MoveObjectIntent.TYPE);
loadQuadTree();
}
@Override
public void onIntentReceived(Intent i) {
switch (i.getType()) {
case PlayerEventIntent.TYPE:
if (i instanceof PlayerEventIntent)
handlePlayerEventIntent((PlayerEventIntent) i);
break;
case ObjectCreatedIntent.TYPE:
if (i instanceof ObjectCreatedIntent)
handleObjectCreatedIntent((ObjectCreatedIntent) i);
break;
case DestroyObjectIntent.TYPE:
if (i instanceof DestroyObjectIntent)
handleDestroyObjectIntent((DestroyObjectIntent) i);
break;
case ObjectTeleportIntent.TYPE:
if (i instanceof ObjectTeleportIntent)
processObjectTeleportIntent((ObjectTeleportIntent) i);
break;
case GalacticPacketIntent.TYPE:
if (i instanceof GalacticPacketIntent)
processGalacticPacketIntent((GalacticPacketIntent) i);
break;
case MoveObjectIntent.TYPE:
if (i instanceof MoveObjectIntent)
processMoveObjectIntent((MoveObjectIntent) i);
break;
default:
break;
}
}
private void handlePlayerEventIntent(PlayerEventIntent pei) {
Player p = pei.getPlayer();
CreatureObject creature = p.getCreatureObject();
if (creature == null)
return;
switch (pei.getEvent()) {
case PE_DISAPPEAR:
moveToLocation(creature, creature.getParent(), null);
creature.setOwner(null);
break;
case PE_ZONE_IN_SERVER:
creature.resetAwareness();
moveToLocation(creature, creature.getParent(), creature.getLocation());
p.sendPacket(new CmdSceneReady());
break;
default:
break;
}
}
private void loadQuadTree() {
for (Terrain t : Terrain.values()) {
quadTree.put(t, new QuadTree<SWGObject>(16, -8192, -8192, 8192, 8192));
}
}
private void handleObjectCreatedIntent(ObjectCreatedIntent oci) {
SWGObject object = oci.getObject();
if (isInAwareness(object))
moveToLocation(object, object.getLocation());
}
private void handleDestroyObjectIntent(DestroyObjectIntent doi) {
SWGObject obj = doi.getObject();
obj.setLocation(GONE_LOCATION);
obj.moveToContainer(null);
obj.clearAware();
obj.clearCustomAware(true);
}
private void processObjectTeleportIntent(ObjectTeleportIntent oti) {
SWGObject object = oti.getObject();
Player owner = object.getOwner();
object.setLocation(oti.getNewLocation());
if (oti.getParent() != null) {
moveToLocation(object, oti.getParent(), null);
} else {
moveToLocation(object, null);
}
if (object instanceof CreatureObject && ((CreatureObject) object).isLoggedInPlayer())
new RequestZoneInIntent(owner, (CreatureObject) object, false).broadcast();
}
private void processGalacticPacketIntent(GalacticPacketIntent i) {
Packet packet = i.getPacket();
if (packet instanceof DataTransform) {
DataTransform trans = (DataTransform) packet;
SWGObject obj = i.getObjectManager().getObjectById(trans.getObjectId());
if (obj instanceof CreatureObject)
moveObject((CreatureObject) obj, trans);
} else if (packet instanceof DataTransformWithParent) {
DataTransformWithParent transformWithParent = (DataTransformWithParent) packet;
SWGObject object = i.getObjectManager().getObjectById(transformWithParent.getObjectId());
SWGObject parent = i.getObjectManager().getObjectById(transformWithParent.getCellId());
if (object instanceof CreatureObject)
moveObject((CreatureObject) object, parent, transformWithParent);
}
}
private void processMoveObjectIntent(MoveObjectIntent i) {
if (i.getParent() != null)
processMoveObjectIntentParent(i);
else
processMoveObjectIntentNoParent(i);
}
private void processMoveObjectIntentNoParent(MoveObjectIntent i) {
SWGObject obj = i.getObject();
Location newLocation = i.getNewLocation();
BuildoutArea area = obj.getBuildoutArea();
if (area != null)
newLocation = area.adjustLocation(newLocation);
moveToLocation(obj, newLocation);
if (area != null)
newLocation = area.readjustLocation(newLocation);
DataTransform transform = new DataTransform(obj.getObjectId());
transform.setTimestamp((int) ProjectSWG.getGalacticTime());
transform.setLocation(newLocation);
transform.setLookAtYaw(0);
transform.setUseLookAtYaw(false);
transform.setSpeed((float) i.getSpeed());
transform.setUpdateCounter(i.getUpdateCounter());
obj.sendDataTransforms(transform);
}
private void processMoveObjectIntentParent(MoveObjectIntent i) {
SWGObject obj = i.getObject();
moveToLocation(obj, i.getParent(), i.getNewLocation());
DataTransformWithParent transform = new DataTransformWithParent(obj.getObjectId());
transform.setTimestamp((int) ProjectSWG.getGalacticTime());
transform.setLocation(i.getNewLocation());
transform.setLookAtYaw(0);
transform.setUseLookAtYaw(false);
transform.setSpeed((float) i.getSpeed());
transform.setUpdateCounter(i.getUpdateCounter());
obj.sendParentDataTransforms(transform);
}
private void moveObject(CreatureObject obj, DataTransform transform) {
transform = new DataTransform(transform);
Location newLocation = transform.getLocation();
newLocation.setTerrain(obj.getTerrain());
moveObjectSpeedChecks(obj, newLocation);
BuildoutArea area = obj.getBuildoutArea();
if (area != null)
newLocation = area.adjustLocation(newLocation);
new PlayerTransformedIntent(obj, obj.getParent(), null, obj.getLocation(), newLocation).broadcast();
moveToLocation(obj, newLocation);
if (area != null)
newLocation = area.readjustLocation(newLocation);
obj.sendDataTransforms(transform);
}
private void moveObject(CreatureObject obj, SWGObject parent, DataTransformWithParent transformWithParent) {
Location newLocation = transformWithParent.getLocation();
newLocation.setTerrain(obj.getTerrain());
if (parent == null) {
Log.e("ObjectManager", "Could not find parent for transform! Cell: %d Object: %s", transformWithParent.getCellId(), obj);
return;
}
moveObjectSpeedChecks(obj, parent, newLocation);
new PlayerTransformedIntent((CreatureObject) obj, obj.getParent(), parent, obj.getLocation(), newLocation).broadcast();
moveToLocation(obj, parent, newLocation);
obj.sendParentDataTransforms(transformWithParent);
}
private void moveObjectSpeedChecks(CreatureObject obj, Location newLocation) {
double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000;
obj.updateLastTransformTime();
Location l = obj.getWorldLocation();
double speed = Math.sqrt(square(l.getX()-newLocation.getX()) + square(l.getZ()-newLocation.getZ())) / time;
if (speed > obj.getMovementScale()*7.3) {
double angle = (newLocation.getX() == l.getX() ? 0 : Math.atan2(newLocation.getZ()-l.getZ(), newLocation.getX()-l.getX()));
newLocation.setX(l.getX()+obj.getMovementScale()*7.3*time*Math.cos(angle));
newLocation.setZ(l.getZ()+obj.getMovementScale()*7.3*time*Math.sin(angle));
}
}
private void moveObjectSpeedChecks(CreatureObject obj, SWGObject parent, Location newLocation) {
double time = ((CreatureObject) obj).getTimeSinceLastTransform() / 1000;
obj.updateLastTransformTime();
Location l = obj.getWorldLocation();
Location nWorld = new Location(newLocation.getX(), 0, newLocation.getZ(), parent.getTerrain());
nWorld.translateLocation(parent.getWorldLocation());
double speed = Math.sqrt(square(l.getX()-nWorld.getX()) + square(l.getZ()-nWorld.getZ())) / time;
if (speed > obj.getMovementScale()*7.3) {
double angle = (nWorld.getX() == l.getX() ? 0 : Math.atan2(nWorld.getZ()-l.getZ(), nWorld.getX()-l.getX())) + Math.PI;
newLocation.setX(newLocation.getX()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.cos(angle)));
newLocation.setZ(newLocation.getZ()+obj.getMovementScale()*7.3*time*invertNormalizedValue(Math.sin(angle)));
}
}
private void moveToLocation(SWGObject object, Location l) {
moveToLocation(object, null, l);
}
private void moveToLocation(SWGObject object, SWGObject parent, Location l) {
remove(object);
if (parent != object.getParent())
object.moveToContainer(parent);
if (l != null) {
object.setLocation(l);
if (isInAwareness(object)) {
add(object);
update(object);
}
} else {
object.clearAware();
}
}
/**
* Adds the specified object to the awareness quadtree
* @param object the object to add
*/
private void add(SWGObject object) {
Location l = object.getLocation();
if (isInMap(object)) {
synchronized (locations) {
locations.put(object.getObjectId(), l);
}
}
QuadTree <SWGObject> tree = getTree(l);
synchronized (tree) {
tree.put(l.getX(), l.getZ(), object);
}
}
/**
* Removes the specified object from the awareness quadtree
* @param object the object to remove
*/
private boolean remove(SWGObject object) {
Location l;
if (isInMap(object)) {
synchronized (locations) {
l = locations.remove(object.getObjectId());
}
} else {
l = object.getLocation();
}
if (l == null)
return false;
QuadTree <SWGObject> tree = getTree(l);
synchronized (tree) {
return tree.remove(l.getX(), l.getZ(), object);
}
}
/**
* Updates the specified object after it has been moved, or to verify that
* the awareness is up to date
* @param obj the object to update
*/
private void update(SWGObject obj) {
if (!obj.isGenerated())
return;
Location l = obj.getWorldLocation();
Set <SWGObject> objectAware = new HashSet<SWGObject>();
QuadTree <SWGObject> tree = getTree(l);
List <SWGObject> range;
synchronized (tree) {
range = tree.getWithinRange(l.getX(), l.getZ(), AWARE_RANGE);
}
for (SWGObject inRange : range) {
if (isValidInRange(obj, inRange, l))
objectAware.add(inRange);
}
obj.updateObjectAwareness(objectAware);
}
private QuadTree <SWGObject> getTree(Location l) {
return quadTree.get(l.getTerrain());
}
private boolean isValidInRange(SWGObject obj, SWGObject inRange, Location objLoc) {
if (inRange.getObjectId() == obj.getObjectId())
return false;
if (inRange instanceof CreatureObject && ((CreatureObject) inRange).isLoggedOutPlayer())
return false;
int distSquared = distanceSquared(objLoc, inRange.getWorldLocation());
int loadSquared = (int) (square(inRange.getLoadRange()) + 0.5);
return (loadSquared != 0 || distSquared <= DEFAULT_LOAD_RANGE) && (loadSquared == 0 || distSquared <= loadSquared);
}
private boolean isInAwareness(SWGObject object) {
if (object instanceof CreatureObject && ((CreatureObject) object).isLoggedOutPlayer())
return false;
if (object.getTerrain() == null)
return false;
return object.getParent() == null && object instanceof TangibleObject;
}
private boolean isInMap(SWGObject object) {
return object.getClassification() == ObjectClassification.GENERATED;
}
private double invertNormalizedValue(double x) {
if (x < 0)
return -1 - x;
return 1-x;
}
private int distanceSquared(Location l1, Location l2) {
return (int) (square(l1.getX()-l2.getX()) + square(l1.getY()-l2.getY()) + square(l1.getZ()-l2.getZ()) + 0.5);
}
private static double square(double x) {
return x * x;
}
}