/*********************************************************************************** * 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 com.projectswg.common.control.Intent; import com.projectswg.common.control.Service; import com.projectswg.common.data.location.Location; import com.projectswg.common.data.location.Point3D; import com.projectswg.common.data.location.Terrain; import com.projectswg.common.debug.Assert; import com.projectswg.common.debug.Log; import intents.PlayerEventIntent; import intents.RequestZoneInIntent; import intents.network.CloseConnectionIntent; import intents.network.GalacticPacketIntent; import intents.object.ContainerTransferIntent; import intents.object.DestroyObjectIntent; import intents.object.ForceAwarenessUpdateIntent; import intents.object.MoveObjectIntent; import intents.object.ObjectCreatedIntent; import intents.object.ObjectTeleportIntent; import intents.player.PlayerTransformedIntent; import main.ProjectSWG; import network.packets.Packet; import network.packets.swg.zone.CmdSceneReady; import network.packets.swg.zone.HeartBeat; import network.packets.swg.zone.ParametersMessage; import network.packets.swg.zone.chat.ChatOnConnectAvatar; import network.packets.swg.zone.chat.VoiceChatStatus; import network.packets.swg.zone.insertion.ChatServerStatus; import network.packets.swg.zone.insertion.CmdStartScene; import network.packets.swg.zone.object_controller.DataTransform; import network.packets.swg.zone.object_controller.DataTransformWithParent; import resources.config.ConfigFile; import resources.network.DisconnectReason; import resources.objects.SWGObject; import resources.objects.awareness.AwarenessHandler; import resources.objects.awareness.DataTransformHandler; import resources.objects.awareness.TerrainMap.TerrainMapCallback; import resources.objects.creature.CreatureObject; import resources.objects.creature.CreatureState; import resources.player.Player; import resources.player.PlayerEvent; import resources.player.PlayerState; import resources.server_info.DataManager; public class ObjectAwareness extends Service implements TerrainMapCallback { private final AwarenessHandler awarenessHandler; private final DataTransformHandler dataTransformHandler; public ObjectAwareness() { awarenessHandler = new AwarenessHandler(this); dataTransformHandler = new DataTransformHandler(); dataTransformHandler.setSpeedCheck(DataManager.getConfig(ConfigFile.FEATURES).getBoolean("SPEED-HACK-CHECK", true)); registerForIntent(PlayerEventIntent.class, pei -> handlePlayerEventIntent(pei)); registerForIntent(ObjectCreatedIntent.class, oci -> handleObjectCreatedIntent(oci)); registerForIntent(DestroyObjectIntent.class, doi -> handleDestroyObjectIntent(doi)); registerForIntent(ObjectTeleportIntent.class, oti -> processObjectTeleportIntent(oti)); registerForIntent(GalacticPacketIntent.class, gpi -> processGalacticPacketIntent(gpi)); registerForIntent(MoveObjectIntent.class, moi -> processMoveObjectIntent(moi)); registerForIntent(ContainerTransferIntent.class, cti -> processContainerTransferIntent(cti)); registerForIntent(RequestZoneInIntent.class, rzii -> handleZoneIn(rzii.getCreature(), rzii.getPlayer(), rzii.getCreature().getLocation(), rzii.getCreature().getParent())); registerForIntent(ForceAwarenessUpdateIntent.class, faui -> handleForceUpdate(faui)); } @Override public boolean terminate() { awarenessHandler.close(); return super.terminate(); } @Override public void onWithinRange(SWGObject obj, SWGObject inRange) { Assert.notNull(obj); Assert.notNull(inRange); Assert.isNull(obj.getParent()); Assert.isNull(inRange.getParent()); Assert.notNull(obj.getTerrain()); Assert.notNull(inRange.getTerrain()); obj.addObjectAware(inRange); } @Override public void onOutOfRange(SWGObject obj, SWGObject outRange) { Assert.notNull(obj); Assert.notNull(outRange); obj.removeObjectAware(outRange); } @Override public void onMoveSuccess(SWGObject obj) { } @Override public void onMoveFailure(SWGObject obj) { Assert.notNull(obj); obj.clearObjectsAware(); } private void handlePlayerEventIntent(PlayerEventIntent pei) { Player p = pei.getPlayer(); CreatureObject creature = p.getCreatureObject(); switch (pei.getEvent()) { case PE_ZONE_IN_CLIENT: Assert.notNull(creature); moveObject(creature, creature.getParent(), creature.getLocation()); break; case PE_DISAPPEAR: Assert.notNull(creature); disappearObject(creature, true, true); break; case PE_DESTROYED: Assert.notNull(creature); creature.setOwner(null); break; default: break; } } private void handleObjectCreatedIntent(ObjectCreatedIntent oci) { SWGObject obj = oci.getObject(); Assert.notNull(obj); Assert.notNull(obj.getTerrain()); moveObject(obj, obj.getParent(), obj.getLocation()); } private void handleDestroyObjectIntent(DestroyObjectIntent doi) { SWGObject obj = doi.getObject(); obj.moveToContainer(null); disappearObject(obj, true, true); obj.setPosition(Terrain.GONE, 0, 0, 0); } private void processObjectTeleportIntent(ObjectTeleportIntent oti) { SWGObject obj = oti.getObject(); if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) { handleZoneIn((CreatureObject) obj, obj.getOwner(), oti.getNewLocation(), oti.getParent()); } else { moveObject(obj, oti.getParent(), oti.getNewLocation()); } } private void processGalacticPacketIntent(GalacticPacketIntent i) { Packet packet = i.getPacket(); if (packet instanceof DataTransform) { handleDataTransform((DataTransform) packet, i.getObjectManager()); } else if (packet instanceof DataTransformWithParent) { handleDataTransformWithParent((DataTransformWithParent) packet, i.getObjectManager()); } else if (packet instanceof CmdSceneReady) { handleCmdSceneReady(i.getPlayer(), (CmdSceneReady) packet); } } private void processMoveObjectIntent(MoveObjectIntent i) { moveObjectWithTransform(i.getObject(), i.getParent(), i.getNewLocation(), i.getSpeed(), i.getUpdateCounter()); } private void processContainerTransferIntent(ContainerTransferIntent i) { SWGObject obj = i.getObject(); Assert.notNull(obj); Assert.notNull(obj.getTerrain()); moveObject(obj, i.getContainer(), obj.getLocation()); } private void handleZoneIn(CreatureObject creature, Player player, Location loc, SWGObject parent) { creature.setOwner(player); // Fresh login or teleport/travel PlayerState state = player.getPlayerState(); if (state != PlayerState.LOGGED_IN && state != PlayerState.ZONED_IN) { new CloseConnectionIntent(player.getNetworkId(), DisconnectReason.APPLICATION).broadcast(); return; } boolean firstZone = state == PlayerState.LOGGED_IN; player.setPlayerState(PlayerState.ZONING_IN); if (parent == null) { Log.i("Zoning in %s with character %s to %s %s", player.getUsername(), player.getCharacterName(), loc.getPosition(), loc.getTerrain()); } else { Assert.notNull(parent.getParent()); // Character must be in a cell, inside a building SWGObject superParent = parent.getSuperParent(); Point3D world = superParent.getLocation().getPosition(); Log.i("Zoning in %s with character %s at %s in %s/%s %s", player.getUsername(), player.getCharacterName(), loc.getPosition(), superParent, world, superParent.getTerrain()); } resetAwarenessOnZone(creature, firstZone); creature.moveToContainer(parent); creature.setLocation(loc); startZone(creature, firstZone); recursiveCreateObject(creature, player); if (parent != null) { for (SWGObject obj : creature.getObjectsAware()) { obj.createObject(creature); creature.createObject(obj); } } Assert.notNull(creature.getTerrain()); } private void handleForceUpdate(ForceAwarenessUpdateIntent faui) { SWGObject obj = faui.getObject(); moveObject(obj, obj.getParent(), obj.getLocation()); } private void resetAwarenessOnZone(CreatureObject creature, boolean firstZone) { if (firstZone) { for (Player observer : creature.getObservers()) { creature.destroyObject(observer); } creature.resetAwareness(); creature.clearCustomAware(false); } else { creature.clearObjectsAware(); } } private void startZone(CreatureObject creature, boolean firstZone) { Player player = creature.getOwner(); long time = ProjectSWG.getGalacticTime(); int realTime = (int) (System.currentTimeMillis()/1E3); Intent firstZoneIntent = null; if (firstZone) { player.sendPacket(new HeartBeat()); player.sendPacket(new ChatServerStatus(true)); player.sendPacket(new VoiceChatStatus()); player.sendPacket(new ParametersMessage()); player.sendPacket(new ChatOnConnectAvatar()); firstZoneIntent = new PlayerEventIntent(player, PlayerEvent.PE_FIRST_ZONE); firstZoneIntent.broadcast(); } player.sendPacket(new CmdStartScene(true, creature.getObjectId(), creature.getRace(), creature.getWorldLocation(), time, realTime)); new PlayerEventIntent(player, PlayerEvent.PE_ZONE_IN_CLIENT).broadcastAfterIntent(firstZoneIntent); } private void handleCmdSceneReady(Player player, CmdSceneReady p) { Assert.test(player.getPlayerState() == PlayerState.ZONING_IN); player.setPlayerState(PlayerState.ZONED_IN); Log.i("%s with character %s zoned in from %s", player.getUsername(), player.getCharacterName(), p.getSocketAddress()); new PlayerEventIntent(player, PlayerEvent.PE_ZONE_IN_SERVER).broadcast(); player.sendPacket(new CmdSceneReady()); } private void recursiveCreateObject(SWGObject obj, Player owner) { SWGObject parent = obj.getParent(); if (parent != null) recursiveCreateObject(parent, owner); else obj.createObject(owner); } private void handleDataTransform(DataTransform dt, ObjectManager objectManager) { SWGObject obj = objectManager.getObjectById(dt.getObjectId()); Assert.test(obj instanceof CreatureObject, "DataTransform object not CreatureObject! Was: " + (obj==null?"null":obj.getClass())); Location requestedLocation = new Location(dt.getLocation()); requestedLocation.setTerrain(obj.getTerrain()); moveObjectWithTransform(obj, null, requestedLocation, dt.getSpeed(), dt.getUpdateCounter()); } private void handleDataTransformWithParent(DataTransformWithParent dt, ObjectManager objectManager) { SWGObject obj = objectManager.getObjectById(dt.getObjectId()); SWGObject parent = objectManager.getObjectById(dt.getCellId()); Assert.test(obj instanceof CreatureObject, "DataTransformWithParent object not CreatureObject! Was: " + (obj==null?"null":obj.getClass())); if (parent == null) { Log.w("Unknown data transform parent! Obj: %d/%s Parent: %d", dt.getObjectId(), obj, dt.getCellId()); return; } Location requestedLocation = new Location(dt.getLocation()); requestedLocation.setTerrain(obj.getTerrain()); moveObjectWithTransform(obj, parent, requestedLocation, dt.getSpeed(), dt.getUpdateCounter()); } private void moveObject(SWGObject obj, SWGObject parent, Location requestedLocation) { Assert.notNull(requestedLocation.getTerrain()); if (parent == null) awarenessHandler.moveObject(obj, requestedLocation); else awarenessHandler.moveObject(obj, parent, requestedLocation); } private void moveObjectWithTransform(SWGObject obj, SWGObject parent, Location requestedLocation, double speed, int update) { if (obj instanceof CreatureObject && ((CreatureObject) obj).isStatesBitmask(CreatureState.RIDING_MOUNT)) parent = obj.getParent(); moveObject(obj, parent, requestedLocation); if (parent == null) dataTransformHandler.handleMove(obj, speed, update); else dataTransformHandler.handleMove(obj, parent, speed, update); if (obj instanceof CreatureObject && ((CreatureObject) obj).isLoggedInPlayer()) new PlayerTransformedIntent((CreatureObject) obj, obj.getParent(), parent, obj.getLocation(), requestedLocation).broadcast(); } private void disappearObject(SWGObject obj, boolean disappearObjects, boolean disappearCustom) { awarenessHandler.disappearObject(obj, disappearObjects, disappearCustom); obj.clearObjectsAware(); obj.clearCustomAware(true); } }