diff --git a/src/intents/FactionIntent.java b/src/intents/FactionIntent.java index f31a75d6..9cceb102 100644 --- a/src/intents/FactionIntent.java +++ b/src/intents/FactionIntent.java @@ -28,6 +28,7 @@ package intents; import resources.PvpFaction; +import resources.PvpStatus; import resources.control.Intent; import resources.objects.tangible.TangibleObject; @@ -37,6 +38,7 @@ public class FactionIntent extends Intent { private TangibleObject target; private PvpFaction newFaction; private FactionIntentType updateType; + private PvpStatus newStatus; private FactionIntent(TangibleObject target) { super(TYPE); @@ -53,6 +55,11 @@ public class FactionIntent extends Intent { this.newFaction = newFaction; } + public FactionIntent(TangibleObject target, PvpStatus newStatus) { + this(target, FactionIntentType.STATUSUPDATE); + this.newStatus = newStatus; + } + public TangibleObject getTarget() { return target; } @@ -64,9 +71,14 @@ public class FactionIntent extends Intent { public FactionIntentType getUpdateType() { return updateType; } + + public PvpStatus getNewStatus() { + return newStatus; + } public enum FactionIntentType { FLAGUPDATE, + SWITCHUPDATE, STATUSUPDATE, FACTIONUPDATE // Is automatically set in the correct constructor, don't use manually. } diff --git a/src/services/combat/CorpseService.java b/src/services/combat/CorpseService.java index 0f77fe1a..bac4171c 100644 --- a/src/services/combat/CorpseService.java +++ b/src/services/combat/CorpseService.java @@ -42,12 +42,15 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.stream.Collectors; +import main.ProjectSWG.CoreException; import resources.Location; import resources.Posture; import resources.PvpFaction; +import resources.PvpStatus; import resources.Terrain; import resources.client_info.ClientFactory; import resources.control.Intent; @@ -79,14 +82,14 @@ public final class CorpseService extends Service { private final ScheduledExecutorService executor; private final Map facilityDataMap; - private final Map cloneMapping; // Needed for dungeons that have no cloning facilities + private final Map cloneMappings; // Needed for dungeons that have no cloning facilities private final List cloningFacilities; private final Random random; public CorpseService() { executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("corpse-service")); facilityDataMap = new HashMap<>(); - cloneMapping = new HashMap<>(); + cloneMappings = new HashMap<>(); cloningFacilities = new ArrayList<>(); random = new Random(); @@ -116,21 +119,41 @@ public final class CorpseService extends Service { long startTime = System.currentTimeMillis(); Log.i(this, "Loading cloning facility data..."); - try (RelationalDatabase spawnerDatabase = RelationalServerFactory.getServerData("cloning/cloning_mapping.db", "cloning_mapping")) { - try (ResultSet set = spawnerDatabase.executeQuery("SELECT * FROM cloning_mapping")) { + try (RelationalDatabase mappingDatabase = RelationalServerFactory.getServerData("cloning/clone_mapping.db", "clone_mapping")) { + try (ResultSet set = mappingDatabase.executeQuery("SELECT * FROM clone_mapping")) { while (set.next()) { - String terrainName; - String areaName; - String targetTerrainName; - String targetAreaName; + String scene = null; + + try { + String sourceBuildoutArea = set.getString("area"); + + if (sourceBuildoutArea.equals("-")) { + sourceBuildoutArea = null; + } + + scene = set.getString("scene").toUpperCase(Locale.ENGLISH); + CloneMapping sourceDestination = new CloneMapping(Terrain.valueOf(scene), sourceBuildoutArea); + + String targetBuildoutArea = set.getString("clone_area"); + if (targetBuildoutArea.equals("-")) { + targetBuildoutArea = null; + } + + scene = set.getString("clone_scene").toUpperCase(Locale.ENGLISH); + CloneMapping targetDestination = new CloneMapping(Terrain.valueOf(scene), targetBuildoutArea); + + cloneMappings.put(sourceDestination, targetDestination); + } catch (IllegalArgumentException e) { + throw new CoreException(String.format("Scene %s in row %d is invalid, as no terrain with that name exists - please correct!", scene, set.getRow())); + } } } catch (SQLException e) { Log.e(this, e); } } - try (RelationalDatabase spawnerDatabase = RelationalServerFactory.getServerData("cloning/cloning_respawn.db", "cloning_respawn")) { - try (ResultSet set = spawnerDatabase.executeQuery("SELECT * FROM cloning_respawn")) { + try (RelationalDatabase respawnDatabase = RelationalServerFactory.getServerData("cloning/cloning_respawn.db", "cloning_respawn")) { + try (ResultSet set = respawnDatabase.executeQuery("SELECT * FROM cloning_respawn")) { while (set.next()) { int tubeCount = set.getInt("tubes"); TubeData[] tubeData = new TubeData[tubeCount]; @@ -177,8 +200,17 @@ public final class CorpseService extends Service { new ChatBroadcastIntent(corpseOwner, new ProsePackage(new StringId("base_player", "prose_victim_dead"), "TT", i.getKillerCreature().getName())).broadcast(); new ChatBroadcastIntent(corpseOwner, new ProsePackage(new StringId("base_player", "revive_exp_msg"), "TT", CLONE_TIMER + " minutes.")).broadcast(); - Terrain destinationTerrain = corpse.getTerrain(); // TODO get from terrainMapping - List availableFacilities = getAvailableFacilities(corpse, destinationTerrain); + Terrain corpseTerrain = corpse.getTerrain(); + CloneMapping cloneMapping = new CloneMapping(corpseTerrain, corpse.getBuildoutArea().getName()); + CloneMapping destinationMapping = cloneMappings.get(cloneMapping); + + List availableFacilities; + + if(destinationMapping != null) { + availableFacilities = getAvailableFacilities(corpse, destinationMapping); + } else { + availableFacilities = getAvailableFacilities(corpse, cloneMapping); + } if (!availableFacilities.isEmpty()) { SuiListBox suiWindow = new SuiListBox(SuiButtons.OK, "@base_player:revive_title", "@base_player:clone_prompt_header"); @@ -227,7 +259,7 @@ public final class CorpseService extends Service { executor.schedule(() -> expireCloneTimer(corpse, availableFacilities, suiWindow), CLONE_TIMER, TimeUnit.MINUTES); } else { // TODO no cloners available! Wat do? - Log.e(this, "No cloning facility is available for terrain %s - %s has nowhere to properly clone", destinationTerrain, corpse); + Log.e(this, "No cloning facility is available for terrain %s - %s has nowhere to properly clone", corpseTerrain, corpse); } } else { // This is a NPC - schedule corpse for deletion @@ -279,20 +311,29 @@ public final class CorpseService extends Service { * to {@code corpse}. Order is reversed, so the closest facility is * first. */ - private List getAvailableFacilities(CreatureObject corpse, Terrain destinationTerrain) { + private List getAvailableFacilities(CreatureObject corpse, CloneMapping destinationMapping) { synchronized (cloningFacilities) { Location corpseLocation = corpse.getWorldLocation(); return cloningFacilities.stream() - .filter(facilityObject -> isValidTerrain(facilityObject, destinationTerrain) && isFactionAllowed(facilityObject, corpse)) + .filter(facilityObject -> isValidTerrain(facilityObject, destinationMapping) && isFactionAllowed(facilityObject, corpse)) .sorted((facility, otherFacility) -> Double.compare(corpseLocation.distanceTo(facility.getLocation()), corpseLocation.distanceTo(otherFacility.getLocation()))) .collect(Collectors.toList()); } } - private boolean isValidTerrain(BuildingObject cloningFacility, Terrain destinationTerrain) { - return cloningFacility.getTerrain() == destinationTerrain; + // TODO needs to account for instancing! + private boolean isValidTerrain(BuildingObject cloningFacility, CloneMapping destinationMapping) { + String destinationBuildoutArea = destinationMapping.getBuildoutAreaName(); + Terrain destinationTerrain = destinationMapping.getTerrain(); + boolean available = true; + + if(destinationBuildoutArea != null) { + available = destinationBuildoutArea.equals(cloningFacility.getBuildoutArea().getName()); + } + + return available && cloningFacility.getTerrain() == destinationTerrain; } private boolean isFactionAllowed(BuildingObject cloningFacility, CreatureObject corpse) { @@ -302,7 +343,7 @@ public final class CorpseService extends Service { return factionRestriction == null || factionRestriction == corpse.getPvpFaction(); } - private CloneResult clone(CreatureObject cloneRequestor, BuildingObject selectedFacility) { + private CloneResult clone(CreatureObject corpse, BuildingObject selectedFacility) { Location facilityLocation = selectedFacility.getLocation(); FacilityData facilityData = facilityDataMap.get(selectedFacility.getTemplate()); @@ -311,9 +352,9 @@ public final class CorpseService extends Service { CellObject cellObject = selectedFacility.getCellByName(cellName); if (cellObject != null) { - // TODO They should go on leave upon cloning -// if(creature.getPvpFaction() != PvpFaction.NEUTRAL) -// new FactionIntent(creature, PvpStatus.ONLEAVE).broadcast(); + // They should go on leave upon cloning + if(corpse.getPvpFaction() != PvpFaction.NEUTRAL) + new FactionIntent(corpse, PvpStatus.ONLEAVE).broadcast(); TubeData[] tubeData = facilityData.getTubeData(); @@ -333,11 +374,11 @@ public final class CorpseService extends Service { cloneLocation.rotateHeading(facilityData.getHeading()); } - new ObjectTeleportIntent(cloneRequestor, cellObject, cloneLocation).broadcast(); - cloneRequestor.setPosture(Posture.UPRIGHT); - cloneRequestor.setTurnScale(1); - cloneRequestor.setMovementScale(1); - cloneRequestor.setHealth(cloneRequestor.getMaxHealth()); + new ObjectTeleportIntent(corpse, cellObject, cloneLocation).broadcast(); + corpse.setPosture(Posture.UPRIGHT); + corpse.setTurnScale(1); + corpse.setMovementScale(1); + corpse.setHealth(corpse.getMaxHealth()); // TODO NGE: cloning debuff @@ -347,7 +388,7 @@ public final class CorpseService extends Service { return CloneResult.INVALID_CELL; } } else { - Log.e(this, "%s could not clone at facility %s because the object template is not in cloning_respawn.sdb", cloneRequestor, selectedFacility); + Log.e(this, "%s could not clone at facility %s because the object template is not in cloning_respawn.sdb", corpse, selectedFacility); return CloneResult.TEMPLATE_MISSING; } } @@ -476,4 +517,23 @@ public final class CorpseService extends Service { private static enum CloneResult { INVALID_SELECTION, TEMPLATE_MISSING, INVALID_CELL, SUCCESS } + + private static class CloneMapping { + private final Terrain terrain; + private final String buildoutAreaName; + + public CloneMapping(Terrain terrain, String buildoutAreaName) { + this.terrain = terrain; + this.buildoutAreaName = buildoutAreaName; + } + + public Terrain getTerrain() { + return terrain; + } + + public String getBuildoutAreaName() { + return buildoutAreaName; + } + + } } \ No newline at end of file diff --git a/src/services/faction/FactionService.java b/src/services/faction/FactionService.java index 49addcaf..0a7fa429 100644 --- a/src/services/faction/FactionService.java +++ b/src/services/faction/FactionService.java @@ -37,6 +37,9 @@ import network.packets.swg.zone.chat.ChatSystemMessage; import network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; import intents.FactionIntent; import intents.PlayerEventIntent; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; import resources.PvpFaction; import resources.PvpFlag; import resources.PvpStatus; @@ -46,10 +49,12 @@ import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import resources.player.Player; +import utilities.ThreadUtilities; public final class FactionService extends Service { private ScheduledExecutorService executor; + private Map> statusChangers; public FactionService() { registerForIntent(FactionIntent.TYPE); @@ -57,7 +62,8 @@ public final class FactionService extends Service { @Override public boolean initialize() { - executor = Executors.newSingleThreadScheduledExecutor(); + statusChangers = new HashMap<>(); + executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("faction-service")); return super.initialize(); } @@ -70,17 +76,11 @@ public final class FactionService extends Service { @Override public boolean terminate() { - boolean success = true; - try { - if (executor != null) { - executor.shutdownNow(); - success = executor.awaitTermination(5, TimeUnit.SECONDS); - } - } catch (InterruptedException e) { - e.printStackTrace(); - success = false; + if (executor != null) { + executor.shutdownNow().forEach(runnable -> runnable.run()); } - return super.terminate() && success; + + return super.terminate(); } private void handleFactionIntent(FactionIntent i) { @@ -88,6 +88,9 @@ public final class FactionService extends Service { case FACTIONUPDATE: handleTypeChange(i); break; + case SWITCHUPDATE: + handleSwitchChange(i); + break; case STATUSUPDATE: handleStatusChange(i); break; @@ -161,7 +164,7 @@ public final class FactionService extends Service { } } - private void handleStatusChange(FactionIntent fi) { + private void handleSwitchChange(FactionIntent fi) { final PvpFlag pvpFlag; final TangibleObject target = fi.getTarget(); final PvpStatus currentStatus = target.getPvpStatus(); @@ -193,6 +196,27 @@ public final class FactionService extends Service { } } + private void handleStatusChange(FactionIntent fi) { + // If they're currently going OVERT or COVERT, we need to handle it accordingly + // Forces them into the given PvpStatus! + TangibleObject target = fi.getTarget(); + PvpStatus oldStatus = target.getPvpStatus(); + PvpStatus newStatus = fi.getNewStatus(); + + // No reason to send deltas and all that if the status isn't effectively changing + if(oldStatus == newStatus) + return; + + // Let's clear PvP flags in case they were in the middle of going covert/overt + Future future = statusChangers.get(target); + future.cancel(true); + + target.setPvpStatus(newStatus); + target.getOwner().sendPacket(new ChatSystemMessage( + SystemChatType.SCREEN_AND_CHAT, + getCompletionMessage(oldStatus, newStatus))); + } + private void handleFlagChange(TangibleObject object) { Player objOwner = object.getOwner();