/* * Copyright 2016 Nathan Howard * * This file is part of OpenGrave * * OpenGrave is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenGrave 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenGrave. If not, see <http://www.gnu.org/licenses/>. */ package com.opengrave.server; import java.util.ArrayList; import java.util.HashMap; import java.util.TimerTask; import java.util.UUID; import com.opengrave.common.order.GatherOrder; import com.opengrave.common.order.Order; import com.opengrave.common.packet.fromserver.ObjectLookTowardsPacket; import com.opengrave.common.packet.fromserver.ObjectMovementOnPathPacket; import com.opengrave.common.packet.fromserver.ObjectsDespawnPacket; import com.opengrave.common.packet.fromserver.ObjectsSpawnPacket; import com.opengrave.common.pathing.Line; import com.opengrave.common.pathing.PathFinderPolygonAStar; import com.opengrave.common.pathing.Point; import com.opengrave.common.world.*; public class ServerHeartbeat extends TimerTask { public ServerHeartbeat() { } Server server; public ServerHeartbeat(Server server) { this.server = server; } public void run() { ArrayList<DataConnector> newCopy = Server.getServer().getConnectionsCopy(); for (DataConnector c : newCopy) { // If dead, close socket cleanly if (c.getDestroy()) { // TODO Somehow notify users of logout? Server.getServer().dropConnection(c); continue; } } // Basic Logic round. Set paths based on orders etc newCopy = Server.getServer().getConnectionsCopy(); // Use a new copy with dead connections dropped for (DataConnector c : newCopy) { if (c.orderList.getArray().size() > 0) { // We have orders ArrayList<Order> o = c.orderList.getOrders(GatherOrder.class); if (o.size() > 0) { for (PlayerCharacter pc : c.pcList) { if (pc.getFinder() == null) { // TODO: Check destination is the same as last path to replace? pc.setFinder(new PathFinderPolygonAStar(Server.getSession().getWorlds().get(0).getNavMesh(), new Point(((GatherOrder) o.get(0)) .getLocation()))); // TODO: Not Assume world 0 } } } } } // TODO Persist the area in common object or object storage. Alter it only on movement below or magical teleport HashMap<CommonAreaLoc, ArrayList<CommonObject>> objectsByArea = new HashMap<CommonAreaLoc, ArrayList<CommonObject>>(); for (CommonObject obj : Server.getSession().getObjectStorage().getObjects()) { CommonAreaLoc loc = CommonWorld.getAreaLocFor(obj.getLocation()); if (!objectsByArea.containsKey(loc)) { objectsByArea.put(loc, new ArrayList<CommonObject>()); } objectsByArea.get(loc).add(obj); } ArrayList<UUID> changedList = Server.getSession().getObjectStorage().getChangedObjectIDs(); for (DataConnector c : newCopy) { if (c.getDestroy()) { continue; } // Calculate areas that connection can see ArrayList<CommonAreaLoc> seenAreas = new ArrayList<CommonAreaLoc>(); ArrayList<CommonAreaLoc> lostAreas = new ArrayList<CommonAreaLoc>(); ArrayList<CommonAreaLoc> newAreas = new ArrayList<CommonAreaLoc>(); lostAreas.addAll(c.lastSeenAreas); for (PlayerCharacter pc : c.pcList) { CommonLocation l = pc.getLocation(); if (l == null) { continue; } CommonAreaLoc center = CommonWorld.getAreaLocFor(l); for (int x = -1; x < 2; x++) { for (int y = -1; y < 2; y++) { CommonAreaLoc loc = center.getNeighbour(x, y); if (!seenAreas.contains(loc)) { seenAreas.add(loc); } if (lostAreas.contains(loc)) { lostAreas.remove(loc); } if (!c.lastSeenAreas.contains(loc)) { newAreas.add(loc); } } } } ArrayList<CommonObject> objSpawnIn = new ArrayList<CommonObject>(); // Add all objects from areas that have only just moved into view for (CommonAreaLoc loc : newAreas) { if (!objectsByArea.containsKey(loc)) { continue; } objSpawnIn.addAll(objectsByArea.get(loc)); } // Add (again!) all objects that have had a major change of type, material, or model for (CommonAreaLoc loc : seenAreas) { if (!objectsByArea.containsKey(loc)) { continue; } for (CommonObject obj : objectsByArea.get(loc)) { if (changedList.contains(obj.getUUID())) { objSpawnIn.add(obj); } } } ObjectsSpawnPacket spawning = new ObjectsSpawnPacket(); spawning.obj = objSpawnIn; c.send(spawning); ArrayList<UUID> objDespawn = new ArrayList<UUID>(); for (CommonAreaLoc loc : lostAreas) { if (!objectsByArea.containsKey(loc)) { continue; } for (CommonObject obj : objectsByArea.get(loc)) { objDespawn.add(obj.getUUID()); } } // TODO : Add any objects that have despawned in full view here. Newly dead etc. ObjectsDespawnPacket despawning = new ObjectsDespawnPacket(); despawning.idList = objDespawn; c.send(despawning); // Push all animations and movement of seen objects to client. c.lastSeenAreas = seenAreas; } // Calculate next tick worth of combat and movement for (CommonObject obj : Server.getSession().getObjectStorage().getObjects()) { if (obj instanceof MovableObject) { MovableObject mobj = (MovableObject) obj; mobj.doAnimationTick(); double pos = mobj.doMovementTick(); Line l = mobj.getLine(pos); if (Double.isNaN(pos)) { continue; } ObjectMovementOnPathPacket packet = new ObjectMovementOnPathPacket(obj.getUUID(), pos); server.sendToAllObject(obj.getUUID(), packet); if (l == null) { continue; } ObjectLookTowardsPacket packet2 = new ObjectLookTowardsPacket(obj.getUUID(), l.getPoint(1)); server.sendToAllObject(obj.getUUID(), packet2); } } } }