/*********************************************************************************** * 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 . * * * ***********************************************************************************/ 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 > quadTree; private final Map 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(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 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 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 objectAware = new HashSet(); QuadTree tree = getTree(l); List 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 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; } }