From bcf6b306d3616e5a48c141b13490f7734ec15c66 Mon Sep 17 00:00:00 2001 From: Obique PSWG Date: Wed, 5 Apr 2017 13:51:19 -0500 Subject: [PATCH] Massive refactor to start using lib/PSWGCommon.jar --- .classpath | 1 + lib/PSWGCommon.jar | Bin 0 -> 167165 bytes src/intents/BuffIntent.java | 14 +- src/intents/DanceIntent.java | 21 +- src/intents/FactionIntent.java | 11 +- src/intents/FlourishIntent.java | 16 +- src/intents/GalacticIntent.java | 8 +- src/intents/GrantBadgeIntent.java | 11 +- src/intents/GrantClickyCollectionIntent.java | 18 +- src/intents/GroupEventIntent.java | 25 +- src/intents/LoginEventIntent.java | 11 +- src/intents/NotifyPlayersPacketIntent.java | 49 +- src/intents/PlayerEventIntent.java | 6 +- src/intents/RequestZoneInIntent.java | 6 +- src/intents/SkillModIntent.java | 12 +- src/intents/WatchIntent.java | 15 +- src/intents/chat/ChatAvatarRequestIntent.java | 21 +- src/intents/chat/ChatBroadcastIntent.java | 6 +- src/intents/chat/ChatCommandIntent.java | 7 +- src/intents/chat/ChatRoomUpdateIntent.java | 54 ++- src/intents/chat/PersistentMessageIntent.java | 21 +- src/intents/chat/SpatialChatIntent.java | 28 +- src/intents/combat/CreatureKilledIntent.java | 14 +- src/intents/combat/DeathblowIntent.java | 12 +- src/intents/experience/ExperienceIntent.java | 16 +- src/intents/experience/GrantSkillIntent.java | 21 +- .../experience/LevelChangedIntent.java | 22 +- .../network/CloseConnectionIntent.java | 5 +- .../network/ConnectionClosedIntent.java | 5 +- .../network/ConnectionOpenedIntent.java | 6 +- src/intents/network/GalacticPacketIntent.java | 3 - src/intents/network/InboundPacketIntent.java | 6 +- .../network/InboundPacketPendingIntent.java | 6 +- src/intents/network/OutboundPacketIntent.java | 6 +- src/intents/network/PingIntent.java | 40 -- .../object/ContainerTransferIntent.java | 6 +- .../object/CreateStaticItemIntent.java | 20 +- src/intents/object/DestroyObjectIntent.java | 6 +- src/intents/object/MoveObjectIntent.java | 7 +- src/intents/object/ObjectCreatedIntent.java | 6 +- src/intents/object/ObjectTeleportIntent.java | 17 +- src/intents/player/DeleteCharacterIntent.java | 6 +- .../player/PlayerTransformedIntent.java | 6 +- src/intents/radial/RadialRegisterIntent.java | 6 +- src/intents/radial/RadialRequestIntent.java | 6 +- src/intents/radial/RadialResponseIntent.java | 6 +- src/intents/radial/RadialSelectionIntent.java | 6 +- src/intents/server/ConfigChangedIntent.java | 17 +- .../server/ServerManagementIntent.java | 30 +- src/intents/server/ServerStatusIntent.java | 13 +- src/intents/sui/SuiWindowIntent.java | 30 +- src/intents/travel/TicketPurchaseIntent.java | 6 +- src/intents/travel/TicketUseIntent.java | 7 +- .../travel/TravelPointSelectionIntent.java | 6 +- src/main/ProjectSWG.java | 25 +- src/network/NetworkClient.java | 17 +- .../swg/zone/resource/ResourceWeight.java | 3 +- .../callback/BaseCallbackManager.java | 106 ----- src/resources/callback/CallbackManager.java | 110 ----- src/resources/collections/SWGList.java | 5 +- src/resources/collections/SWGMap.java | 16 +- src/resources/collections/SWGSet.java | 23 +- .../commands/callbacks/GotoCmdCallback.java | 5 +- .../commands/callbacks/QaToolCmdCallback.java | 16 +- .../callbacks/TransferItemCallback.java | 3 +- .../concurrency/PswgBasicScheduledThread.java | 64 --- .../concurrency/PswgBasicThread.java | 65 --- .../concurrency/PswgScheduledThreadPool.java | 115 ----- .../concurrency/PswgTaskThreadPool.java | 73 --- src/resources/concurrency/PswgThreadPool.java | 119 ----- src/resources/concurrency/SmartLock.java | 129 ----- .../concurrency/SynchronizedQueue.java | 151 ------ src/resources/control/Assert.java | 117 ----- src/resources/control/Intent.java | 151 ------ src/resources/control/IntentManager.java | 140 ------ src/resources/control/IntentQueue.java | 235 --------- src/resources/control/IntentReceiver.java | 41 -- src/resources/control/Manager.java | 183 ------- src/resources/control/Service.java | 138 ------ src/resources/objects/SWGObject.java | 28 +- .../objects/awareness/AwarenessHandler.java | 3 +- .../objects/awareness/TerrainMap.java | 5 +- .../objects/awareness/TerrainMapChunk.java | 3 +- .../objects/building/BuildingObject.java | 26 +- .../creature/CreatureObjectSharedNP.java | 3 +- src/resources/objects/group/GroupObject.java | 29 +- src/resources/player/Player.java | 5 +- src/resources/server_info/Config.java | 183 ------- src/resources/server_info/ConfigData.java | 152 ------ src/resources/server_info/ConfigWatcher.java | 139 +++--- src/resources/server_info/CrcDatabase.java | 3 + src/resources/server_info/DataManager.java | 182 +++---- src/resources/server_info/ObjectDatabase.java | 3 +- .../server_info/PostgresqlDatabase.java | 36 -- .../server_info/RelationalDatabase.java | 190 -------- .../server_info/RelationalServerData.java | 445 ------------------ .../server_info/RelationalServerFactory.java | 160 ------- .../server_info/SynchronizedList.java | 182 ------- .../server_info/SynchronizedMap.java | 107 ----- .../server_info/SynchronizedSet.java | 131 ------ src/services/CoreManager.java | 21 +- src/services/EngineManager.java | 12 +- src/services/ShutdownService.java | 10 +- .../admin/OnlineInterfaceService.java | 241 ---------- src/services/admin/WebserverData.java | 131 ------ src/services/admin/WebserverHandler.java | 362 -------------- src/services/admin/http/HttpCookie.java | 239 ---------- src/services/admin/http/HttpImageType.java | 34 -- src/services/admin/http/HttpServer.java | 263 ----------- src/services/admin/http/HttpSession.java | 52 -- src/services/admin/http/HttpSocket.java | 362 -------------- src/services/admin/http/HttpStatusCode.java | 75 --- src/services/admin/http/HttpsServer.java | 107 ----- src/services/chat/ChatMailService.java | 7 +- src/services/chat/ChatManager.java | 18 +- src/services/chat/ChatRoomContainer.java | 3 +- src/services/chat/ChatRoomHandler.java | 7 +- src/services/chat/ChatRoomService.java | 5 +- .../collections/CollectionBadgeManager.java | 9 +- .../collections/CollectionService.java | 19 +- .../collections/ExplorationBadgeService.java | 12 +- src/services/combat/CombatManager.java | 18 +- src/services/combat/CombatXpService.java | 11 +- src/services/combat/CorpseService.java | 17 +- src/services/commands/BuffService.java | 16 +- src/services/commands/CommandService.java | 10 +- .../commands/EntertainmentService.java | 18 +- src/services/commands/buff/BuffData.java | 3 +- src/services/commands/buff/BuffMap.java | 3 +- src/services/dev/DeveloperService.java | 6 +- .../experience/ExperienceManager.java | 11 +- src/services/experience/ExpertiseService.java | 9 +- src/services/experience/SkillManager.java | 12 +- .../experience/SkillTemplateService.java | 14 +- src/services/faction/FactionService.java | 8 +- src/services/galaxy/ConnectionService.java | 14 +- src/services/galaxy/EnvironmentService.java | 8 +- src/services/galaxy/GalacticManager.java | 18 +- src/services/galaxy/GameManager.java | 15 +- src/services/galaxy/SkillModService.java | 3 +- .../galaxy/terminals/TerminalService.java | 16 +- src/services/galaxy/travel/TravelService.java | 32 +- src/services/group/GroupService.java | 14 +- src/services/map/CityService.java | 14 +- src/services/map/MapManager.java | 23 +- .../network/InboundNetworkManager.java | 3 +- .../network/NetworkClientManager.java | 11 +- src/services/network/NetworkManager.java | 2 +- src/services/network/NetworkProtocol.java | 5 +- .../network/OutboundNetworkManager.java | 5 +- .../objects/ClientBuildoutService.java | 17 +- src/services/objects/ObjectAwareness.java | 10 +- src/services/objects/ObjectManager.java | 12 +- src/services/objects/RadialService.java | 12 +- src/services/objects/StaticItemService.java | 19 +- src/services/objects/UniformBoxService.java | 14 +- .../player/CharacterCreationService.java | 22 +- src/services/player/LoginService.java | 24 +- src/services/player/PlayerManager.java | 16 +- src/services/player/TerrainZoneInsertion.java | 5 +- src/services/player/ZoneManager.java | 13 +- .../player/creation/CharacterCreation.java | 8 +- src/services/spawn/SpawnerService.java | 17 +- src/services/spawn/StaticService.java | 9 +- src/services/sui/SuiService.java | 8 +- src/utilities/IntentChain.java | 61 --- src/utilities/IntentFactory.java | 8 +- test/resources/control/TestIntentQueue.java | 49 -- .../objects/awareness/TestAwareness.java | 3 + test/resources/services/TestConfig.java | 3 +- 170 files changed, 955 insertions(+), 6498 deletions(-) create mode 100644 lib/PSWGCommon.jar delete mode 100644 src/intents/network/PingIntent.java delete mode 100644 src/resources/callback/BaseCallbackManager.java delete mode 100644 src/resources/callback/CallbackManager.java delete mode 100644 src/resources/concurrency/PswgBasicScheduledThread.java delete mode 100644 src/resources/concurrency/PswgBasicThread.java delete mode 100644 src/resources/concurrency/PswgScheduledThreadPool.java delete mode 100644 src/resources/concurrency/PswgTaskThreadPool.java delete mode 100644 src/resources/concurrency/PswgThreadPool.java delete mode 100644 src/resources/concurrency/SmartLock.java delete mode 100644 src/resources/concurrency/SynchronizedQueue.java delete mode 100644 src/resources/control/Assert.java delete mode 100644 src/resources/control/Intent.java delete mode 100644 src/resources/control/IntentManager.java delete mode 100644 src/resources/control/IntentQueue.java delete mode 100644 src/resources/control/IntentReceiver.java delete mode 100644 src/resources/control/Manager.java delete mode 100644 src/resources/control/Service.java delete mode 100644 src/resources/server_info/Config.java delete mode 100644 src/resources/server_info/ConfigData.java delete mode 100644 src/resources/server_info/PostgresqlDatabase.java delete mode 100644 src/resources/server_info/RelationalDatabase.java delete mode 100644 src/resources/server_info/RelationalServerData.java delete mode 100644 src/resources/server_info/RelationalServerFactory.java delete mode 100644 src/resources/server_info/SynchronizedList.java delete mode 100644 src/resources/server_info/SynchronizedMap.java delete mode 100644 src/resources/server_info/SynchronizedSet.java delete mode 100644 src/services/admin/OnlineInterfaceService.java delete mode 100644 src/services/admin/WebserverData.java delete mode 100644 src/services/admin/WebserverHandler.java delete mode 100644 src/services/admin/http/HttpCookie.java delete mode 100644 src/services/admin/http/HttpImageType.java delete mode 100644 src/services/admin/http/HttpServer.java delete mode 100644 src/services/admin/http/HttpSession.java delete mode 100644 src/services/admin/http/HttpSocket.java delete mode 100644 src/services/admin/http/HttpStatusCode.java delete mode 100644 src/services/admin/http/HttpsServer.java delete mode 100644 src/utilities/IntentChain.java delete mode 100644 test/resources/control/TestIntentQueue.java diff --git a/.classpath b/.classpath index 19a2f0421..16ca8f86e 100644 --- a/.classpath +++ b/.classpath @@ -27,5 +27,6 @@ + diff --git a/lib/PSWGCommon.jar b/lib/PSWGCommon.jar new file mode 100644 index 0000000000000000000000000000000000000000..9d38bbf755825d83fd55ff7f4b5fb5f871e753e4 GIT binary patch literal 167165 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Ⓢ90{u5@lD`(5?*`%ajWN< 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

Kmr$;`JR&a1bg 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 literal 0 HcmV?d00001 diff --git a/src/intents/BuffIntent.java b/src/intents/BuffIntent.java index 50e9b0b90..00b6b3221 100644 --- a/src/intents/BuffIntent.java +++ b/src/intents/BuffIntent.java @@ -27,37 +27,35 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -public class BuffIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "BuffIntent"; +public class BuffIntent extends Intent { private final String buffName; private final CreatureObject buffer, receiver; private final boolean remove; public BuffIntent(String buffName, CreatureObject buffer, CreatureObject receiver, boolean remove) { - super(TYPE); this.buffName = buffName; this.buffer = buffer; this.receiver = receiver; this.remove = remove; } - + public CreatureObject getReceiver() { return receiver; } - + public CreatureObject getBuffer() { return buffer; } - + public String getBuffName() { return buffName; } - + public boolean isRemove() { return remove; } diff --git a/src/intents/DanceIntent.java b/src/intents/DanceIntent.java index 4c02e7f1c..6e1921d0d 100644 --- a/src/intents/DanceIntent.java +++ b/src/intents/DanceIntent.java @@ -27,26 +27,22 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public class DanceIntent extends Intent { - public static final String TYPE = "DanceIntent"; private final String danceName; private final CreatureObject creatureObject; private final boolean changeDance; /** * Start dancing + * * @param danceName - * @param creatureObject + * @param creatureObject */ public DanceIntent(String danceName, CreatureObject creatureObject, boolean changeDance) { - super(TYPE); this.danceName = danceName; this.creatureObject = creatureObject; this.changeDance = changeDance; @@ -54,16 +50,17 @@ public class DanceIntent extends Intent { /** * Stop dancing - * @param creatureObject + * + * @param creatureObject */ public DanceIntent(CreatureObject creatureObject) { this(null, creatureObject, false); } - + public String getDanceName() { return danceName; } - + public CreatureObject getCreatureObject() { return creatureObject; } @@ -71,7 +68,7 @@ public class DanceIntent extends Intent { public boolean isStartDance() { return danceName != null; } - + public boolean isChangeDance() { return changeDance; } diff --git a/src/intents/FactionIntent.java b/src/intents/FactionIntent.java index 9cceb1025..ba50ad789 100644 --- a/src/intents/FactionIntent.java +++ b/src/intents/FactionIntent.java @@ -29,19 +29,18 @@ package intents; import resources.PvpFaction; import resources.PvpStatus; -import resources.control.Intent; import resources.objects.tangible.TangibleObject; -public class FactionIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "FactionIntent"; +public class FactionIntent extends Intent { + private TangibleObject target; private PvpFaction newFaction; private FactionIntentType updateType; private PvpStatus newStatus; private FactionIntent(TangibleObject target) { - super(TYPE); this.target = target; } @@ -71,7 +70,7 @@ public class FactionIntent extends Intent { public FactionIntentType getUpdateType() { return updateType; } - + public PvpStatus getNewStatus() { return newStatus; } @@ -83,4 +82,4 @@ public class FactionIntent extends Intent { FACTIONUPDATE // Is automatically set in the correct constructor, don't use manually. } -} \ No newline at end of file +} diff --git a/src/intents/FlourishIntent.java b/src/intents/FlourishIntent.java index f83e30b69..5c8c5f233 100644 --- a/src/intents/FlourishIntent.java +++ b/src/intents/FlourishIntent.java @@ -27,30 +27,24 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.player.Player; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public class FlourishIntent extends Intent { - public static final String TYPE = "FlourishIntent"; - private final Player performer; private final String flourishName; - + public FlourishIntent(Player performer, String flourishName) { - super(TYPE); this.performer = performer; this.flourishName = flourishName; } - + public Player getPerformer() { return performer; } - + public String getFlourishName() { return flourishName; } diff --git a/src/intents/GalacticIntent.java b/src/intents/GalacticIntent.java index ca62db586..831e4b559 100644 --- a/src/intents/GalacticIntent.java +++ b/src/intents/GalacticIntent.java @@ -27,7 +27,8 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; +import com.projectswg.common.control.Intent; + import services.galaxy.GalacticManager; import services.objects.ObjectManager; import services.player.PlayerManager; @@ -36,12 +37,11 @@ public abstract class GalacticIntent extends Intent { private GalacticManager galacticManager; - protected GalacticIntent(String type) { - super(type); + protected GalacticIntent() { + } protected GalacticIntent(GalacticIntent i) { - super(i.getType()); setGalacticManager(i.getGalacticManager()); } diff --git a/src/intents/GrantBadgeIntent.java b/src/intents/GrantBadgeIntent.java index 1fbd6d036..d33ec3c35 100644 --- a/src/intents/GrantBadgeIntent.java +++ b/src/intents/GrantBadgeIntent.java @@ -26,27 +26,26 @@ ******************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; +import com.projectswg.common.control.Intent; + public class GrantBadgeIntent extends Intent { - public static final String TYPE = "GrantBadgeIntent"; private CreatureObject creature; private String collectionBadgeName; - public GrantBadgeIntent(CreatureObject creature, String collectionBadgeName){ - super(TYPE); + public GrantBadgeIntent(CreatureObject creature, String collectionBadgeName) { this.creature = creature; this.collectionBadgeName = collectionBadgeName; } - public CreatureObject getCreature(){ + public CreatureObject getCreature() { return creature; } - public String getCollectionBadgeName(){ + public String getCollectionBadgeName() { return collectionBadgeName; } } diff --git a/src/intents/GrantClickyCollectionIntent.java b/src/intents/GrantClickyCollectionIntent.java index 616652953..49406b093 100644 --- a/src/intents/GrantClickyCollectionIntent.java +++ b/src/intents/GrantClickyCollectionIntent.java @@ -26,36 +26,32 @@ ******************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.SWGObject; import resources.objects.collections.CollectionItem; import resources.objects.creature.CreatureObject; -/** - * Created by Yakattak on 9/8/16. - */ -public class GrantClickyCollectionIntent extends Intent { - public static final String TYPE = "GrantClickyCollectionIntent"; +import com.projectswg.common.control.Intent; +public class GrantClickyCollectionIntent extends Intent { + private CreatureObject creature; private SWGObject inventoryItem; private CollectionItem collection; - + public GrantClickyCollectionIntent(CreatureObject creature, SWGObject inventoryItem, CollectionItem collection) { - super(TYPE); this.creature = creature; this.inventoryItem = inventoryItem; this.collection = collection; } - + public CreatureObject getCreature() { return creature; } - + public SWGObject getInventoryItem() { return inventoryItem; } - + public CollectionItem getCollection() { return collection; } diff --git a/src/intents/GroupEventIntent.java b/src/intents/GroupEventIntent.java index 960952403..f6c654588 100644 --- a/src/intents/GroupEventIntent.java +++ b/src/intents/GroupEventIntent.java @@ -24,48 +24,41 @@ * You should have received a copy of the GNU Affero General Public License * along with Holocore. If not, see ******************************************************************************/ - package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; import resources.player.Player; -/** - * Created by Waverunner on 10/4/2015 - */ -public class GroupEventIntent extends Intent { - public static final String TYPE = "GroupEventIntent"; +import com.projectswg.common.control.Intent; +public class GroupEventIntent extends Intent { private GroupEventType eventType; private Player player; private CreatureObject target; - + public GroupEventIntent(GroupEventType eventType, Player player, CreatureObject target) { - super(TYPE); this.eventType = eventType; this.player = player; this.target = target; } - + public GroupEventIntent(GroupEventType eventType, Player player) { - super(TYPE); this.eventType = eventType; this.player = player; } - + public GroupEventType getEventType() { return eventType; } - + public Player getPlayer() { return player; } - + public CreatureObject getTarget() { return target; } - + public enum GroupEventType { GROUP_INVITE, GROUP_UNINVITE, @@ -77,4 +70,4 @@ public class GroupEventIntent extends Intent { GROUP_KICK, GROUP_MAKE_MASTER_LOOTER } -} \ No newline at end of file +} diff --git a/src/intents/LoginEventIntent.java b/src/intents/LoginEventIntent.java index ef705b4c3..4a24bd20a 100644 --- a/src/intents/LoginEventIntent.java +++ b/src/intents/LoginEventIntent.java @@ -27,17 +27,14 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; +import com.projectswg.common.control.Intent; public class LoginEventIntent extends Intent { - public static final String TYPE = "LoginEventIntent"; - private LoginEvent event; private long networkId; public LoginEventIntent(long networkId, LoginEvent event) { - super(TYPE); setNetworkId(networkId); setEvent(event); } @@ -59,11 +56,7 @@ public class LoginEventIntent extends Intent { } public enum LoginEvent { - LOGIN_FAIL_INVALID_VERSION_CODE, - LOGIN_FAIL_INVALID_USER_PASS, - LOGIN_FAIL_SERVER_ERROR, - LOGIN_FAIL_BANNED, - LOGIN_SUCCESS + LOGIN_FAIL_INVALID_VERSION_CODE, LOGIN_FAIL_INVALID_USER_PASS, LOGIN_FAIL_SERVER_ERROR, LOGIN_FAIL_BANNED, LOGIN_SUCCESS } } diff --git a/src/intents/NotifyPlayersPacketIntent.java b/src/intents/NotifyPlayersPacketIntent.java index 64da68fae..12df53e5c 100644 --- a/src/intents/NotifyPlayersPacketIntent.java +++ b/src/intents/NotifyPlayersPacketIntent.java @@ -27,55 +27,64 @@ ***********************************************************************************/ package intents; -import network.packets.Packet; -import resources.Terrain; -import resources.control.Intent; -import resources.player.Player; - import java.util.List; -public class NotifyPlayersPacketIntent extends Intent { +import network.packets.Packet; +import resources.Terrain; +import resources.player.Player; - public static final String TYPE = "GalaxyWidePacketIntent"; +import com.projectswg.common.control.Intent; + +public class NotifyPlayersPacketIntent extends Intent { private Packet packet; private Terrain terrain; private List networkIds; private ConditionalNotify condition; - + public NotifyPlayersPacketIntent(Packet packet, Terrain terrain, ConditionalNotify condition, List networkIds) { - super(TYPE); this.packet = packet; this.terrain = terrain; this.condition = condition; this.networkIds = networkIds; } - + public NotifyPlayersPacketIntent(Packet packet, Terrain terrain, ConditionalNotify condition) { this(packet, terrain, condition, null); } - + public NotifyPlayersPacketIntent(Packet packet, ConditionalNotify condition, List networkIds) { this(packet, null, condition, networkIds); } - + public NotifyPlayersPacketIntent(Packet packet, List networkIds) { this(packet, null, null, networkIds); } - + public NotifyPlayersPacketIntent(Packet packet, Terrain terrain) { this(packet, terrain, null); } - + public NotifyPlayersPacketIntent(Packet p) { this(p, null, null, null); } - - public Packet getPacket() { return packet; } - public Terrain getTerrain() { return terrain; } - public ConditionalNotify getCondition() { return condition; } - public List getNetworkIds() { return networkIds; } - + + public Packet getPacket() { + return packet; + } + + public Terrain getTerrain() { + return terrain; + } + + public ConditionalNotify getCondition() { + return condition; + } + + public List getNetworkIds() { + return networkIds; + } + public interface ConditionalNotify { boolean meetsCondition(Player player); } diff --git a/src/intents/PlayerEventIntent.java b/src/intents/PlayerEventIntent.java index 246c58ebb..43698258e 100644 --- a/src/intents/PlayerEventIntent.java +++ b/src/intents/PlayerEventIntent.java @@ -27,19 +27,17 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; +import com.projectswg.common.control.Intent; + import resources.player.Player; import resources.player.PlayerEvent; public class PlayerEventIntent extends Intent { - public static final String TYPE = "PlayerEventIntent"; - private Player player; private PlayerEvent event; public PlayerEventIntent(Player p, PlayerEvent event) { - super(TYPE); setPlayer(p); setEvent(event); } diff --git a/src/intents/RequestZoneInIntent.java b/src/intents/RequestZoneInIntent.java index 2f5423b0f..9a1249192 100644 --- a/src/intents/RequestZoneInIntent.java +++ b/src/intents/RequestZoneInIntent.java @@ -27,19 +27,17 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; import resources.player.Player; +import com.projectswg.common.control.Intent; + public class RequestZoneInIntent extends Intent { - public static final String TYPE = "ZoneInIntent"; - private Player player; private CreatureObject creature; public RequestZoneInIntent(Player player, CreatureObject creature) { - super(TYPE); setPlayer(player); setCreature(creature); } diff --git a/src/intents/SkillModIntent.java b/src/intents/SkillModIntent.java index f8b147df9..317845960 100644 --- a/src/intents/SkillModIntent.java +++ b/src/intents/SkillModIntent.java @@ -27,12 +27,11 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -public class SkillModIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "SkillModIntent"; +public class SkillModIntent extends Intent { private final String skillModName; private final int adjustBase; @@ -40,21 +39,20 @@ public class SkillModIntent extends Intent { private final CreatureObject[] affectedCreatures; public SkillModIntent(String skillModName, int adjustBase, int adjustModifier, CreatureObject... affectedCreatures) { - super(TYPE); this.skillModName = skillModName; this.adjustBase = adjustBase; this.adjustModifier = adjustModifier; this.affectedCreatures = affectedCreatures; } - + public int getAdjustModifier() { return adjustModifier; } - + public int getAdjustBase() { return adjustBase; } - + public String getSkillModName() { return skillModName; } diff --git a/src/intents/WatchIntent.java b/src/intents/WatchIntent.java index d1085e73e..defe4145e 100644 --- a/src/intents/WatchIntent.java +++ b/src/intents/WatchIntent.java @@ -27,36 +27,31 @@ ***********************************************************************************/ package intents; -import resources.control.Intent; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public class WatchIntent extends Intent { - public static final String TYPE = "WatchIntent"; private final CreatureObject actor; private final SWGObject target; private final boolean startWatch; public WatchIntent(CreatureObject actor, SWGObject target, boolean startWatch) { - super(TYPE); this.actor = actor; this.target = target; this.startWatch = startWatch; } - + public CreatureObject getActor() { return actor; } - + public SWGObject getTarget() { return target; } - + public boolean isStartWatch() { return startWatch; } diff --git a/src/intents/chat/ChatAvatarRequestIntent.java b/src/intents/chat/ChatAvatarRequestIntent.java index 1e3902148..9f1c88c49 100644 --- a/src/intents/chat/ChatAvatarRequestIntent.java +++ b/src/intents/chat/ChatAvatarRequestIntent.java @@ -24,41 +24,36 @@ * You should have received a copy of the GNU Affero General Public License * along with Holocore. If not, see ******************************************************************************/ - package intents.chat; -import resources.control.Intent; import resources.player.Player; -/** - * @author Waverunner - */ -public class ChatAvatarRequestIntent extends Intent{ - public static final String TYPE = "ChatAvatarRequestIntent"; +import com.projectswg.common.control.Intent; +public class ChatAvatarRequestIntent extends Intent { + private Player player; private String target; private RequestType requestType; - + public ChatAvatarRequestIntent(Player player, String target, RequestType requestType) { - super(TYPE); this.player = player; this.target = target; this.requestType = requestType; } - + public Player getPlayer() { return player; } - + public String getTarget() { return target; } - + public RequestType getRequestType() { return requestType; } - + public enum RequestType { FRIEND_LIST, TARGET_STATUS, diff --git a/src/intents/chat/ChatBroadcastIntent.java b/src/intents/chat/ChatBroadcastIntent.java index f5ac2592c..119efe10a 100644 --- a/src/intents/chat/ChatBroadcastIntent.java +++ b/src/intents/chat/ChatBroadcastIntent.java @@ -27,15 +27,14 @@ ***********************************************************************************/ package intents.chat; +import com.projectswg.common.control.Intent; + import resources.Terrain; -import resources.control.Intent; import resources.encodables.ProsePackage; import resources.player.Player; public class ChatBroadcastIntent extends Intent { - public static final String TYPE = "ChatBroadcastIntent"; - private BroadcastType broadcastType; private Player broadcaster; private Terrain terrain; @@ -43,7 +42,6 @@ public class ChatBroadcastIntent extends Intent { private ProsePackage prose; public ChatBroadcastIntent(String message, Player broadcaster, Terrain terrain, BroadcastType type) { - super(TYPE); this.message = message; this.broadcastType = type; this.broadcaster = broadcaster; diff --git a/src/intents/chat/ChatCommandIntent.java b/src/intents/chat/ChatCommandIntent.java index 8a7969c41..9e8acf6aa 100644 --- a/src/intents/chat/ChatCommandIntent.java +++ b/src/intents/chat/ChatCommandIntent.java @@ -27,22 +27,20 @@ ***********************************************************************************/ package intents.chat; +import com.projectswg.common.control.Intent; + import resources.commands.Command; -import resources.control.Intent; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; public class ChatCommandIntent extends Intent { - public static final String TYPE = "ChatCommandIntent"; - private CreatureObject source; private SWGObject target; private Command command; private String [] arguments; public ChatCommandIntent() { - super(TYPE); setSource(null); setTarget(null); setCommand(null); @@ -50,7 +48,6 @@ public class ChatCommandIntent extends Intent { } public ChatCommandIntent(CreatureObject source, SWGObject target, Command command, String [] arguments) { - super(TYPE); setSource(source); setTarget(target); setCommand(command); diff --git a/src/intents/chat/ChatRoomUpdateIntent.java b/src/intents/chat/ChatRoomUpdateIntent.java index 79ae01c7a..4fa75891e 100644 --- a/src/intents/chat/ChatRoomUpdateIntent.java +++ b/src/intents/chat/ChatRoomUpdateIntent.java @@ -24,19 +24,15 @@ * You should have received a copy of the GNU Affero General Public License * along with Holocore. If not, see ******************************************************************************/ - package intents.chat; import resources.chat.ChatAvatar; -import resources.control.Intent; import resources.player.Player; -/** - * @author Waverunner - */ -public class ChatRoomUpdateIntent extends Intent { - public static final String TYPE = "ChatRoomUpdateIntent"; +import com.projectswg.common.control.Intent; +public class ChatRoomUpdateIntent extends Intent { + private String path; private String title; private String target; @@ -46,30 +42,36 @@ public class ChatRoomUpdateIntent extends Intent { private boolean isPublic; private Player player; private boolean ignoreInvitation; - + public ChatRoomUpdateIntent(String path, String title, String target, ChatAvatar avatar, String message, UpdateType updateType) { - super(TYPE); this.path = path; this.updateType = updateType; this.avatar = avatar; - - switch(updateType) { - case DESTROY: break; - case CREATE: this.title = title; break; - case SEND_MESSAGE: this.message = message; break; - default: this.target = target; break; + + switch (updateType) { + case DESTROY: + break; + case CREATE: + this.title = title; + break; + case SEND_MESSAGE: + this.message = message; + break; + default: + this.target = target; + break; } } - + public ChatRoomUpdateIntent(ChatAvatar avatar, String path, String title, boolean isPublic) { this(path, title, null, avatar, null, UpdateType.CREATE); this.isPublic = isPublic; } - + public ChatRoomUpdateIntent(ChatAvatar avatar, String path, UpdateType updateType) { this(path, null, null, avatar, null, updateType); } - + public ChatRoomUpdateIntent(Player player, String path, String title, String target, String message, UpdateType updateType, boolean ignoreInvitation) { this(path, title, target, ChatAvatar.getFromPlayer(player), message, updateType); this.player = player; @@ -84,35 +86,35 @@ public class ChatRoomUpdateIntent extends Intent { public String getPath() { return path; } - + public String getTitle() { return title; } - + public String getTarget() { return target; } - + public ChatAvatar getAvatar() { return avatar; } - + public String getMessage() { return message; } - + public UpdateType getUpdateType() { return updateType; } - + public boolean isPublic() { return isPublic; } - + public Player getPlayer() { return player; } - + public boolean isIgnoreInvitation() { return ignoreInvitation; } diff --git a/src/intents/chat/PersistentMessageIntent.java b/src/intents/chat/PersistentMessageIntent.java index d21e16c2e..79768e7b0 100644 --- a/src/intents/chat/PersistentMessageIntent.java +++ b/src/intents/chat/PersistentMessageIntent.java @@ -27,25 +27,32 @@ ***********************************************************************************/ package intents.chat; -import resources.control.Intent; import resources.encodables.player.Mail; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class PersistentMessageIntent extends Intent { - public static final String TYPE = "PersistentMessageIntent"; private Mail mail; private SWGObject receiver; private String galaxy; public PersistentMessageIntent(SWGObject receiver, Mail mail, String galaxy) { - super(TYPE); this.receiver = receiver; this.mail = mail; this.galaxy = galaxy; } - - public Mail getMail() { return this.mail; } - public SWGObject getReceiver() { return this.receiver; } - public String getGalaxy() { return this.galaxy; } + + public Mail getMail() { + return this.mail; + } + + public SWGObject getReceiver() { + return this.receiver; + } + + public String getGalaxy() { + return this.galaxy; + } } diff --git a/src/intents/chat/SpatialChatIntent.java b/src/intents/chat/SpatialChatIntent.java index 38aac9378..30613da39 100644 --- a/src/intents/chat/SpatialChatIntent.java +++ b/src/intents/chat/SpatialChatIntent.java @@ -27,27 +27,37 @@ ***********************************************************************************/ package intents.chat; -import resources.control.Intent; import resources.player.Player; -public class SpatialChatIntent extends Intent { - public static final String TYPE = "SpatialChatIntent"; +import com.projectswg.common.control.Intent; +public class SpatialChatIntent extends Intent { + private Player player; private String message; private int chatType; private int moodId; public SpatialChatIntent(Player player, int chatType, String message, int moodId) { - super(TYPE); this.player = player; this.message = message; this.chatType = chatType; this.moodId = moodId; } - - public Player getPlayer() { return player; } - public String getMessage() { return message; } - public int getChatType() { return chatType; } - public int getMoodId() { return moodId; } + + public Player getPlayer() { + return player; + } + + public String getMessage() { + return message; + } + + public int getChatType() { + return chatType; + } + + public int getMoodId() { + return moodId; + } } diff --git a/src/intents/combat/CreatureKilledIntent.java b/src/intents/combat/CreatureKilledIntent.java index dd830d403..0099463b9 100644 --- a/src/intents/combat/CreatureKilledIntent.java +++ b/src/intents/combat/CreatureKilledIntent.java @@ -27,28 +27,26 @@ ***********************************************************************************/ package intents.combat; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -public class CreatureKilledIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "CreatureKilledIntent"; +public class CreatureKilledIntent extends Intent { private final CreatureObject killer; private final CreatureObject corpse; - + public CreatureKilledIntent(CreatureObject killer, CreatureObject corpse) { - super(TYPE); this.killer = killer; this.corpse = corpse; } - + public CreatureObject getKiller() { return killer; } - + public CreatureObject getCorpse() { return corpse; } - + } diff --git a/src/intents/combat/DeathblowIntent.java b/src/intents/combat/DeathblowIntent.java index 67eb4d146..903e9ab01 100644 --- a/src/intents/combat/DeathblowIntent.java +++ b/src/intents/combat/DeathblowIntent.java @@ -27,30 +27,28 @@ ***********************************************************************************/ package intents.combat; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -public class DeathblowIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "DeathblowIntent"; +public class DeathblowIntent extends Intent { private final CreatureObject killer; private final CreatureObject corpse; - + /** * @param killer {@code CreatureObject} that's deathblowing * @param corpse {@code CreatureObject} that's being deathblown */ public DeathblowIntent(CreatureObject killer, CreatureObject corpse) { - super(TYPE); this.killer = killer; this.corpse = corpse; } - + public CreatureObject getKiller() { return killer; } - + public CreatureObject getCorpse() { return corpse; } diff --git a/src/intents/experience/ExperienceIntent.java b/src/intents/experience/ExperienceIntent.java index 084064381..4b1dafcd4 100644 --- a/src/intents/experience/ExperienceIntent.java +++ b/src/intents/experience/ExperienceIntent.java @@ -27,36 +27,30 @@ ***********************************************************************************/ package intents.experience; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public final class ExperienceIntent extends Intent { - public static final String TYPE = "ExperienceIntent"; - private final CreatureObject creatureObject; private final String xpType; private final int experienceGained; public ExperienceIntent(CreatureObject creatureObject, String xpType, int experienceGained) { - super(TYPE); this.creatureObject = creatureObject; this.xpType = xpType; this.experienceGained = experienceGained; } - + public CreatureObject getCreatureObject() { return creatureObject; } - + public String getXpType() { return xpType; } - + public int getExperienceGained() { return experienceGained; } diff --git a/src/intents/experience/GrantSkillIntent.java b/src/intents/experience/GrantSkillIntent.java index 71afc815b..fdb237498 100644 --- a/src/intents/experience/GrantSkillIntent.java +++ b/src/intents/experience/GrantSkillIntent.java @@ -27,48 +27,43 @@ ***********************************************************************************/ package intents.experience; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public final class GrantSkillIntent extends Intent { - public static final String TYPE = "GrantSkillIntent"; - private final IntentType intentType; private final String skillName; private final CreatureObject target; private final boolean grantRequiredSkills; public GrantSkillIntent(IntentType type, String skillName, CreatureObject target, boolean grantRequiredSkills) { - super(TYPE); this.intentType = type; this.skillName = skillName; this.target = target; this.grantRequiredSkills = grantRequiredSkills; } - + public IntentType getIntentType() { return intentType; } - + public String getSkillName() { return skillName; } - + public CreatureObject getTarget() { return target; } - + public boolean isGrantRequiredSkills() { return grantRequiredSkills; } public static enum IntentType { - GRANT, GIVEN + GRANT, + GIVEN } } diff --git a/src/intents/experience/LevelChangedIntent.java b/src/intents/experience/LevelChangedIntent.java index 2176816af..c0510f26e 100644 --- a/src/intents/experience/LevelChangedIntent.java +++ b/src/intents/experience/LevelChangedIntent.java @@ -27,38 +27,32 @@ ***********************************************************************************/ package intents.experience; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -/** - * - * @author Mads - */ +import com.projectswg.common.control.Intent; + public final class LevelChangedIntent extends Intent { - - public static final String TYPE = "LevelChangedIntent"; - + private final CreatureObject creatureObject; private final short previousLevel; private final short newLevel; - + public LevelChangedIntent(CreatureObject creatureObject, short previousLevel, short newLevel) { - super(TYPE); this.creatureObject = creatureObject; this.previousLevel = previousLevel; this.newLevel = newLevel; } - + public CreatureObject getCreatureObject() { return creatureObject; } - + public short getPreviousLevel() { return previousLevel; } - + public short getNewLevel() { return newLevel; } - + } diff --git a/src/intents/network/CloseConnectionIntent.java b/src/intents/network/CloseConnectionIntent.java index 3cf6d9eb4..062ef0dbb 100644 --- a/src/intents/network/CloseConnectionIntent.java +++ b/src/intents/network/CloseConnectionIntent.java @@ -27,19 +27,16 @@ ***********************************************************************************/ package intents.network; -import resources.control.Intent; import resources.network.DisconnectReason; +import com.projectswg.common.control.Intent; public class CloseConnectionIntent extends Intent { - public static final String TYPE = "CloseConnectionIntent"; - private long networkId; private DisconnectReason reason; public CloseConnectionIntent(long networkId, DisconnectReason reason) { - super(TYPE); setNetworkId(networkId); setDisconnectReason(reason); } diff --git a/src/intents/network/ConnectionClosedIntent.java b/src/intents/network/ConnectionClosedIntent.java index b4d95a20d..ba458a19b 100644 --- a/src/intents/network/ConnectionClosedIntent.java +++ b/src/intents/network/ConnectionClosedIntent.java @@ -28,18 +28,15 @@ package intents.network; import network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason; -import resources.control.Intent; +import com.projectswg.common.control.Intent; public class ConnectionClosedIntent extends Intent { - public static final String TYPE = "ConnectionClosedIntent"; - private long networkId; private ConnectionStoppedReason reason; public ConnectionClosedIntent(long networkId, ConnectionStoppedReason reason) { - super(TYPE); setNetworkId(networkId); setReason(reason); } diff --git a/src/intents/network/ConnectionOpenedIntent.java b/src/intents/network/ConnectionOpenedIntent.java index e46254cb6..2efa78b26 100644 --- a/src/intents/network/ConnectionOpenedIntent.java +++ b/src/intents/network/ConnectionOpenedIntent.java @@ -27,17 +27,13 @@ ***********************************************************************************/ package intents.network; -import resources.control.Intent; - +import com.projectswg.common.control.Intent; public class ConnectionOpenedIntent extends Intent { - public static final String TYPE = "ConnectionOpenedIntent"; - private long networkId; public ConnectionOpenedIntent(long networkId) { - super(TYPE); setNetworkId(networkId); } diff --git a/src/intents/network/GalacticPacketIntent.java b/src/intents/network/GalacticPacketIntent.java index 17bb14bd6..49aa7a36f 100644 --- a/src/intents/network/GalacticPacketIntent.java +++ b/src/intents/network/GalacticPacketIntent.java @@ -33,13 +33,10 @@ import network.packets.Packet; public class GalacticPacketIntent extends GalacticIntent { - public static final String TYPE = "GalacticPacketIntent"; - private Packet packet; private Player player; public GalacticPacketIntent(Packet packet, Player player) { - super(TYPE); setPacket(packet); setPlayer(player); } diff --git a/src/intents/network/InboundPacketIntent.java b/src/intents/network/InboundPacketIntent.java index cebbd0b5e..5fe0833bc 100644 --- a/src/intents/network/InboundPacketIntent.java +++ b/src/intents/network/InboundPacketIntent.java @@ -28,17 +28,15 @@ package intents.network; import network.packets.Packet; -import resources.control.Intent; + +import com.projectswg.common.control.Intent; public class InboundPacketIntent extends Intent { - public static final String TYPE = "InboundPacketIntent"; - private Packet packet; private long networkId; public InboundPacketIntent(Packet p, long networkId) { - super(TYPE); setPacket(p); setNetworkId(networkId); } diff --git a/src/intents/network/InboundPacketPendingIntent.java b/src/intents/network/InboundPacketPendingIntent.java index 237d806b7..3e1e63ef8 100644 --- a/src/intents/network/InboundPacketPendingIntent.java +++ b/src/intents/network/InboundPacketPendingIntent.java @@ -28,12 +28,11 @@ package intents.network; import network.NetworkClient; -import resources.control.Intent; + +import com.projectswg.common.control.Intent; public class InboundPacketPendingIntent extends Intent { - public static final String TYPE = "InboundPacketPendingIntent"; - private NetworkClient client; public InboundPacketPendingIntent() { @@ -41,7 +40,6 @@ public class InboundPacketPendingIntent extends Intent { } public InboundPacketPendingIntent(NetworkClient client) { - super(TYPE); setClient(client); } diff --git a/src/intents/network/OutboundPacketIntent.java b/src/intents/network/OutboundPacketIntent.java index fab4e69dc..b28b33775 100644 --- a/src/intents/network/OutboundPacketIntent.java +++ b/src/intents/network/OutboundPacketIntent.java @@ -28,17 +28,15 @@ package intents.network; import network.packets.Packet; -import resources.control.Intent; + +import com.projectswg.common.control.Intent; public class OutboundPacketIntent extends Intent { - public static final String TYPE = "OutboundPacketIntent"; - private Packet packet; private long networkId; public OutboundPacketIntent(Packet p, long networkId) { - super(TYPE); setPacket(p); setNetworkId(networkId); } diff --git a/src/intents/network/PingIntent.java b/src/intents/network/PingIntent.java deleted file mode 100644 index 5ea5a8595..000000000 --- a/src/intents/network/PingIntent.java +++ /dev/null @@ -1,40 +0,0 @@ -/*********************************************************************************** -* 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 intents.network; - -import resources.control.Intent; - -public class PingIntent extends Intent { - - public static final String TYPE = "PingIntent"; - - public PingIntent() { - super(TYPE); - } - -} diff --git a/src/intents/object/ContainerTransferIntent.java b/src/intents/object/ContainerTransferIntent.java index 7372a5d76..4927fb666 100644 --- a/src/intents/object/ContainerTransferIntent.java +++ b/src/intents/object/ContainerTransferIntent.java @@ -27,13 +27,12 @@ ***********************************************************************************/ package intents.object; -import resources.control.Intent; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class ContainerTransferIntent extends Intent { - public static final String TYPE = "ContainerTransferIntent"; - private SWGObject obj; private SWGObject container; @@ -42,7 +41,6 @@ public class ContainerTransferIntent extends Intent { } public ContainerTransferIntent(SWGObject obj, SWGObject container) { - super(TYPE); setObject(obj); setContainer(container); } diff --git a/src/intents/object/CreateStaticItemIntent.java b/src/intents/object/CreateStaticItemIntent.java index bb3e9ee6a..361f9db58 100644 --- a/src/intents/object/CreateStaticItemIntent.java +++ b/src/intents/object/CreateStaticItemIntent.java @@ -27,45 +27,39 @@ ***********************************************************************************/ package intents.object; -import resources.control.Intent; import resources.objects.SWGObject; import services.objects.StaticItemService.ObjectCreationHandler; -/** - * - * @author mads - */ +import com.projectswg.common.control.Intent; + public final class CreateStaticItemIntent extends Intent { - public static final String TYPE = "CreateStaticItemIntent"; - private final SWGObject requester; private final SWGObject container; private final ObjectCreationHandler objectCreationHandler; private final String[] itemNames; public CreateStaticItemIntent(SWGObject requester, SWGObject container, ObjectCreationHandler objectCreationHandler, String... itemNames) { - super(TYPE); this.requester = requester; this.container = container; this.objectCreationHandler = objectCreationHandler; this.itemNames = itemNames; } - + public SWGObject getContainer() { return container; } - + public String[] getItemNames() { return itemNames; } - + public SWGObject getRequester() { return requester; } - + public ObjectCreationHandler getObjectCreationHandler() { return objectCreationHandler; } -} \ No newline at end of file +} diff --git a/src/intents/object/DestroyObjectIntent.java b/src/intents/object/DestroyObjectIntent.java index 957f16209..188e1c1de 100644 --- a/src/intents/object/DestroyObjectIntent.java +++ b/src/intents/object/DestroyObjectIntent.java @@ -27,17 +27,15 @@ ***********************************************************************************/ package intents.object; -import resources.control.Intent; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class DestroyObjectIntent extends Intent { - public static final String TYPE = "DestroyObjectIntent"; - private final SWGObject obj; public DestroyObjectIntent(SWGObject obj) { - super(TYPE); this.obj = obj; } diff --git a/src/intents/object/MoveObjectIntent.java b/src/intents/object/MoveObjectIntent.java index f3b8b6a52..4a3a7e9e8 100644 --- a/src/intents/object/MoveObjectIntent.java +++ b/src/intents/object/MoveObjectIntent.java @@ -28,13 +28,12 @@ package intents.object; import resources.Location; -import resources.control.Intent; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class MoveObjectIntent extends Intent { - public static final String TYPE = "MoveObjectIntent"; - private SWGObject object; private SWGObject parent; private Location newLocation; @@ -42,7 +41,6 @@ public class MoveObjectIntent extends Intent { private int updateCounter; public MoveObjectIntent(SWGObject object, Location newLocation, double speed, int updateCounter) { - super(TYPE); this.object = object; this.parent = null; this.newLocation = newLocation; @@ -51,7 +49,6 @@ public class MoveObjectIntent extends Intent { } public MoveObjectIntent(SWGObject object, SWGObject parent, Location newLocation, double speed, int updateCounter) { - super(TYPE); this.object = object; this.parent = parent; this.newLocation = newLocation; diff --git a/src/intents/object/ObjectCreatedIntent.java b/src/intents/object/ObjectCreatedIntent.java index b1d017285..4b4f57533 100644 --- a/src/intents/object/ObjectCreatedIntent.java +++ b/src/intents/object/ObjectCreatedIntent.java @@ -27,17 +27,15 @@ ***********************************************************************************/ package intents.object; -import resources.control.Intent; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class ObjectCreatedIntent extends Intent { - public static final String TYPE = "ObjectCreatedIntent"; - private final SWGObject obj; public ObjectCreatedIntent(SWGObject obj) { - super(TYPE); this.obj = obj; } diff --git a/src/intents/object/ObjectTeleportIntent.java b/src/intents/object/ObjectTeleportIntent.java index bc593cfa0..c34e3deec 100644 --- a/src/intents/object/ObjectTeleportIntent.java +++ b/src/intents/object/ObjectTeleportIntent.java @@ -28,26 +28,23 @@ package intents.object; import resources.Location; -import resources.control.Intent; import resources.objects.SWGObject; +import com.projectswg.common.control.Intent; + public class ObjectTeleportIntent extends Intent { - public static final String TYPE = "ObjectTeleportIntent"; - private SWGObject object; private SWGObject parent; private Location newLocation; public ObjectTeleportIntent(SWGObject object, Location newLocation) { - super(TYPE); this.object = object; this.parent = null; this.newLocation = newLocation; } public ObjectTeleportIntent(SWGObject object, SWGObject parent, Location newLocation) { - super(TYPE); this.object = object; this.parent = parent; this.newLocation = newLocation; @@ -56,7 +53,7 @@ public class ObjectTeleportIntent extends Intent { public SWGObject getObject() { return object; } - + public void setObject(SWGObject object) { this.object = object; } @@ -68,13 +65,13 @@ public class ObjectTeleportIntent extends Intent { public void setParent(SWGObject parent) { this.parent = parent; } - + public Location getNewLocation() { return newLocation; } - + public void setNewLocation(Location newLocation) { this.newLocation = newLocation; - } - + } + } diff --git a/src/intents/player/DeleteCharacterIntent.java b/src/intents/player/DeleteCharacterIntent.java index fb78bbfd6..ece2f126a 100644 --- a/src/intents/player/DeleteCharacterIntent.java +++ b/src/intents/player/DeleteCharacterIntent.java @@ -26,17 +26,15 @@ ******************************************************************************/ package intents.player; -import resources.control.Intent; import resources.objects.creature.CreatureObject; +import com.projectswg.common.control.Intent; + public class DeleteCharacterIntent extends Intent { - public static final String TYPE = "DeleteCharacterIntent"; - private final CreatureObject creature; public DeleteCharacterIntent(CreatureObject creature) { - super(TYPE); this.creature = creature; } diff --git a/src/intents/player/PlayerTransformedIntent.java b/src/intents/player/PlayerTransformedIntent.java index bc62b7409..ceac63e71 100644 --- a/src/intents/player/PlayerTransformedIntent.java +++ b/src/intents/player/PlayerTransformedIntent.java @@ -27,16 +27,15 @@ ***********************************************************************************/ package intents.player; +import com.projectswg.common.control.Intent; + import resources.Location; import resources.Terrain; -import resources.control.Intent; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; public class PlayerTransformedIntent extends Intent { - public static final String TYPE = "PlayerTransformedIntent"; - private final CreatureObject object; private final SWGObject oldParent; private final SWGObject newParent; @@ -44,7 +43,6 @@ public class PlayerTransformedIntent extends Intent { private final Location newLocation; public PlayerTransformedIntent(CreatureObject object, SWGObject oldParent, SWGObject newParent, Location oldLocation, Location newLocation) { - super(TYPE); this.object = object; this.oldParent = oldParent; this.newParent = newParent; diff --git a/src/intents/radial/RadialRegisterIntent.java b/src/intents/radial/RadialRegisterIntent.java index 0346f39e0..b568c9071 100644 --- a/src/intents/radial/RadialRegisterIntent.java +++ b/src/intents/radial/RadialRegisterIntent.java @@ -29,22 +29,18 @@ package intents.radial; import java.util.Set; -import resources.control.Intent; +import com.projectswg.common.control.Intent; public class RadialRegisterIntent extends Intent { - - public static final String TYPE = "RadialRegisterIntent"; private Set templates; private boolean register; public RadialRegisterIntent(boolean register) { - super(TYPE); setRegister(register); } public RadialRegisterIntent(Set templates, boolean register) { - super(TYPE); setTemplates(templates); setRegister(register); } diff --git a/src/intents/radial/RadialRequestIntent.java b/src/intents/radial/RadialRequestIntent.java index 2b26330bd..5785a4bd0 100644 --- a/src/intents/radial/RadialRequestIntent.java +++ b/src/intents/radial/RadialRequestIntent.java @@ -28,20 +28,18 @@ package intents.radial; import network.packets.swg.zone.object_controller.ObjectMenuRequest; -import resources.control.Intent; import resources.objects.SWGObject; import resources.player.Player; +import com.projectswg.common.control.Intent; + public class RadialRequestIntent extends Intent { - public static final String TYPE = "RadialRequestIntent"; - private Player player; private SWGObject target; private ObjectMenuRequest request; public RadialRequestIntent(Player player, SWGObject target, ObjectMenuRequest request) { - super(TYPE); setPlayer(player); setTarget(target); setRequest(request); diff --git a/src/intents/radial/RadialResponseIntent.java b/src/intents/radial/RadialResponseIntent.java index 8d822e477..110775877 100644 --- a/src/intents/radial/RadialResponseIntent.java +++ b/src/intents/radial/RadialResponseIntent.java @@ -29,22 +29,20 @@ package intents.radial; import java.util.List; -import resources.control.Intent; import resources.objects.SWGObject; import resources.player.Player; import resources.radial.RadialOption; +import com.projectswg.common.control.Intent; + public class RadialResponseIntent extends Intent { - public static final String TYPE = "RadialResponseIntent"; - private Player player; private SWGObject target; private List options; private int counter; public RadialResponseIntent(Player player, SWGObject target, List options, int counter) { - super(TYPE); setPlayer(player); setTarget(target); setOptions(options); diff --git a/src/intents/radial/RadialSelectionIntent.java b/src/intents/radial/RadialSelectionIntent.java index 731853da4..7e37ff43b 100644 --- a/src/intents/radial/RadialSelectionIntent.java +++ b/src/intents/radial/RadialSelectionIntent.java @@ -27,21 +27,19 @@ ***********************************************************************************/ package intents.radial; -import resources.control.Intent; import resources.objects.SWGObject; import resources.player.Player; import resources.radial.RadialItem; +import com.projectswg.common.control.Intent; + public class RadialSelectionIntent extends Intent { - public static final String TYPE = "RadialSelectionIntent"; - private Player player; private SWGObject target; private RadialItem selection; public RadialSelectionIntent(Player player, SWGObject target, RadialItem selection) { - super(TYPE); setPlayer(player); setTarget(target); setSelection(selection); diff --git a/src/intents/server/ConfigChangedIntent.java b/src/intents/server/ConfigChangedIntent.java index e1caf0a30..3a15a423b 100644 --- a/src/intents/server/ConfigChangedIntent.java +++ b/src/intents/server/ConfigChangedIntent.java @@ -27,19 +27,18 @@ ***********************************************************************************/ package intents.server; +import com.projectswg.common.control.Intent; + import resources.config.ConfigFile; -import resources.control.Intent; public final class ConfigChangedIntent extends Intent { - - public static final String TYPE = "ConfigChangedIntent"; - private final ConfigFile changedConfig; - private final String key, oldValue, newValue; - public ConfigChangedIntent(ConfigFile changedConfig, - String key, String oldValue, String newValue) { - super(TYPE); - + private final ConfigFile changedConfig; + private final String key; + private final String oldValue; + private final String newValue; + + public ConfigChangedIntent(ConfigFile changedConfig, String key, String oldValue, String newValue) { this.changedConfig = changedConfig; this.key = key; this.oldValue = oldValue; diff --git a/src/intents/server/ServerManagementIntent.java b/src/intents/server/ServerManagementIntent.java index e9170a0d2..29e73d794 100644 --- a/src/intents/server/ServerManagementIntent.java +++ b/src/intents/server/ServerManagementIntent.java @@ -29,11 +29,11 @@ package intents.server; import java.util.concurrent.TimeUnit; -import resources.control.Intent; import resources.player.Player; +import com.projectswg.common.control.Intent; + public class ServerManagementIntent extends Intent { - public static final String TYPE = "ServerManagementIntent"; private Player player; private String target; @@ -42,7 +42,6 @@ public class ServerManagementIntent extends Intent { private TimeUnit timeUnit; public ServerManagementIntent(Player p, String target, ServerManagementEvent event) { - super(TYPE); this.player = p; this.target = target; this.event = event; @@ -54,11 +53,25 @@ public class ServerManagementIntent extends Intent { this.timeUnit = timeUnit; } - public long getTime() { return time; } - public TimeUnit getTimeUnit() { return timeUnit; } - public Player getPlayer() { return player; } - public String getTarget() { return target; } - public ServerManagementEvent getEvent() { return event; } + public long getTime() { + return time; + } + + public TimeUnit getTimeUnit() { + return timeUnit; + } + + public Player getPlayer() { + return player; + } + + public String getTarget() { + return target; + } + + public ServerManagementEvent getEvent() { + return event; + } public enum ServerManagementEvent { LOCK, @@ -68,4 +81,5 @@ public class ServerManagementIntent extends Intent { BAN, UNBAN } + } diff --git a/src/intents/server/ServerStatusIntent.java b/src/intents/server/ServerStatusIntent.java index bf227ea27..72f43700c 100644 --- a/src/intents/server/ServerStatusIntent.java +++ b/src/intents/server/ServerStatusIntent.java @@ -29,24 +29,21 @@ package intents.server; import java.util.concurrent.TimeUnit; -import resources.control.Intent; import resources.control.ServerStatus; +import com.projectswg.common.control.Intent; + public class ServerStatusIntent extends Intent { - public static final String TYPE = "ServerStatusIntent"; - private ServerStatus status; private long time; private TimeUnit timeUnit; public ServerStatusIntent() { - super(TYPE); - setStatus(null); + this(null); } public ServerStatusIntent(ServerStatus status) { - super(TYPE); setStatus(status); } @@ -59,7 +56,7 @@ public class ServerStatusIntent extends Intent { public void setTime(long time) { this.time = time; } - + public long getTime() { return time; } @@ -71,7 +68,7 @@ public class ServerStatusIntent extends Intent { public ServerStatus getStatus() { return status; } - + public TimeUnit getTimeUnit() { return timeUnit; } diff --git a/src/intents/sui/SuiWindowIntent.java b/src/intents/sui/SuiWindowIntent.java index a3acdbb58..be3102657 100644 --- a/src/intents/sui/SuiWindowIntent.java +++ b/src/intents/sui/SuiWindowIntent.java @@ -27,34 +27,44 @@ ***********************************************************************************/ package intents.sui; -import resources.control.Intent; import resources.player.Player; import resources.sui.SuiBaseWindow; +import com.projectswg.common.control.Intent; + public class SuiWindowIntent extends Intent { - public static final String TYPE = "SuiWindowIntent"; private SuiBaseWindow window; private int windowId; private Player player; private SuiWindowEvent event; - + public SuiWindowIntent(Player player, SuiBaseWindow window, int windowId, SuiWindowEvent event) { - super(TYPE); this.player = player; this.window = window; this.windowId = windowId; this.event = event; } - + public SuiWindowIntent(Player player, SuiBaseWindow window, SuiWindowEvent event) { this(player, window, -1, event); } - - public SuiBaseWindow getWindow() { return this.window; } - public int getWindowId() { return windowId; } - public Player getPlayer() { return this.player; } - public SuiWindowEvent getEvent() { return event; } + + public SuiBaseWindow getWindow() { + return this.window; + } + + public int getWindowId() { + return windowId; + } + + public Player getPlayer() { + return this.player; + } + + public SuiWindowEvent getEvent() { + return event; + } public enum SuiWindowEvent { NEW, diff --git a/src/intents/travel/TicketPurchaseIntent.java b/src/intents/travel/TicketPurchaseIntent.java index 970e1a487..ac674960e 100644 --- a/src/intents/travel/TicketPurchaseIntent.java +++ b/src/intents/travel/TicketPurchaseIntent.java @@ -27,12 +27,11 @@ ***********************************************************************************/ package intents.travel; -import resources.control.Intent; import resources.objects.creature.CreatureObject; -public final class TicketPurchaseIntent extends Intent { +import com.projectswg.common.control.Intent; - public static final String TYPE = "TicketPurchaseIntent"; +public final class TicketPurchaseIntent extends Intent { private final CreatureObject purchaser; private final boolean roundTrip; @@ -40,7 +39,6 @@ public final class TicketPurchaseIntent extends Intent { private final String destinationPlanet; public TicketPurchaseIntent(CreatureObject purchaser, String destinationPlanet, String destinationName, boolean roundTrip) { - super(TYPE); this.purchaser = purchaser; this.destinationPlanet = destinationPlanet; this.destinationName = destinationName; diff --git a/src/intents/travel/TicketUseIntent.java b/src/intents/travel/TicketUseIntent.java index 0bb6be14f..42ae54125 100644 --- a/src/intents/travel/TicketUseIntent.java +++ b/src/intents/travel/TicketUseIntent.java @@ -27,24 +27,21 @@ ***********************************************************************************/ package intents.travel; -import resources.control.Intent; import resources.objects.SWGObject; import resources.player.Player; +import com.projectswg.common.control.Intent; + public class TicketUseIntent extends Intent { - public static final String TYPE = "TicketUseIntent"; - private final Player player; private final SWGObject ticket; - public TicketUseIntent(Player player) { this(player, null); } public TicketUseIntent(Player player, SWGObject ticket) { - super(TYPE); this.player = player; this.ticket = ticket; } diff --git a/src/intents/travel/TravelPointSelectionIntent.java b/src/intents/travel/TravelPointSelectionIntent.java index e4e28278a..5234dcc63 100644 --- a/src/intents/travel/TravelPointSelectionIntent.java +++ b/src/intents/travel/TravelPointSelectionIntent.java @@ -27,18 +27,16 @@ ***********************************************************************************/ package intents.travel; -import resources.control.Intent; import resources.objects.creature.CreatureObject; +import com.projectswg.common.control.Intent; + public class TravelPointSelectionIntent extends Intent { - public static final String TYPE = "TravelPointSelectionIntent"; - private CreatureObject creature; private boolean instant; public TravelPointSelectionIntent(CreatureObject creature, boolean instant) { - super(TYPE); this.creature = creature; this.instant = instant; } diff --git a/src/main/ProjectSWG.java b/src/main/ProjectSWG.java index e85e88ffe..eb97c0cc8 100644 --- a/src/main/ProjectSWG.java +++ b/src/main/ProjectSWG.java @@ -27,6 +27,8 @@ ***********************************************************************************/ package main; +import intents.server.ServerStatusIntent; + import java.lang.Thread.State; import java.util.HashMap; import java.util.List; @@ -34,13 +36,15 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import intents.server.ServerStatusIntent; import resources.Galaxy.GalaxyStatus; -import resources.control.IntentManager; import resources.control.ServerStatus; +import resources.server_info.DataManager; import resources.server_info.Log; import services.CoreManager; +import com.projectswg.common.concurrency.Delay; +import com.projectswg.common.control.IntentManager; + public class ProjectSWG { private static ProjectSWG server; @@ -59,7 +63,7 @@ public class ProjectSWG { server.forceShutdown(); }, "main-shutdown-hook")); try { - startupIntentManager(); + startupStaticClasses(); server.run(args); } catch (Throwable t) { Log.e(t); @@ -68,7 +72,7 @@ public class ProjectSWG { server.stop(); server.terminate(); } finally { - shutdownIntentManager(); + shutdownStaticClasses(); printFinalPswgState(); Log.i("Server shut down."); if (!forcingShutdown.get()) @@ -93,12 +97,14 @@ public class ProjectSWG { return (long) (System.currentTimeMillis()/1E3 - 1309996800L); // Date is 07/07/2011 GMT } - private static void startupIntentManager() { + private static void startupStaticClasses() { + DataManager.initialize(); IntentManager.getInstance().initialize(); } - private static void shutdownIntentManager() { + private static void shutdownStaticClasses() { IntentManager.getInstance().terminate(); + DataManager.terminate(); } private static void printFinalPswgState() { @@ -207,11 +213,8 @@ public class ProjectSWG { private void loop() { setStatus((manager.getGalaxyStatus() == GalaxyStatus.UP) ? ServerStatus.OPEN : ServerStatus.LOCKED); while (!shutdownRequested && !manager.isShutdownRequested() && manager.isOperational()) { - try { - Thread.sleep(50); - } catch (InterruptedException e) { - throw new CoreException("Main Thread Interrupted."); - } + if (!Delay.sleepMicro(50)) + throw new CoreException("Main Thread Interrupted"); } } diff --git a/src/network/NetworkClient.java b/src/network/NetworkClient.java index afeb4e8e2..ec57ee04c 100644 --- a/src/network/NetworkClient.java +++ b/src/network/NetworkClient.java @@ -36,18 +36,19 @@ import java.net.SocketAddress; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import resources.control.Assert; -import resources.network.NetBufferStream; -import resources.server_info.Log; -import services.network.HolocoreSessionManager; -import services.network.NetworkProtocol; -import services.network.PacketSender; -import services.network.HolocoreSessionManager.ResponseAction; -import utilities.IntentChain; import network.packets.Packet; import network.packets.swg.admin.AdminPacket; import network.packets.swg.holo.HoloConnectionStopped; import network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason; +import resources.network.NetBufferStream; +import resources.server_info.Log; +import services.network.HolocoreSessionManager; +import services.network.HolocoreSessionManager.ResponseAction; +import services.network.NetworkProtocol; +import services.network.PacketSender; + +import com.projectswg.common.control.IntentChain; +import com.projectswg.common.debug.Assert; public class NetworkClient { diff --git a/src/network/packets/swg/zone/resource/ResourceWeight.java b/src/network/packets/swg/zone/resource/ResourceWeight.java index a833a2601..b187f541f 100644 --- a/src/network/packets/swg/zone/resource/ResourceWeight.java +++ b/src/network/packets/swg/zone/resource/ResourceWeight.java @@ -34,9 +34,10 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import resources.control.Assert; import network.packets.swg.zone.object_controller.ObjectController; +import com.projectswg.common.debug.Assert; + public class ResourceWeight extends ObjectController { public static final int CRC = 0x0207; diff --git a/src/resources/callback/BaseCallbackManager.java b/src/resources/callback/BaseCallbackManager.java deleted file mode 100644 index 663031878..000000000 --- a/src/resources/callback/BaseCallbackManager.java +++ /dev/null @@ -1,106 +0,0 @@ -/*********************************************************************************** -* 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 resources.callback; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import utilities.ThreadUtilities; - -class BaseCallbackManager { - - private final Object executorMutex; - private final String name; - private final int threadCount; - private final AtomicBoolean shutdown; - private ExecutorService executor; - - public BaseCallbackManager(String name) { - this(name, 1); - } - - public BaseCallbackManager(String name, int threadCount) { - this.executorMutex = new Object(); - this.name = name; - this.threadCount = threadCount; - this.shutdown = new AtomicBoolean(false); - this.executor = null; - } - - public void start() { - synchronized (executorMutex) { - if (executor != null && !shutdown.get()) - executor.shutdown(); - shutdown.set(false); - if (threadCount <= 1) - executor = Executors.newSingleThreadExecutor(ThreadUtilities.newThreadFactory(name+"-callback-manager")); - else - executor = Executors.newFixedThreadPool(threadCount, ThreadUtilities.newThreadFactory(name+"-callback-manager-%d")); - } - } - - public void stop() { - synchronized (executorMutex) { - executor.shutdown(); - shutdown.set(true); - } - } - - public boolean awaitTermination(long timeout, TimeUnit unit) { - synchronized (executorMutex) { - if (executor == null) - return true; - if (!shutdown.get()) - throw new IllegalStateException("Cannot wait for termination when never stopped!"); - try { - return executor.awaitTermination(timeout, unit); - } catch (InterruptedException e) { - return false; - } - } - } - - protected boolean call(Runnable r) { - synchronized (executorMutex) { - if (executor == null) - throw new IllegalStateException("Manager has not been started!"); - if (shutdown.get()) - return false; - try { - executor.execute(r); - return true; - } catch (RejectedExecutionException e) { - return false; - } - } - } - -} diff --git a/src/resources/callback/CallbackManager.java b/src/resources/callback/CallbackManager.java deleted file mode 100644 index b03614a82..000000000 --- a/src/resources/callback/CallbackManager.java +++ /dev/null @@ -1,110 +0,0 @@ -/*********************************************************************************** -* 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 resources.callback; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import resources.control.Assert; -import resources.server_info.Log; - -public class CallbackManager extends BaseCallbackManager { - - private final List callbacks; - private final AtomicInteger running; - - public CallbackManager(String name) { - this(name, 1); - } - - public CallbackManager(String name, int threadCount) { - super(name, threadCount); - this.callbacks = new ArrayList<>(); - this.running = new AtomicInteger(0); - } - - public void addCallback(T callback) { - synchronized (callbacks) { - callbacks.add(callback); - } - } - - public void removeCallback(T callback) { - synchronized (callbacks) { - callbacks.remove(callback); - } - } - - public void setCallback(T callback) { - synchronized (callbacks) { - callbacks.clear(); - callbacks.add(callback); - } - } - - public void clearCallbacks() { - synchronized (callbacks) { - callbacks.clear(); - } - } - - public boolean isQueueEmpty() { - Assert.test(running.get() >= 0); - return running.get() == 0; - } - - public boolean callOnEach(CallCallback call) { - synchronized (callbacks) { - running.incrementAndGet(); - boolean ret = call(() -> { - try { - for (T callback : callbacks) { - try { - call.run(callback); - } catch (Throwable t) { - Log.e(t); - } - } - } catch (Throwable t) { - Log.e(t); - } finally { - running.decrementAndGet(); - } - }); - if (!ret) - running.decrementAndGet(); - return ret; - } - } - - - public interface CallCallback { - void run(T callback); - } -} diff --git a/src/resources/collections/SWGList.java b/src/resources/collections/SWGList.java index 94820430b..5045eeed0 100644 --- a/src/resources/collections/SWGList.java +++ b/src/resources/collections/SWGList.java @@ -29,12 +29,10 @@ package resources.collections; import network.packets.Packet; import network.packets.swg.zone.baselines.Baseline.BaselineType; -import resources.control.Assert; import resources.encodables.Encodable; import resources.network.NetBuffer; import resources.objects.SWGObject; import resources.server_info.Log; -import resources.server_info.SynchronizedList; import utilities.Encoder; import utilities.Encoder.StringType; @@ -45,6 +43,9 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import com.projectswg.common.concurrency.SynchronizedList; +import com.projectswg.common.debug.Assert; + /** * Supports a list of elements which automatically sends data as a delta when changed for baselines. * diff --git a/src/resources/collections/SWGMap.java b/src/resources/collections/SWGMap.java index 00aedba7f..fb41dc797 100644 --- a/src/resources/collections/SWGMap.java +++ b/src/resources/collections/SWGMap.java @@ -27,20 +27,20 @@ ***********************************************************************************/ package resources.collections; -import resources.encodables.Encodable; -import resources.network.NetBuffer; -import resources.objects.SWGObject; -import resources.server_info.Log; -import resources.server_info.SynchronizedMap; -import utilities.Encoder; -import utilities.Encoder.StringType; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import network.packets.Packet; +import resources.encodables.Encodable; +import resources.network.NetBuffer; +import resources.objects.SWGObject; +import resources.server_info.Log; +import utilities.Encoder; +import utilities.Encoder.StringType; + +import com.projectswg.common.concurrency.SynchronizedMap; public class SWGMap extends SynchronizedMap implements Encodable { diff --git a/src/resources/collections/SWGSet.java b/src/resources/collections/SWGSet.java index 4e06a443a..cac052d7e 100644 --- a/src/resources/collections/SWGSet.java +++ b/src/resources/collections/SWGSet.java @@ -27,17 +27,6 @@ ***********************************************************************************/ package resources.collections; -import network.packets.Packet; -import network.packets.swg.zone.baselines.Baseline; -import resources.encodables.Encodable; -import resources.network.NetBuffer; -import resources.objects.SWGObject; -import resources.server_info.Log; -import resources.server_info.SynchronizedList; -import resources.server_info.SynchronizedSet; -import utilities.Encoder; -import utilities.Encoder.StringType; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.AbstractSet; @@ -47,6 +36,18 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import network.packets.Packet; +import network.packets.swg.zone.baselines.Baseline; +import resources.encodables.Encodable; +import resources.network.NetBuffer; +import resources.objects.SWGObject; +import resources.server_info.Log; +import utilities.Encoder; +import utilities.Encoder.StringType; + +import com.projectswg.common.concurrency.SynchronizedList; +import com.projectswg.common.concurrency.SynchronizedSet; + public class SWGSet extends SynchronizedSet implements Encodable { private final int view; diff --git a/src/resources/commands/callbacks/GotoCmdCallback.java b/src/resources/commands/callbacks/GotoCmdCallback.java index 131f1381a..b21f015bc 100644 --- a/src/resources/commands/callbacks/GotoCmdCallback.java +++ b/src/resources/commands/callbacks/GotoCmdCallback.java @@ -41,11 +41,12 @@ import resources.objects.building.BuildingObject; import resources.objects.cell.CellObject; import resources.player.Player; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import services.galaxy.GalacticManager; import services.objects.ObjectManager; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + public class GotoCmdCallback implements ICmdCallback { @Override diff --git a/src/resources/commands/callbacks/QaToolCmdCallback.java b/src/resources/commands/callbacks/QaToolCmdCallback.java index 80fdf672b..f33d01d2a 100644 --- a/src/resources/commands/callbacks/QaToolCmdCallback.java +++ b/src/resources/commands/callbacks/QaToolCmdCallback.java @@ -30,11 +30,16 @@ package resources.commands.callbacks; import intents.chat.ChatBroadcastIntent; import intents.experience.ExperienceIntent; import intents.network.CloseConnectionIntent; +import intents.object.CreateStaticItemIntent; import intents.object.DestroyObjectIntent; import intents.object.ObjectTeleportIntent; -import intents.object.CreateStaticItemIntent; import intents.player.DeleteCharacterIntent; + import java.io.FileNotFoundException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + import resources.Location; import resources.Terrain; import resources.commands.ICmdCallback; @@ -45,8 +50,6 @@ import resources.objects.cell.CellObject; import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import resources.sui.ISuiCallback; import resources.sui.SuiButtons; import resources.sui.SuiEvent; @@ -55,13 +58,12 @@ import resources.sui.SuiListBox; import resources.sui.SuiMessageBox; import services.galaxy.GalacticManager; import services.objects.ObjectManager; +import services.objects.StaticItemService.ObjectCreationHandler; import services.player.PlayerManager; import utilities.Scripts; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Map; -import services.objects.StaticItemService.ObjectCreationHandler; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; /** * Created by Waverunner on 8/19/2015 diff --git a/src/resources/commands/callbacks/TransferItemCallback.java b/src/resources/commands/callbacks/TransferItemCallback.java index e0243af2e..1b75b72fe 100644 --- a/src/resources/commands/callbacks/TransferItemCallback.java +++ b/src/resources/commands/callbacks/TransferItemCallback.java @@ -30,7 +30,6 @@ package resources.commands.callbacks; import intents.chat.ChatBroadcastIntent; import resources.commands.ICmdCallback; -import resources.control.Assert; import resources.objects.GameObjectType; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; @@ -38,6 +37,8 @@ import resources.objects.weapon.WeaponObject; import resources.player.Player; import services.galaxy.GalacticManager; +import com.projectswg.common.debug.Assert; + /** * This callback is used for all three kinds of transfer commands. The commands * differ based on object type (misc, armor and weapon). We should NOT trust diff --git a/src/resources/concurrency/PswgBasicScheduledThread.java b/src/resources/concurrency/PswgBasicScheduledThread.java deleted file mode 100644 index 6763d2028..000000000 --- a/src/resources/concurrency/PswgBasicScheduledThread.java +++ /dev/null @@ -1,64 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -public class PswgBasicScheduledThread extends PswgScheduledThreadPool { - - private final Runnable runnable; - - public PswgBasicScheduledThread(String name, Runnable runnable) { - super(1, name); - this.runnable = runnable; - } - - @Override - public void start() { - throw new UnsupportedOperationException("Cannot use this function. Must use startX(initialDelay, periodicDelay)"); - } - - public void startWithFixedRate(long initialDelay, long periodicDelay) { - super.start(); - super.executeWithFixedRate(initialDelay, periodicDelay, runnable); - } - - public void startWithFixedDelay(long initialDelay, long periodicDelay) { - super.start(); - super.executeWithFixedDelay(initialDelay, periodicDelay, runnable); - } - - @Override - public void executeWithFixedRate(long initialDelay, long periodicDelay, Runnable runnable) { - throw new UnsupportedOperationException("Runnable is defined in the constructor!"); - } - - @Override - public void executeWithFixedDelay(long initialDelay, long periodicDelay, Runnable runnable) { - throw new UnsupportedOperationException("Runnable is defined in the constructor!"); - } - -} diff --git a/src/resources/concurrency/PswgBasicThread.java b/src/resources/concurrency/PswgBasicThread.java deleted file mode 100644 index 66ff6f4fa..000000000 --- a/src/resources/concurrency/PswgBasicThread.java +++ /dev/null @@ -1,65 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class PswgBasicThread extends PswgThreadPool { - - private final AtomicBoolean executing; - private final Runnable runnable; - - public PswgBasicThread(String name, Runnable runnable) { - super(1, name); - this.executing = new AtomicBoolean(false); - this.runnable = runnable; - } - - @Override - public void start() { - super.start(); - super.execute(() -> { - executing.set(true); - try { - runnable.run(); - } finally { - executing.set(false); - } - }); - } - - public boolean isExecuting() { - return executing.get(); - } - - @Override - public boolean execute(Runnable runnable) { - throw new UnsupportedOperationException("Runnable is defined in the constructor!"); - } - -} diff --git a/src/resources/concurrency/PswgScheduledThreadPool.java b/src/resources/concurrency/PswgScheduledThreadPool.java deleted file mode 100644 index 9fc7b106e..000000000 --- a/src/resources/concurrency/PswgScheduledThreadPool.java +++ /dev/null @@ -1,115 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import resources.control.Assert; - -public class PswgScheduledThreadPool { - - private final AtomicBoolean running; - private final int nThreads; - private final ThreadFactory threadFactory; - private ScheduledExecutorService executor; - - public PswgScheduledThreadPool(int nThreads, String nameFormat) { - this.running = new AtomicBoolean(false); - this.nThreads = nThreads; - this.threadFactory = new CustomThreadFactory(nameFormat); - this.executor = null; - } - - public void start() { - Assert.test(!running.getAndSet(true)); - executor = Executors.newScheduledThreadPool(nThreads, threadFactory); - } - - public void stop() { - Assert.test(running.getAndSet(false)); - executor.shutdownNow(); - } - - public void executeWithFixedRate(long initialDelay, long time, Runnable runnable) { - Assert.test(running.get()); - executor.scheduleAtFixedRate(() -> { - try { - runnable.run(); - } catch (Throwable t) { - t.printStackTrace(); - } - }, initialDelay, time, TimeUnit.MILLISECONDS); - } - - public void executeWithFixedDelay(long initialDelay, long time, Runnable runnable) { - Assert.test(running.get()); - executor.scheduleWithFixedDelay(() -> { - try { - runnable.run(); - } catch (Throwable t) { - t.printStackTrace(); - } - }, initialDelay, time, TimeUnit.MILLISECONDS); - } - - public boolean awaitTermination(long time) { - Assert.notNull(executor); - try { - return executor.awaitTermination(time, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - return false; - } - } - - private static class CustomThreadFactory implements ThreadFactory { - - private final String pattern; - private int counter; - - public CustomThreadFactory(String pattern) { - this.pattern = pattern; - this.counter = 0; - } - - @Override - public Thread newThread(Runnable r) { - String name; - if (pattern.contains("%d")) - name = String.format(pattern, counter++); - else - name = pattern; - return new Thread(r, name); - } - - } - -} diff --git a/src/resources/concurrency/PswgTaskThreadPool.java b/src/resources/concurrency/PswgTaskThreadPool.java deleted file mode 100644 index 400dffd3b..000000000 --- a/src/resources/concurrency/PswgTaskThreadPool.java +++ /dev/null @@ -1,73 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.ArrayDeque; -import java.util.Queue; - -public class PswgTaskThreadPool extends PswgThreadPool { - - private final Queue tasks; - private final Runnable runner; - - public PswgTaskThreadPool(int nThreads, String namePattern, TaskExecutor executor) { - super(nThreads, namePattern); - this.tasks = new ArrayDeque<>(); - this.runner = () -> { - T t = null; - synchronized (tasks) { - t = tasks.poll(); - } - if (t != null) - executor.run(t); - }; - } - - @Override - public boolean execute(Runnable runnable) { - throw new UnsupportedOperationException("Runnable are posted automatically by addTask!"); - } - - public void addTask(T t) { - synchronized (tasks) { - tasks.add(t); - } - super.execute(runner); - } - - public int getTasks() { - synchronized (tasks) { - return tasks.size(); - } - } - - public interface TaskExecutor { - void run(T t); - } - -} diff --git a/src/resources/concurrency/PswgThreadPool.java b/src/resources/concurrency/PswgThreadPool.java deleted file mode 100644 index e91b55190..000000000 --- a/src/resources/concurrency/PswgThreadPool.java +++ /dev/null @@ -1,119 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import resources.control.Assert; - -public class PswgThreadPool { - - private final AtomicBoolean running; - private final int nThreads; - private final ThreadFactory threadFactory; - private ExecutorService executor; - private volatile boolean waitForTermination; - - public PswgThreadPool(int nThreads, String nameFormat) { - this.running = new AtomicBoolean(false); - this.nThreads = nThreads; - this.threadFactory = new CustomThreadFactory(nameFormat); - this.executor = null; - this.waitForTermination = false; - } - - public void setWaitForTermination(boolean wait) { - this.waitForTermination = wait; - } - - public void start() { - Assert.test(!running.getAndSet(true)); - executor = Executors.newFixedThreadPool(nThreads, threadFactory); - } - - public void stop() { - Assert.test(running.getAndSet(false)); - if (waitForTermination) - executor.shutdownNow(); - else - executor.shutdown(); - } - - public boolean execute(Runnable runnable) { - if (!running.get()) - return false; - executor.execute(() -> { - try { - runnable.run(); - } catch (Throwable t) { - t.printStackTrace(); - } - }); - return true; - } - - public boolean awaitTermination(long time) { - Assert.notNull(executor); - try { - return executor.awaitTermination(time, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - return false; - } - } - - public boolean isRunning() { - return running.get(); - } - - private static class CustomThreadFactory implements ThreadFactory { - - private final String pattern; - private int counter; - - public CustomThreadFactory(String pattern) { - this.pattern = pattern; - this.counter = 0; - } - - @Override - public Thread newThread(Runnable r) { - String name; - if (pattern.contains("%d")) - name = String.format(pattern, counter++); - else - name = pattern; - return new Thread(r, name); - } - - } - -} diff --git a/src/resources/concurrency/SmartLock.java b/src/resources/concurrency/SmartLock.java deleted file mode 100644 index 30f0eb8fc..000000000 --- a/src/resources/concurrency/SmartLock.java +++ /dev/null @@ -1,129 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.Date; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public class SmartLock { - - private final Lock lock; - private final Condition condition; - - public SmartLock() { - this.lock = new ReentrantLock(true); - this.condition = lock.newCondition(); - } - - public void lock() { - lock.lock(); - } - - public void lockInterruptibly() throws InterruptedException { - lock.lockInterruptibly(); - } - - public boolean tryLock() { - return lock.tryLock(); - } - - public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { - return lock.tryLock(time, unit); - } - - public void unlock() { - lock.unlock(); - } - - public void await() throws InterruptedException { - lock(); - try { - condition.await(); - } finally { - unlock(); - } - } - - public void awaitUninterruptibly() { - lock(); - try { - condition.awaitUninterruptibly(); - } finally { - unlock(); - } - } - - public long awaitNanos(long nanosTimeout) throws InterruptedException { - lock(); - try { - return condition.awaitNanos(nanosTimeout); - } finally { - unlock(); - } - } - - public boolean await(long time, TimeUnit unit) throws InterruptedException { - lock(); - try { - return condition.await(time, unit); - } finally { - unlock(); - } - } - - public boolean awaitUntil(Date deadline) throws InterruptedException { - lock(); - try { - return condition.awaitUntil(deadline); - } finally { - unlock(); - } - } - - public void signal() { - lock(); - try { - condition.signal(); - } finally { - unlock(); - } - } - - public void signalAll() { - lock(); - try { - condition.signalAll(); - } finally { - unlock(); - } - } - -} diff --git a/src/resources/concurrency/SynchronizedQueue.java b/src/resources/concurrency/SynchronizedQueue.java deleted file mode 100644 index 098527c81..000000000 --- a/src/resources/concurrency/SynchronizedQueue.java +++ /dev/null @@ -1,151 +0,0 @@ -/*********************************************************************************** -* 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 resources.concurrency; - -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Iterator; -import java.util.Queue; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -public class SynchronizedQueue implements Queue { - - private final Queue queue; - - public SynchronizedQueue() { - this(new ArrayDeque<>()); - } - - public SynchronizedQueue(Queue queue) { - this.queue = queue; - } - - public synchronized void forEach(Consumer action) { - queue.forEach(action); - } - - public synchronized boolean add(T e) { - return queue.add(e); - } - - public synchronized boolean offer(T e) { - return queue.offer(e); - } - - public synchronized int size() { - return queue.size(); - } - - public synchronized boolean isEmpty() { - return queue.isEmpty(); - } - - public synchronized boolean contains(Object o) { - return queue.contains(o); - } - - public synchronized T remove() { - return queue.remove(); - } - - public synchronized T poll() { - return queue.poll(); - } - - public synchronized T element() { - return queue.element(); - } - - public synchronized Iterator iterator() { - return queue.iterator(); - } - - public synchronized T peek() { - return queue.peek(); - } - - public synchronized Object[] toArray() { - return queue.toArray(); - } - - public synchronized E[] toArray(E[] a) { - return queue.toArray(a); - } - - public synchronized boolean remove(Object o) { - return queue.remove(o); - } - - public synchronized boolean containsAll(Collection c) { - return queue.containsAll(c); - } - - public synchronized boolean addAll(Collection c) { - return queue.addAll(c); - } - - public synchronized boolean removeAll(Collection c) { - return queue.removeAll(c); - } - - public synchronized boolean removeIf(Predicate filter) { - return queue.removeIf(filter); - } - - public synchronized boolean retainAll(Collection c) { - return queue.retainAll(c); - } - - public synchronized void clear() { - queue.clear(); - } - - public synchronized boolean equals(Object o) { - return queue.equals(o); - } - - public synchronized int hashCode() { - return queue.hashCode(); - } - - public synchronized Spliterator spliterator() { - return queue.spliterator(); - } - - public synchronized Stream stream() { - return queue.stream(); - } - - public synchronized Stream parallelStream() { - return queue.parallelStream(); - } - -} diff --git a/src/resources/control/Assert.java b/src/resources/control/Assert.java deleted file mode 100644 index d76ee55f6..000000000 --- a/src/resources/control/Assert.java +++ /dev/null @@ -1,117 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import resources.server_info.Log; - -public class Assert { - - private static volatile AssertLevel level = AssertLevel.ASSERT; - - public static void setLevel(AssertLevel level) { - Assert.level = level; - } - - public static boolean debug() { - return level != AssertLevel.IGNORE; - } - - public static void notNull(Object o) { - notNull(o, ""); - } - - public static void notNull(Object o, String message) { - if (debug() && o == null) - handle(new NullPointerException(message)); - } - - public static void isNull(Object o) { - isNull(o, ""); - } - - public static void isNull(Object o, String message) { - if (debug() && o != null) - handle(new AssertionException(message)); - } - - public static void test(boolean expr) { - test(expr, ""); - } - - public static void test(boolean expr, String message) { - if (debug() && !expr) - handle(new AssertionException(message)); - } - - public static void fail() { - fail(""); - } - - public static void fail(String message) { - if (debug()) - handle(new AssertionException(message)); - } - - private static void handle(RuntimeException e) { - AssertLevel level = Assert.level; - switch (level) { - case WARN: - warn(e); - break; - case ASSERT: - throw e; - default: - break; - } - } - - private static void warn(Exception e) { - StackTraceElement [] elements = e.getStackTrace(); - if (elements.length <= 1) - Log.e(e); - else - Log.e(e); - } - - private static class AssertionException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public AssertionException(String message) { - super(message); - } - - } - - public enum AssertLevel { - IGNORE, - WARN, - ASSERT - } - -} diff --git a/src/resources/control/Intent.java b/src/resources/control/Intent.java deleted file mode 100644 index 13aba1c1d..000000000 --- a/src/resources/control/Intent.java +++ /dev/null @@ -1,151 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - - -public abstract class Intent { - - private final String type; - private boolean broadcasted; - private boolean complete; - private Intent parallel; - private Intent sequential; - - protected Intent(String type) { - this.type = type; - this.broadcasted = false; - this.complete = false; - this.parallel = null; - this.sequential = null; - } - - /** - * Called when the intent has been completed - */ - public synchronized void markAsComplete() { - this.complete = true; - if (sequential != null) - sequential.broadcast(); - sequential = null; - parallel = null; - } - - /** - * Returns the type of intent - * @return the type of intent - */ - public synchronized String getType() { - return type; - } - - /** - * Determines whether or not the intent has been broadcasted and processed - * by the system - * @return TRUE if the intent has been broadcasted and processed, FALSE - * otherwise - */ - public synchronized boolean isComplete() { - return complete; - } - - /** - * Determines whether or not the intent has been broadcasted to the system - * @return TRUE if the intent has been broadcasted, FALSE otherwise - */ - public synchronized boolean isBroadcasted() { - return broadcasted; - } - - /** - * Waits for the intent as the parameter to finish before this intent - * starts - * @param i the intent to execute after - */ - public synchronized void broadcastAfterIntent(Intent i) { - if (i == null) { - broadcast(); - return; - } - synchronized (i) { - if (i.isComplete()) - broadcast(); - else - i.setAsSequential(this); - } - } - - /** - * Waits for the intent as the parameter to start before this intent starts - * @param i the intent to execute with - */ - public synchronized void broadcastWithIntent(Intent i) { - if (i == null) { - broadcast(); - return; - } - synchronized (i) { - if (!isComplete()) { - setAsParallel(i); - } - broadcast(); - } - } - - /** - * Broadcasts this node to the system - */ - public synchronized void broadcast() { - if (broadcasted) - throw new IllegalStateException("Intent has already been broadcasted!"); - broadcasted = true; - IntentManager.getInstance().broadcastIntent(this); - if (parallel != null) - parallel.broadcast(); - parallel = null; - } - - @Override - public synchronized String toString() { - return getType(); - } - - private synchronized void setAsParallel(Intent i) { - if (parallel == null) - parallel = i; - else - parallel.setAsParallel(i); - } - - private synchronized void setAsSequential(Intent i) { - if (sequential == null) - sequential = i; - else - sequential.setAsParallel(i); - } - -} diff --git a/src/resources/control/IntentManager.java b/src/resources/control/IntentManager.java deleted file mode 100644 index 7f87c6597..000000000 --- a/src/resources/control/IntentManager.java +++ /dev/null @@ -1,140 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import resources.concurrency.PswgTaskThreadPool; -import resources.server_info.Log; - - -public class IntentManager { - - private static final IntentManager INSTANCE = new IntentManager(); - - private final Map > intentRegistrations; - private final PswgTaskThreadPool broadcastThreads; - private final AtomicBoolean initialized; - - private IntentManager() { - intentRegistrations = new HashMap>(); - initialized = new AtomicBoolean(false); - int threadCount = Runtime.getRuntime().availableProcessors() * 10; - broadcastThreads = new PswgTaskThreadPool<>(threadCount, "intent-processor-%d", i -> broadcast(i)); - broadcastThreads.setWaitForTermination(true); - initialize(); - } - - public void initialize() { - if (initialized.getAndSet(true)) - return; - broadcastThreads.start(); - } - - public void terminate() { - if (!initialized.getAndSet(false)) - return; - broadcastThreads.stop(); - broadcastThreads.awaitTermination(5000); - } - - protected void broadcastIntent(Intent i) { - if (i == null) - throw new NullPointerException("Intent cannot be null!"); - broadcastThreads.addTask(i); - } - - public void registerForIntent(String intentType, IntentReceiver r) { - if (r == null) - throw new NullPointerException("Cannot register a null value for an intent"); - synchronized (intentRegistrations) { - List intents = intentRegistrations.get(intentType); - if (intents == null) { - intents = new CopyOnWriteArrayList<>(); - intentRegistrations.put(intentType, intents); - } - synchronized (intents) { - intents.add(r); - } - } - } - - protected void unregisterForIntent(String intentType, IntentReceiver r) { - if (r == null) - return; - synchronized (intentRegistrations) { - if (!intentRegistrations.containsKey(intentType)) - return; - List receivers = intentRegistrations.get(intentType); - for (IntentReceiver recv : receivers) { - if (r == recv || r.equals(recv)) { - r = recv; - break; - } - } - receivers.remove(r); - } - } - - private void broadcast(Intent i) { - List receivers; - synchronized (intentRegistrations) { - receivers = intentRegistrations.get(i.getType()); - } - if (receivers == null) { - i.markAsComplete(); - return; - } - for (IntentReceiver r : receivers) { - broadcast(r, i); - } - i.markAsComplete(); - } - - private void broadcast(IntentReceiver r, Intent i) { - try { - r.onIntentReceived(i); - } catch (Throwable t) { - Log.e("Fatal Exception while processing intent: " + i); - Log.e(t); - } - } - - public static int getIntentsQueued() { - return getInstance().broadcastThreads.getTasks(); - } - - public static IntentManager getInstance() { - return INSTANCE; - } - -} diff --git a/src/resources/control/IntentQueue.java b/src/resources/control/IntentQueue.java deleted file mode 100644 index 84c79906b..000000000 --- a/src/resources/control/IntentQueue.java +++ /dev/null @@ -1,235 +0,0 @@ -/************************************************************************************ - * 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 resources.control; - -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.NoSuchElementException; -import java.util.Queue; - -class IntentQueue implements Queue { - - private final Node head; - private int size; - private int modificationCount; - - public IntentQueue() { - head = new Node(null, null, null); // Left = Forward, Right = Reverse - head.left = head; - head.right = head; - modificationCount = 0; - } - - @Override - public int size() { - return size; - } - - @Override - public boolean isEmpty() { - return size == 0; - } - - @Override - public boolean contains(Object o) { - Node n = head; - while (n.left != head) { - if (n.left.intent == o) - return true; - } - return false; - } - - @Override - public Iterator iterator() { - return new Iterator(); - } - - @Override - public Object [] toArray() { - return null; - } - - @Override - public T [] toArray(T [] a) { - return null; - } - - @Override - public boolean remove(Object o) { - modificationCount++; - Node n = head; - while (n.left != head) { - if (n.left.intent == o) { - n.left = n.left.left; - n.left.right = n; - } - } - return false; - } - - @Override - public boolean containsAll(Collection c) { - for (Object o : c) - if (!contains(o)) - return false; - return true; - } - - @Override - public boolean addAll(Collection c) { - boolean added = false; - for (Intent i : c) - added = add(i) || added; - return added; - } - - @Override - public boolean removeAll(Collection c) { - boolean changed = false; - for (Object o : c) - changed = remove(o) || changed; - return changed; - } - - @Override - public boolean retainAll(Collection c) { - boolean changed = false; - Node n = head; - while (n.left != head) { - if (!c.contains(n.left.intent)) { - n.left.left.right = n; - n.left = n.left.left; - } - } - return changed; - } - - @Override - public void clear() { - modificationCount++; - size = 0; - head.left = head; - head.right = head; - } - - @Override - public boolean add(Intent e) { - head.right.left = new Node(e, head, head.right); - head.right = head.right.left; - modificationCount++; - size++; - return true; - } - - @Override - public boolean offer(Intent e) { - return add(e); - } - - @Override - public Intent remove() { - if (isEmpty()) - throw new NoSuchElementException("Queue is empty!"); - modificationCount++; - Intent i = head.left.intent; - head.left = head.left.left; - head.left.right = head; - size--; - return i; - } - - @Override - public Intent poll() { - if (isEmpty()) - return null; - modificationCount++; - Intent i = head.left.intent; - head.left = head.left.left; - head.left.right = head; - size--; - return i; - } - - @Override - public Intent element() { - if (isEmpty()) - throw new NoSuchElementException("Queue is empty!"); - return head.left.intent; - } - - @Override - public Intent peek() { - if (isEmpty()) - return null; - return head.left.intent; - } - - private class Iterator implements java.util.Iterator { - - private final int modificationCount; - private Node currentNode; - - public Iterator() { - this.modificationCount = IntentQueue.this.modificationCount; - this.currentNode = IntentQueue.this.head; - } - - @Override - public boolean hasNext() { - if (this.modificationCount != IntentQueue.this.modificationCount) - throw new ConcurrentModificationException(); - return currentNode.left != IntentQueue.this.head; - } - - @Override - public Intent next() { - if (!hasNext()) - throw new NoSuchElementException("Iterator has reached the end of the queue!"); - Intent i = currentNode.left.intent; - currentNode = currentNode.left; - return i; - } - - } - - private static class Node { - - public final Intent intent; - public Node left; - public Node right; - - public Node(Intent intent, Node left, Node right) { - this.intent = intent; - this.left = left; - this.right = right; - } - - } - -} diff --git a/src/resources/control/IntentReceiver.java b/src/resources/control/IntentReceiver.java deleted file mode 100644 index b234a1c58..000000000 --- a/src/resources/control/IntentReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - - -public interface IntentReceiver { - - /** - * This function will be called if an intent is broadcasted and this - * manager is listening for it, or if this manager is specifically given - * this intent. - * @param i the intent received - */ - public void onIntentReceived(Intent i); - -} diff --git a/src/resources/control/Manager.java b/src/resources/control/Manager.java deleted file mode 100644 index ea98d97b3..000000000 --- a/src/resources/control/Manager.java +++ /dev/null @@ -1,183 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import java.util.HashSet; -import java.util.Set; - -import resources.server_info.Log; - - -/** - * A Manager is a class that will manage services, and generally controls the - * program as a whole - */ -public abstract class Manager extends Service { - - private final Set children; - - public Manager() { - children = new HashSet<>(); - } - - /** - * Initializes this manager. If the manager returns false on this method - * then the initialization failed and may not work as intended. This will - * initialize all children automatically. - * @return TRUE if initialization was successful, FALSE otherwise - */ - @Override - public boolean initialize() { - boolean success = super.initialize(); - synchronized (children) { - for (Service child : children) { - if (!child.initialize()) { - Log.e(child.getClass().getSimpleName() + " failed to initialize!"); - success = false; - break; - } - } - } - return success; - } - - /** - * Starts this manager. If the manager returns false on this method then - * the manger failed to start and may not work as intended. This will start - * all children automatically. - * @return TRUE if starting was successful, FALSE otherwise - */ - @Override - public boolean start() { - boolean success = super.start(); - synchronized (children) { - for (Service child : children) { - if (!child.start()) { - Log.e(child.getClass().getSimpleName() + " failed to start!"); - success = false; - break; - } - } - } - return success; - } - - /** - * Stops this manager. If the manager returns false on this method then - * the manger failed to stop and may not have fully locked down. This will - * start all children automatically. - * @return TRUE if stopping was successful, FALSE otherwise - */ - @Override - public boolean stop() { - boolean success = super.stop(), cSuccess = true; - synchronized (children) { - for (Service child : children) { - if (!success) - break; - cSuccess = child.stop(); - if (!cSuccess) { - Log.e(child.getClass().getSimpleName() + " failed to stop!"); - success = false; - } - } - } - return success; - } - - /** - * Terminates this manager. If the manager returns false on this method - * then the manager failed to shut down and resources may not have been - * cleaned up. This will terminate all children automatically. - * @return TRUE if termination was successful, FALSE otherwise - */ - @Override - public boolean terminate() { - boolean success = super.terminate(); - synchronized (children) { - for (Service child : children) { - if (!child.terminate()) - success = false; - } - } - return success; - } - - /** - * Determines whether or not this manager is operational - * @return TRUE if this manager is operational, FALSE otherwise - */ - @Override - public boolean isOperational() { - boolean success = true; - synchronized (children) { - for (Service child : children) { - if (!child.isOperational()) - success = false; - } - } - return success; - } - - /** - * Adds a child to the manager's list of children. This creates a tree of - * services that allows information to propogate freely through the network - * in an easy way. - * @param s the service to add as a child. - */ - protected final void addChildService(Service s) { - if (s == null) - throw new NullPointerException("Child service cannot be null!"); - synchronized (children) { - children.add(s); - } - } - - /** - * Removes the service from the list of children - * @param s the service to remove - */ - protected final void removeChildService(Service s) { - if (s == null) - return; - synchronized (children) { - children.remove(s); - } - } - - /** - * Returns a copied {@code Set] of the children of this manager - * @return a copied {@code Set] of the children of this manager - */ - public Set getManagerChildren() { - synchronized (children) { - return new HashSet<>(children); - } - } - -} diff --git a/src/resources/control/Service.java b/src/resources/control/Service.java deleted file mode 100644 index 4ebaa7172..000000000 --- a/src/resources/control/Service.java +++ /dev/null @@ -1,138 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import java.util.function.Consumer; - -import resources.config.ConfigFile; -import resources.server_info.Config; -import resources.server_info.DataManager; -import resources.server_info.Log; - - -/** - * A Service is a class that does a specific job for the application - */ -public abstract class Service implements IntentReceiver { - - public Service() { - - } - - /** - * Initializes this service. If the service returns false on this method - * then the initialization failed and may not work as intended. - * @return TRUE if initialization was successful, FALSE otherwise - */ - public boolean initialize() { - return DataManager.getInstance().isInitialized(); - } - - /** - * Starts this service. If the service returns false on this method then - * the service failed to start and may not work as intended. - * @return TRUE if starting was successful, FALSE otherwise - */ - public boolean start() { - return DataManager.getInstance().isInitialized(); - } - - /** - * Stops the service. If the service returns false on this method then the - * service failed to stop and may not have fully locked down. - * @return TRUE if stopping was successful, FALSe otherwise - */ - public boolean stop() { - return true; - } - - /** - * Terminates this service. If the service returns false on this method - * then the service failed to shut down and resources may not have been - * cleaned up. - * @return TRUE if termination was successful, FALSE otherwise - */ - public boolean terminate() { - DataManager.terminate(); - return true; - } - - /** - * Determines whether or not this service is operational - * @return TRUE if this service is operational, FALSE otherwise - */ - public boolean isOperational() { - return true; - } - - /** - * Registers for the specified intent string - * @param type the intent string - */ - protected final void registerForIntent(String type) { - IntentManager.getInstance().registerForIntent(type, this); - } - - @SuppressWarnings("unchecked") - protected final void registerForIntent(Class c, Consumer consumer) { - try { - String type = (String) c.getDeclaredField("TYPE").get(null); - IntentManager.getInstance().registerForIntent(type, (i) -> consumer.accept((T) i)); - } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { - e.printStackTrace(); - } - } - - /** - * Unregisters for the specified intent string - * @param type the intent string - */ - protected final void unregisterForIntent(String type) { - IntentManager.getInstance().unregisterForIntent(type, this); - } - - /** - * Callback when an intent is received from the system - */ - @Override - public void onIntentReceived(Intent i) { - Log.w("Warning: " + getClass().getSimpleName() + " did not override onIntentReceived"); - } - - /** - * Gets the config object associated with a certain file, or NULL if the - * file failed to load on startup - * @param file the file to get the config for - * @return the config object associated with the file, or NULL if the - * config failed to load - */ - public synchronized final Config getConfig(ConfigFile file) { - return DataManager.getInstance().getConfig(file); - } - -} diff --git a/src/resources/objects/SWGObject.java b/src/resources/objects/SWGObject.java index 7f6faa72f..4784c2559 100644 --- a/src/resources/objects/SWGObject.java +++ b/src/resources/objects/SWGObject.java @@ -27,6 +27,18 @@ ***********************************************************************************/ package resources.objects; +import intents.object.ContainerTransferIntent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + import network.packets.Packet; import network.packets.swg.zone.SceneCreateObjectByCrc; import network.packets.swg.zone.SceneDestroyObject; @@ -40,7 +52,6 @@ import resources.client_info.visitors.ObjectData.ObjectDataAttribute; import resources.common.CRC; import resources.containers.ContainerPermissionsType; import resources.containers.ContainerResult; -import resources.control.Assert; import resources.encodables.StringId; import resources.network.BaselineBuilder; import resources.network.BaselineObject; @@ -54,22 +65,13 @@ import resources.persistable.Persistable; import resources.persistable.SWGObjectFactory; import resources.player.Player; import resources.server_info.Log; -import resources.server_info.SynchronizedMap; -import resources.server_info.SynchronizedSet; import services.CoreManager; import services.objects.ObjectCreator; import utilities.AwarenessUtilities; -import intents.object.ContainerTransferIntent; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.concurrency.SynchronizedSet; +import com.projectswg.common.debug.Assert; public abstract class SWGObject extends BaselineObject implements Comparable, Persistable { diff --git a/src/resources/objects/awareness/AwarenessHandler.java b/src/resources/objects/awareness/AwarenessHandler.java index 91b710ef9..ff0b84815 100644 --- a/src/resources/objects/awareness/AwarenessHandler.java +++ b/src/resources/objects/awareness/AwarenessHandler.java @@ -32,11 +32,12 @@ import java.util.Map; import resources.Location; import resources.Terrain; -import resources.control.Assert; import resources.objects.SWGObject; import resources.objects.awareness.TerrainMap.TerrainMapCallback; import resources.server_info.Log; +import com.projectswg.common.debug.Assert; + public class AwarenessHandler implements AutoCloseable { private final Map terrains; diff --git a/src/resources/objects/awareness/TerrainMap.java b/src/resources/objects/awareness/TerrainMap.java index d0d03adef..f3bfd4736 100644 --- a/src/resources/objects/awareness/TerrainMap.java +++ b/src/resources/objects/awareness/TerrainMap.java @@ -32,13 +32,14 @@ import java.util.Set; import resources.Location; import resources.Terrain; -import resources.callback.CallbackManager; -import resources.control.Assert; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.waypoint.WaypointObject; import resources.server_info.Log; +import com.projectswg.common.callback.CallbackManager; +import com.projectswg.common.debug.Assert; + public class TerrainMap { private static final int CHUNK_COUNT_ACROSS = 16; diff --git a/src/resources/objects/awareness/TerrainMapChunk.java b/src/resources/objects/awareness/TerrainMapChunk.java index 332c6a0be..0e11145a1 100644 --- a/src/resources/objects/awareness/TerrainMapChunk.java +++ b/src/resources/objects/awareness/TerrainMapChunk.java @@ -34,7 +34,8 @@ import java.util.Set; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; -import resources.server_info.SynchronizedSet; + +import com.projectswg.common.concurrency.SynchronizedSet; class TerrainMapChunk { diff --git a/src/resources/objects/building/BuildingObject.java b/src/resources/objects/building/BuildingObject.java index be349956d..020a943a8 100644 --- a/src/resources/objects/building/BuildingObject.java +++ b/src/resources/objects/building/BuildingObject.java @@ -27,24 +27,26 @@ ***********************************************************************************/ package resources.objects.building; -import network.packets.swg.zone.baselines.Baseline.BaselineType; -import resources.Location; -import resources.client_info.ClientFactory; -import resources.client_info.visitors.PortalLayoutData; -import resources.client_info.visitors.ObjectData.ObjectDataAttribute; -import resources.control.Assert; -import resources.network.NetBufferStream; -import resources.objects.SWGObject; -import resources.objects.cell.CellObject; -import resources.objects.tangible.TangibleObject; -import resources.server_info.SynchronizedMap; -import services.objects.ObjectCreator; import intents.object.ObjectCreatedIntent; import java.util.ArrayList; import java.util.List; import java.util.Map; +import network.packets.swg.zone.baselines.Baseline.BaselineType; +import resources.Location; +import resources.client_info.ClientFactory; +import resources.client_info.visitors.ObjectData.ObjectDataAttribute; +import resources.client_info.visitors.PortalLayoutData; +import resources.network.NetBufferStream; +import resources.objects.SWGObject; +import resources.objects.cell.CellObject; +import resources.objects.tangible.TangibleObject; +import services.objects.ObjectCreator; + +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.debug.Assert; + public class BuildingObject extends TangibleObject { private final Map nameToCell; diff --git a/src/resources/objects/creature/CreatureObjectSharedNP.java b/src/resources/objects/creature/CreatureObjectSharedNP.java index 203218e7b..19668f958 100644 --- a/src/resources/objects/creature/CreatureObjectSharedNP.java +++ b/src/resources/objects/creature/CreatureObjectSharedNP.java @@ -36,7 +36,6 @@ import resources.HologramColour; import resources.collections.SWGList; import resources.collections.SWGMap; import resources.common.CRC; -import resources.control.Assert; import resources.encodables.player.Equipment; import resources.network.BaselineBuilder; import resources.network.NetBuffer; @@ -48,6 +47,8 @@ import resources.persistable.SWGObjectFactory; import resources.player.Player; import services.group.GroupInviterData; +import com.projectswg.common.debug.Assert; + class CreatureObjectSharedNP implements Persistable { private transient GroupInviterData inviterData = new GroupInviterData(0, null, "", 0); diff --git a/src/resources/objects/group/GroupObject.java b/src/resources/objects/group/GroupObject.java index 74efed318..98630eaf0 100644 --- a/src/resources/objects/group/GroupObject.java +++ b/src/resources/objects/group/GroupObject.java @@ -26,20 +26,6 @@ ******************************************************************************/ package resources.objects.group; -import network.packets.Packet; -import network.packets.swg.zone.baselines.Baseline; -import resources.Location; -import resources.Terrain; -import resources.collections.SWGList; -import resources.control.Assert; -import resources.encodables.Encodable; -import resources.network.BaselineBuilder; -import resources.objects.SWGObject; -import resources.objects.creature.CreatureObject; -import resources.player.Player; -import resources.server_info.SynchronizedMap; -import utilities.Encoder; - import java.nio.ByteBuffer; import java.util.HashMap; import java.util.HashSet; @@ -48,6 +34,21 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import network.packets.Packet; +import network.packets.swg.zone.baselines.Baseline; +import resources.Location; +import resources.Terrain; +import resources.collections.SWGList; +import resources.encodables.Encodable; +import resources.network.BaselineBuilder; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import resources.player.Player; +import utilities.Encoder; + +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.debug.Assert; + public class GroupObject extends SWGObject { private final SWGList groupMembers = new SWGList<>(6, 2, Encoder.StringType.ASCII); diff --git a/src/resources/player/Player.java b/src/resources/player/Player.java index 3f7ead461..937a7c4c7 100644 --- a/src/resources/player/Player.java +++ b/src/resources/player/Player.java @@ -29,12 +29,13 @@ package resources.player; import intents.network.OutboundPacketIntent; import network.packets.Packet; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.player.PlayerObject; import services.player.PlayerManager; -import utilities.IntentChain; + +import com.projectswg.common.control.IntentChain; +import com.projectswg.common.control.Service; public class Player implements Comparable { diff --git a/src/resources/server_info/Config.java b/src/resources/server_info/Config.java deleted file mode 100644 index 6785b0a15..000000000 --- a/src/resources/server_info/Config.java +++ /dev/null @@ -1,183 +0,0 @@ -/*********************************************************************************** -* 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 resources.server_info; - -import java.io.File; -import java.util.Map; - -/** - * Reads and stores configuration data from a file - */ -public class Config { - - private final ConfigData configData; - - /** - * Initilizes the Config and loads the data in the file - * @param filename the file to load - */ - public Config(String filename) { - this(new File(filename)); - } - - /** - * Initilizes the Config and loads the data in the file - * @param file the file to load - */ - public Config(File file) { - if (!file.exists() || !file.isFile()) - throw new IllegalArgumentException("Filepath does not point to a valid file!"); - configData = new ConfigData(file); - load(); - save(); - } - - /** - * Determines whether or not the key-value pair exists in the config - * @param key the key to check - * @return TRUE if the key-value pair exists, FALSE otherwise - */ - public boolean containsKey(String key) { - return configData.containsKey(key); - } - - /** - * Gets the parameter with the specified key. If no such parameter exists, - * it returns the default - * @param key the key to get the value for - * @param def the default value - * @return the value represented by the key, or the default value - */ - public String getString(String key, String def) { - if (!containsKey(key)) { - setProperty(key, def); - return def; - } - return configData.get(key); - } - - /** - * Gets the parameter with the specified key. If no such parameter exists, - * or if the value isn't an integer then it returns the default - * @param key the key to get the value for - * @param def the default value - * @return the value represented by the key, or the default value - */ - public int getInt(String key, int def) { - try { - return Integer.parseInt(getString(key, Integer.toString(def))); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Gets the parameter with the specified key. If no such parameter exists, - * or if the value isn't a double then it returns the default - * @param key the key to get the value for - * @param def the default value - * @return the value represented by the key, or the default value - */ - public double getDouble(String key, double def) { - try { - return Double.parseDouble(getString(key, Double.toString(def))); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Gets the parameter with the specified key. If no such parameter exists, - * or if the value isn't a boolean then it returns the default - * @param key the key to get the value for - * @param def the default value - * @return the value represented by the key, or the default value - */ - public boolean getBoolean(String key, boolean def) { - String val = getString(key, def?"true":"false"); - if (val.equalsIgnoreCase("true") || val.equals("1")) - return true; - if (val.equalsIgnoreCase("false") || val.equals("0")) - return false; - return def; - } - - /** - * Sets the property value for the specified key - * @param key the key of the value to set - * @param value the value to set - */ - public void setProperty(String key, String value) { - configData.put(key, value); - save(); - } - - /** - * Sets the property value for the specified key - * @param key the key of the value to set - * @param value the value to set - */ - public void setProperty(String key, int value) { - setProperty(key, Integer.toString(value)); - } - - /** - * Sets the property value for the specified key - * @param key the key of the value to set - * @param value the value to set - */ - public void setProperty(String key, boolean value) { - setProperty(key, value ? "true" : "false"); - } - - /** - * Sets the property value for the specified key - * @param key the key of the value to set - * @param value the value to set - */ - public void setProperty(String key, double value) { - setProperty(key, Double.toString(value)); - } - - /** - * Reloads the config data from the file - * @return TRUE if the data was successfully loaded, FALSE otherwise - */ - public Map load() { - return configData.load(); - } - - /** - * Saves the config data to the file - * @return TRUE if the data was successfully saved, FALSE otherwise - */ - public boolean save() { - return configData.save(); - } - -} diff --git a/src/resources/server_info/ConfigData.java b/src/resources/server_info/ConfigData.java deleted file mode 100644 index e98392d08..000000000 --- a/src/resources/server_info/ConfigData.java +++ /dev/null @@ -1,152 +0,0 @@ -/*********************************************************************************** -* 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 resources.server_info; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; - -class ConfigData { - - private final DateFormat FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss zzz"); - private final Map data; - private final File file; - - public ConfigData(File file) { - this.data = new TreeMap<>(); - this.file = file; - } - - public boolean containsKey(String key) { - synchronized (data) { - return data.containsKey(key); - } - } - - public String get(String key) { - synchronized (data) { - return data.get(key); - } - } - - public String put(String key, String value) { - synchronized (data) { - return data.put(key, value); - } - } - - /** - * @return null on an I/O failure, an empty {@code Map} on the first load and - * a populated {@code Map} when called afterwards - */ - public Map load() { - Map delta = new HashMap<>(); - BufferedReader reader = null; - - synchronized (data) { - delta.putAll(data); // Copy the current data - - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - String line = reader.readLine(); - while (line != null) { - loadLine(line); - line = reader.readLine(); - } - } catch (IOException e) { - Log.e(e); - return null; - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - return null; - } - } - } - for(Entry entry : data.entrySet()) - delta.remove(entry.getKey(), entry.getValue()); - } - - return delta; - } - - public boolean save() { - BufferedWriter writer = null; - synchronized (data) { - try { - writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)); - writer.write("# "+FORMAT.format(System.currentTimeMillis())); - writer.newLine(); - for (Entry e : data.entrySet()) { - writer.write(e.getKey() + "=" + e.getValue()); - writer.newLine(); - } - return true; - } catch (IOException e) { - Log.e(e); - return false; - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - - } - } - } - } - } - - private void loadLine(String line) { - String beforeComment = line; - if (line.contains("#")) - beforeComment = line.substring(0, line.indexOf('#')); - if (!beforeComment.contains("=")) - return; - String key = beforeComment.substring(0, beforeComment.indexOf('=')); - String val = beforeComment.substring(key.length()+1); - synchronized (data) { - data.put(key, val); - } - } - -} diff --git a/src/resources/server_info/ConfigWatcher.java b/src/resources/server_info/ConfigWatcher.java index 7b1a41a8d..7edd0cab3 100644 --- a/src/resources/server_info/ConfigWatcher.java +++ b/src/resources/server_info/ConfigWatcher.java @@ -33,106 +33,95 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; -import java.nio.file.WatchEvent.Kind; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.concurrency.PswgBasicThread; +import com.projectswg.common.info.Config; import resources.config.ConfigFile; -import static java.nio.file.StandardWatchEventKinds.OVERFLOW; -import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; public final class ConfigWatcher { - - private final WatchService watcher; - private final Map configMap; - private final Path directory; - private ExecutorService executor; - private boolean stop; + private static final String CFGPATH = "cfg/"; - public ConfigWatcher(Map configMap) throws IOException { + private final Map configMap; + private final Path directory; + private final PswgBasicThread watcherThread; + private final AtomicBoolean running; + + private WatchService watcher; + + public ConfigWatcher(Map configMap) { this.configMap = configMap; - watcher = FileSystems.getDefault().newWatchService(); - directory = Paths.get(CFGPATH); - - directory.register(watcher, ENTRY_MODIFY); + this.directory = Paths.get(CFGPATH); + this.watcherThread = new PswgBasicThread("config-watcher", () -> watch()); + this.running = new AtomicBoolean(false); + + try { + this.watcher = FileSystems.getDefault().newWatchService(); + directory.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); + } catch (IOException e) { + this.watcher = null; + } + watcherThread.setWaitForTermination(false); } - - @SuppressWarnings("unchecked") + public void start() { - executor = Executors.newSingleThreadExecutor(); - executor.execute(() -> { - WatchKey key; - Kind eventKind; - WatchEvent ev; - Path filename; - ConfigFile cfgFile; - Config cfg; - - while (!stop) { - try { - key = watcher.take(); // We're stuck here until a change is made. - if (key == null) - break; - } catch (Exception e) { + watcherThread.start(); + } + + public void stop() { + watcherThread.stop(); + watcherThread.awaitTermination(1000); + } + + private void watch() { + if (watcher == null) { + Log.e("WatcherService is null!"); + return; + } + WatchKey key; + + try { + while (running.get()) { + key = watcher.take(); // We're stuck here until a change is made. + if (key == null) break; - } - + for (WatchEvent event : key.pollEvents()) { - eventKind = event.kind(); - - // This key is registered only - // for ENTRY_CREATE events, - // but an OVERFLOW event can - // occur regardless if events - // are lost or discarded. - if (eventKind == OVERFLOW) { - key.reset(); - continue; - } - - // The context is the name of the file. - ev = (WatchEvent) event; - filename = ev.context(); - - cfgFile = ConfigFile.configFileForName(CFGPATH + filename); - cfg = configMap.get(cfgFile); - - if(cfg == null) { - key.reset(); - continue; - } - - Map delta = cfg.load(); - - for (Entry entry : delta.entrySet()) { - String entryKey = entry.getKey(); - - new ConfigChangedIntent(cfgFile, entryKey, entry.getValue(), - cfg.getString(entryKey, null)).broadcast(); - } - - // We want to receive further watch - // events from this key - reset it. + processEvents(event); key.reset(); } } + } catch (Exception e) { + // Ignored + } finally { try { watcher.close(); } catch (Exception e) { Log.e(e); } - }); + } } - public void stop() { - stop = true; - if (executor != null) - executor.shutdownNow(); + private void processEvents(WatchEvent event) { + if (event.kind() == StandardWatchEventKinds.OVERFLOW) + return; + + @SuppressWarnings("unchecked") + ConfigFile cfgFile = ConfigFile.configFileForName(CFGPATH + ((WatchEvent) event).context()); + Config cfg = configMap.get(cfgFile); + if (cfg == null) + return; + + for (Entry entry : cfg.load().entrySet()) { + new ConfigChangedIntent(cfgFile, entry.getKey(), entry.getValue(), cfg.getString(entry.getKey(), null)).broadcast(); + } } } diff --git a/src/resources/server_info/CrcDatabase.java b/src/resources/server_info/CrcDatabase.java index 29a33ca1d..6482b98dd 100644 --- a/src/resources/server_info/CrcDatabase.java +++ b/src/resources/server_info/CrcDatabase.java @@ -35,6 +35,9 @@ import java.util.Map; import resources.common.CRC; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public class CrcDatabase implements AutoCloseable { private static final String INSERT_CRC_SQL = "INSERT INTO crcs (string, crc) VALUES (?, ?)"; diff --git a/src/resources/server_info/DataManager.java b/src/resources/server_info/DataManager.java index a2effbc14..1e755c7eb 100644 --- a/src/resources/server_info/DataManager.java +++ b/src/resources/server_info/DataManager.java @@ -27,147 +27,115 @@ ***********************************************************************************/ package resources.server_info; -import intents.server.ConfigChangedIntent; - import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.Config; +import com.projectswg.common.info.RelationalServerFactory; import resources.config.ConfigFile; -import resources.control.Intent; -import resources.control.IntentManager; -import resources.control.IntentReceiver; - -public class DataManager implements IntentReceiver { +public class DataManager { + private static final Object instanceLock = new Object(); - private static final String ENABLE_LOGGING = "ENABLE-LOGGING"; private static DataManager instance = null; - - private Map config; - private List watchers; - private boolean initialized; - + + private final Map configs; + private final ConfigWatcher watcher; + private final AtomicBoolean initialized; + private DataManager() { - initialized = false; - IntentManager.getInstance().registerForIntent(ConfigChangedIntent.TYPE, this); - watchers = new ArrayList<>(); - } - - private synchronized void initialize() { - initializeConfig(); - if (getConfig(ConfigFile.PRIMARY).getBoolean(ENABLE_LOGGING, true)) - Log.start(); - initialized = true; + this.configs = new SynchronizedMap<>(); + this.watcher = new ConfigWatcher(configs); + this.initialized = new AtomicBoolean(false); } - private synchronized void shutdown() { - for (ConfigWatcher watcher : watchers) - watcher.stop(); + private void initializeImpl() { + if (initialized.getAndSet(true)) + return; + initializeConfig(); + RelationalServerFactory.setBasePath("serverdata/"); } - - private synchronized void initializeConfig() { - config = new ConcurrentHashMap(); + + private void terminateImpl() { + if (!initialized.getAndSet(false)) + return; + watcher.stop(); + configs.clear(); + } + + private Config getConfigImpl(ConfigFile file) { + return configs.get(file); + } + + private void initializeConfig() { + Assert.test(configs.isEmpty(), "Internal error in DataManager!"); for (ConfigFile file : ConfigFile.values()) { File f = new File(file.getFilename()); - try { - if (!createFilesAndDirectories(f)) { - Log.w("ConfigFile could not be loaded! " + file.getFilename()); - } else { - config.put(file, new Config(f)); - } - - ConfigWatcher watcher = new ConfigWatcher(config); - watcher.start(); - watchers.add(watcher); - } catch (IOException e) { - Log.e(e); + if (!createConfig(f)) { + Log.w("ConfigFile could not be loaded! " + file.getFilename()); + } else { + configs.put(file, new Config(f)); } } + watcher.start(); } - private boolean createFilesAndDirectories(File file) { + private boolean createConfig(File file) { if (file.exists()) - return true; + return file.isFile(); try { - String parentName = file.getParent(); - if (parentName != null && !parentName.isEmpty()) { - File parent = new File(file.getParent()); - if (!parent.exists() && !parent.mkdirs()) - Log.e("Failed to create parent directories for ODB: " + file.getCanonicalPath()); + File parent = file.getParentFile(); + if (parent != null && !parent.exists() && !parent.mkdirs()) { + Log.e("Failed to create parent directories for config: " + file.getCanonicalPath()); + return false; + } + if (!file.createNewFile()) { + Log.e("Failed to create new config: " + file.getCanonicalPath()); + return false; } - } catch (IOException e) { - Log.e(e); - } - try { - if (!file.createNewFile()) - Log.e("Failed to create new ODB: " + file.getCanonicalPath()); } catch (IOException e) { Log.e(e); } return file.exists(); } - + /** - * Gets the config object associated with a certain file, or NULL if the - * file failed to load on startup + * Gets the config object associated with a certain file, or NULL if the file failed to load on startup * - * @param file - * the file to get the config for - * @return the config object associated with the file, or NULL if the config - * failed to load + * @param file the file to get the config for + * @return the config object associated with the file, or NULL if the config failed to load */ - public synchronized final Config getConfig(ConfigFile file) { - Config c = config.get(file); - if (c == null) - return new Config(file.getFilename()); - return c; + public static Config getConfig(ConfigFile file) { + return getInstance().getConfigImpl(file); } - - public synchronized final boolean isInitialized() { - return initialized; + + public static void initialize() { + synchronized (instanceLock) { + if (instance != null) + return; + instance = new DataManager(); + instance.initializeImpl(); + } } - - public synchronized static final DataManager getInstance() { + + public static void terminate() { + synchronized (instanceLock) { + if (instance == null) + return; + instance.terminateImpl(); + instance = null; + } + } + + private static final DataManager getInstance() { synchronized (instanceLock) { - if (instance == null) { - instance = new DataManager(); - instance.initialize(); - } return instance; } } - public synchronized static final void terminate() { - synchronized (instanceLock) { - if (instance != null) { - instance.shutdown(); - instance = null; - } - } - } - - @Override - public void onIntentReceived(Intent i) { - if (!(i instanceof ConfigChangedIntent)) - return; - ConfigChangedIntent cci = (ConfigChangedIntent) i; - if(!cci.getKey().equals(ENABLE_LOGGING)) - return; - boolean log = Boolean.valueOf(cci.getNewValue()); - boolean oldValue = Boolean.valueOf(cci.getOldValue()); - - // If the value hasn't changed, then do nothing. - if(log == oldValue) - return; - - if (log) - Log.start(); - else - Log.stop(); - } - } diff --git a/src/resources/server_info/ObjectDatabase.java b/src/resources/server_info/ObjectDatabase.java index 30e7d5c33..ec62955e9 100644 --- a/src/resources/server_info/ObjectDatabase.java +++ b/src/resources/server_info/ObjectDatabase.java @@ -31,9 +31,10 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; -import resources.concurrency.PswgBasicScheduledThread; import resources.persistable.Persistable; +import com.projectswg.common.concurrency.PswgBasicScheduledThread; + public abstract class ObjectDatabase { private final File file; diff --git a/src/resources/server_info/PostgresqlDatabase.java b/src/resources/server_info/PostgresqlDatabase.java deleted file mode 100644 index ec7f08552..000000000 --- a/src/resources/server_info/PostgresqlDatabase.java +++ /dev/null @@ -1,36 +0,0 @@ -/*********************************************************************************** -* 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 resources.server_info; - -public class PostgresqlDatabase extends RelationalDatabase { - - public PostgresqlDatabase(String host, String db, String user, String pass) { - super("org.postgresql.Driver", "postgresql", host, db, user, pass, ""); - } - -} diff --git a/src/resources/server_info/RelationalDatabase.java b/src/resources/server_info/RelationalDatabase.java deleted file mode 100644 index 964187a10..000000000 --- a/src/resources/server_info/RelationalDatabase.java +++ /dev/null @@ -1,190 +0,0 @@ -/*********************************************************************************** -* 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 resources.server_info; - -import java.io.Closeable; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.Statement; - -public abstract class RelationalDatabase implements Closeable { - - private DatabaseMetaData metaData; - private Connection connection; - private boolean online; - - protected RelationalDatabase(String jdbcClass, String url) { - try { - Class.forName(jdbcClass); - initialize(url); - } catch (ClassNotFoundException e) { - Log.e(e); - online = false; - } - } - - protected RelationalDatabase(String jdbcClass, String url, String user, String pass) { - try { - Class.forName(jdbcClass); - initialize(url, user, pass); - } catch (ClassNotFoundException e) { - Log.e(e); - online = false; - } - } - - public RelationalDatabase(String jdbcClass, String url, String user, String pass, String params) { - try { - Class.forName(jdbcClass); - if (params != null && params.length() > 0) - url += "?" + params; - initialize(url, user, pass); - } catch (ClassNotFoundException e) { - Log.e(e); - online = false; - } - } - - public RelationalDatabase(String jdbcClass, String type, String host, String db, String user, String pass, String params) { - try { - Class.forName(jdbcClass); - String url = "jdbc:" + type + "://" + host + "/" + db; - if (params != null && params.length() > 0) - url += "?" + params; - initialize(url, user, pass); - } catch (ClassNotFoundException e) { - Log.e(e); - online = false; - } - } - - private void initialize(String url) { - try { - connection = DriverManager.getConnection(url); - metaData = connection.getMetaData(); - online = true; - } catch (SQLException e) { - Log.e("Failed to initialize relational database! %s - %s", e.getClass().getSimpleName(), e.getMessage()); - online = false; - } - } - - private void initialize(String url, String user, String pass) { - try { - connection = DriverManager.getConnection(url, user, pass); - metaData = connection.getMetaData(); - online = true; - } catch (SQLException e) { - Log.e("Failed to initialize relational database! %s - %s", e.getClass().getSimpleName(), e.getMessage()); - online = false; - } - } - - public void close() { - try { - connection.close(); - online = false; - } catch (SQLException e) { - Log.e(e); - } - } - - public boolean isOnline() { - if (connection == null) - return false; - try { - return online && !connection.isClosed(); - } catch (SQLException e) { - return online; - } - } - - public PreparedStatement prepareStatement(String sql) { - if (connection == null) { - Log.e("Cannot prepare statement! Connection is null"); - return null; - } - try { - return connection.prepareStatement(sql); - } catch (SQLException e) { - Log.e(e); - return null; - } - } - - public ResultSet executeQuery(String query) { - if (connection == null) - return null; - Statement s = null; - try { - s = connection.createStatement(); - s.execute(query); - try { - s.closeOnCompletion(); - } catch (SQLFeatureNotSupportedException e) { - // It was worth a shot - } - return s.getResultSet(); - } catch (SQLException e) { - Log.e(e); - if (s != null) { - try { s.close(); } catch (SQLException ex) { } - } - return null; - } - } - - public int updateQuery(String query) { - if (connection == null) - return 0; - try { - try (Statement s = connection.createStatement()) { - return s.executeUpdate(query); - } - } catch (SQLException e) { - Log.e(e); - return 0; - } - } - - public boolean isTable(String name) { - if (metaData == null) - return false; - try { - return metaData.getTables(null, null, name, null).next(); - } catch (SQLException e) { - return false; - } - } - -} diff --git a/src/resources/server_info/RelationalServerData.java b/src/resources/server_info/RelationalServerData.java deleted file mode 100644 index 842cbad5a..000000000 --- a/src/resources/server_info/RelationalServerData.java +++ /dev/null @@ -1,445 +0,0 @@ -/*********************************************************************************** -* 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 resources.server_info; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -public class RelationalServerData extends RelationalDatabase { - - private static final Charset ASCII = Charset.forName("ASCII"); - - private static final String META_TABLE = "__server_table_metadata__"; - private final PreparedStatement getTableMetadata; - private final PreparedStatement updateTableMetadata; - private final PreparedStatement insertTableMetadata; - - public RelationalServerData(String file) { - super("org.sqlite.JDBC", "jdbc:sqlite:" + file); - /* - * GREATLY speeds up INSERT speeds. This makes it so SQLite will not - * wait until the previous write finishes before starting to write the - * next INSERT. All imports will only take milliseconds. A caveat of - * this is that if the power ever cuts or the application is force - * killed it may result in database corruption. - */ - updateQuery("PRAGMA synchronous=OFF"); - updateQuery("CREATE TABLE IF NOT EXISTS "+META_TABLE+" (table_name TEXT, last_imported INTEGER)"); - getTableMetadata = prepareStatement("SELECT * FROM "+META_TABLE+" WHERE table_name=?"); - updateTableMetadata = prepareStatement("UPDATE "+META_TABLE+" SET last_imported=? WHERE table_name=?"); - insertTableMetadata = prepareStatement("INSERT INTO "+META_TABLE+" (table_name, last_imported) VALUES (?, ?)"); - } - - public boolean linkTableWithSdb(String table, String filepath) { - File sdb = new File(filepath); - if (!sdb.isFile()) - return false; - if (testLastModified(table, sdb) || testMasterSdbModified(table, sdb)) { - updateQuery("DROP TABLE IF EXISTS " + table); - importFromSdb(table, sdb); - updateLastImported(table, System.currentTimeMillis()); - } - return true; - } - - private boolean testLastModified(String table, File sdb) { - return sdb.lastModified() > getLastImported(table); - } - - private boolean testMasterSdbModified(String table, File sdb) { - if (!sdb.getName().endsWith(".msdb")) - return false; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(sdb), ASCII))) { - reader.readLine(); - reader.readLine(); - String line; - while ((line = reader.readLine()) != null) { - if (testLastModified(table, new File(sdb.getParent(), line.substring(0, line.indexOf('\t'))))) - return true; - } - } catch (IOException e) { - Log.e(e); - } - return false; - } - - /** - * Inserts a record with the specified information into the database - * @param table the table to insert into - * @param columns the columns to insert into - * @param params the parameters to insert - * @return TRUE on success, FALSE on failure - * @throws SQLException upon error - */ - public boolean insert(String table, String [] columns, Object ... params) throws SQLException { - StringBuilder columnStr = new StringBuilder(""); - StringBuilder valuesStr = new StringBuilder(""); - if (columns == null) - columns = getColumnsForTable(table); - for (int i = 0; i < columns.length; i++) { - columnStr.append(columns[i]); - valuesStr.append('?'); - if (i+1 < columns.length) { - columnStr.append(','); - valuesStr.append(','); - } - } - final String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", table, columnStr, valuesStr); - try (PreparedStatement statement = prepareStatement(sql)) { - if (statement == null || params.length < getSqlParameterCount(sql)) - return false; - assignParameters(statement, params); - return statement.executeUpdate() > 0; - } - } - - /** - * Selects from the specified table with the supplied where clause, given - * the supplied parameters - * @param tables the tables to query, separated by commas - * @param columns the columns to select, null to select all - * @param where the where clause - * @param params the parameters to put into the ?s in the where clause - * @return the result set - * @throws SQLException upon error - */ - public ResultSet selectFromTable(String tables, String [] columns, String where, Object ... params) throws SQLException { - final String sql = createSelectQuery(tables, columns, where, params); - PreparedStatement statement = prepareStatement(sql); - if (statement == null || params.length < getSqlParameterCount(sql)) - return null; - assignParameters(statement, params); - return statement.executeQuery(); - } - - private void assignParameters(PreparedStatement statement, Object ... params) throws SQLException { - for (int i = 0; i < params.length; i++) { - if (params[i] instanceof Integer || params[i] instanceof Long) - statement.setLong(i+1, ((Number) params[i]).longValue()); - else if (params[i] instanceof Float || params[i] instanceof Double) - statement.setDouble(i+1, ((Number) params[i]).doubleValue()); - else if (params[i] instanceof String) - statement.setString(i+1, (String) params[i]); - else if (params[i] != null) - throw new IllegalArgumentException("Unknown object type: " + params[i].getClass().getSimpleName()); - else - throw new NullPointerException("Parameters cannot have null elements!"); - } - } - - private int getSqlParameterCount(String sql) { - int ret = 0; - for (int i = 0; i < sql.length(); i++) { - if (sql.charAt(i) == '?') - ret++; - } - return ret; - } - - private String createSelectQuery(String tables, String [] columns, String where, Object ... params) { - String columnStr = "*"; - if (columns == null && tables.contains(",")) { - StringBuilder bldr = new StringBuilder(""); - for (String table : tables.split(",")) { - bldr.append(table.trim()); - bldr.append(".*, "); - } - columnStr = bldr.substring(0, columnStr.length()-2); - } else if (columns != null) { - StringBuilder bldr = new StringBuilder(""); - for (String column : columns) { - bldr.append(column); - bldr.append(", "); - } - columnStr = bldr.substring(0, bldr.length()-2); - } - return "SELECT " + columnStr + " FROM " + tables + " WHERE " + where; - } - - private String [] getColumnsForTable(String table) throws SQLException { - List columns = new ArrayList<>(); - try (ResultSet set = executeQuery("PRAGMA table_info('"+table+"')")) { - int colInd = set.findColumn("name"); - while (set.next()) { - columns.add(set.getString(colInd)); - } - } - return columns.toArray(new String[columns.size()]); - } - - private long getLastImported(String table) { - synchronized (getTableMetadata) { - ResultSet set = null; - try { - getTableMetadata.setString(1, table); - set = getTableMetadata.executeQuery(); - if (set.next()) - return set.getLong("last_imported"); - } catch (SQLException e) { - Log.e(e); - } finally { - if (set != null) { - try { - set.close(); - } catch (SQLException e) { - Log.e(e); - } - } - } - return -1; - } - } - - private void updateLastImported(String table, long lastImported) { - synchronized (updateTableMetadata) { - try { - updateTableMetadata.setLong(1, lastImported); - updateTableMetadata.setString(2, table); - if (updateTableMetadata.executeUpdate() > 0) - return; - } catch (SQLException e) { - Log.e(e); - return; - } - } - synchronized (insertTableMetadata) { - try { - insertTableMetadata.setString(1, table); - insertTableMetadata.setLong(2, lastImported); - insertTableMetadata.executeUpdate(); - } catch (SQLException e) { - Log.e(e); - } - } - } - - private boolean importFromSdb(String table, File sdb) { - try (TableReader reader = new TableReader(table, sdb)) { - Log.i("Importing sdb... '" + sdb + "'"); - if (sdb.getName().endsWith(".msdb")) - reader.readMaster(); - else - reader.readNormal(); - return true; - } catch (IOException e) { - Log.e(e); - } catch (SQLException e) { - Log.e(e); - } catch (IllegalArgumentException e) { - Log.e("Invalid file format. Aborting read of %s! Message: %s", sdb, e.getMessage()); - } - return false; - } - - private class TableReader implements Closeable { - - private final String table; - private final File file; - private String [] columnNames; - private String [] columnTypes; - private PreparedStatement insert; - - public TableReader(String table, File file) { - this.table = table; - this.file = file; - this.columnNames = null; - this.columnTypes = null; - this.insert = null; - } - - @Override - public void close() { - if (insert != null) { - try { - insert.close(); - } catch (SQLException e) { - Log.e(e); - } - } - } - - public void readNormal() throws IOException, SQLException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { - readNormalRemainder(reader, 1); - } - } - - public void readMaster() throws SQLException, IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { - int lineNum = 1; - readLine(reader.readLine(), lineNum++); - readLine(reader.readLine(), lineNum++); - readMasterRemainder(reader, lineNum); - } - } - - private void readSlave() throws IOException, SQLException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { - int lineNum = 3; - reader.readLine(); - reader.readLine(); // Skip column names and types - readNormalRemainder(reader, lineNum); - } - } - - private void readMasterRemainder(BufferedReader reader, int lineNum) throws IOException, SQLException { - String line; - while ((line = reader.readLine()) != null) { - String [] parts = line.split("\t"); - if (parts.length != 2) { - Log.e("Invalid line [%d]: %s", lineNum, line); - continue; - } - boolean load = Boolean.parseBoolean(parts[1]); - if (load) { - File sdb = new File(file.getParent(), parts[0]); - Log.i(" Importing sdb... '" + sdb + "'"); - if (!sdb.isFile()) { - Log.e(" Failed to import sdb! File is not file or does not exist"); - continue; - } - @SuppressWarnings("resource") // This closes the database.. we don't want to do that yet - TableReader slave = new TableReader(table, sdb); - slave.columnNames = columnNames; - slave.columnTypes = columnTypes; - slave.insert = insert; - slave.readSlave(); - } - } - } - - private void readNormalRemainder(BufferedReader reader, int lineNum) throws IOException, SQLException { - String line; - while ((line = reader.readLine()) != null) { - readLine(line, lineNum++); - } - if (insert != null) - insert.executeBatch(); - } - - private void readLine(String line, int lineNum) throws SQLException { - if (line == null) - return; - switch (lineNum) { - case 1: - columnNames = fastTabSplit(line); - break; - case 2: - columnTypes = fastTabSplit(line); - createTable(); - createPreparedStatement(); - break; - default: - generateInsert(fastTabSplit(line), lineNum); - break; - } - } - - private String [] fastTabSplit(String str) { - int count = 1; - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == '\t') { - count++; - } - } - String [] tabs = new String[count]; - int tabInd = 0; - int prevInd = 0; - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == '\t') { - tabs[tabInd++] = str.substring(prevInd, i); - prevInd = i+1; - } - } - if (prevInd < str.length()) - tabs[tabInd++] = str.substring(prevInd); - return tabs; - } - - private void createTable() { - if (columnNames.length != columnTypes.length) - throw new IllegalArgumentException("Names length and types length mismatch"); - StringBuilder sql = new StringBuilder("CREATE TABLE "+table+" ("); - for (int i = 0; i < columnNames.length; i++) { - if (i > 0) - sql.append(", "); - sql.append(columnNames[i]); - sql.append(' '); - sql.append(columnTypes[i]); - } - sql.append(')'); - updateQuery(sql.toString()); - } - - private void createPreparedStatement() { - StringBuilder sql = new StringBuilder("INSERT INTO " + table + " VALUES ("); - for (int i = 0; i < columnNames.length; i++) { - if (i > 0) - sql.append(", "); - sql.append('?'); - } - sql.append(')'); - insert = prepareStatement(sql.toString()); - } - - private void generateInsert(String [] data, int line) throws SQLException { - if (columnTypes.length != data.length) { - Log.e("Could not load record: Types length and data length mismatch. Line: " + line); - return; - } - int column = 0; - try { - for (int i = 0; i < data.length; i++, column++) { - if (columnTypes[i].startsWith("TEXT")) - insert.setString(i+1, data[i]); - else if (columnTypes[i].startsWith("REAL")) - insert.setDouble(i+1, Double.parseDouble(data[i])); - else if (columnTypes[i].startsWith("INTEGER")) - insert.setLong(i+1, Long.parseLong(data[i])); - else - throw new SQLException("Data type unsupported by sdb/sqlite! Type: " + columnTypes[i]); - } - insert.addBatch(); - } catch (NumberFormatException e) { - Log.e("Could not load record: Record has invalid data. Line: " + line + " Column: " + column); - } - } - - } - -} diff --git a/src/resources/server_info/RelationalServerFactory.java b/src/resources/server_info/RelationalServerFactory.java deleted file mode 100644 index c52332008..000000000 --- a/src/resources/server_info/RelationalServerFactory.java +++ /dev/null @@ -1,160 +0,0 @@ -/************************************************************************************ - * 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 resources.server_info; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - -public class RelationalServerFactory { - - private static final RelationalServerFactory INSTANCE = new RelationalServerFactory(); - private static final String BASE_PATH = "serverdata" + File.separator; - private static final Map FILE_LOAD_LOCKING = new HashMap<>(); - - public static RelationalServerData getServerData(String file, String ... tables) { - return INSTANCE.getData(file, tables); - } - - public static RelationalServerData getServerDatabase(String file) { - return INSTANCE.getDatabase(file); - } - - private RelationalServerData getData(String file, String ... tables) { - if (!file.endsWith(".db")) - throw new IllegalArgumentException("File path for database must end in .db!"); - file = file.replace('/', File.separatorChar); - final Object lock = getFileLocking(file); - synchronized (lock) { - File f = new File(BASE_PATH + file); - RelationalServerData data = new RelationalServerData(BASE_PATH + file); - if (loadServerData(data, f, tables)) - return data; - return null; - } - } - - private boolean loadServerData(RelationalServerData data, File file, String ... tables) { - File parent = file.getParentFile(); - try { - if (loadTables(data, parent, tables)) - return true; - data.close(); - } catch (Exception e) { - Log.e(e); - data.close(); - } - return false; - } - - private RelationalServerData getDatabase(String file) { - if (!file.endsWith(".db")) - throw new IllegalArgumentException("File path for database must end in .db!"); - final Object lock = getFileLocking(file); - synchronized (lock) { - File f = new File(BASE_PATH + file); - RelationalServerData data = new RelationalServerData(BASE_PATH + file); - try { - String [] commands = getCommandsFromSchema(f.getPath().substring(0, f.getPath().length()-3) + ".sql"); - for (String command : commands) - executeCommand(data, command); - return data; - } catch (Exception e) { - Log.e(e); - } - data.close(); - return null; - } - } - - private boolean loadTables(RelationalServerData data, File parent, String [] tables) { - for (String table : tables) { - table = table.replace('/', File.separatorChar); - String path = generatePath(parent, table); - table = path.substring(path.lastIndexOf(File.separatorChar)+1, path.lastIndexOf('.')); - if (!data.linkTableWithSdb(table, path)) - return false; - } - return true; - } - - private String generatePath(File parent, String table) { - String base; - if (table.contains(File.separator)) - base = BASE_PATH + table; - else - base = parent.getPath() + File.separator + table; - if (new File(base + ".msdb").isFile()) - return base + ".msdb"; - return base + ".sdb"; - } - - private void executeCommand(RelationalServerData data, String command) { - command = command.trim(); - if (command.startsWith("SELECT")) { - try (ResultSet set = data.executeQuery(command)) { - - } catch (SQLException e) { - Log.e(e); - } - } else { - data.updateQuery(command); - } - } - - private String [] getCommandsFromSchema(String schema) throws IOException { - String command; - try (InputStream input = new FileInputStream(new File(schema))) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(input.available()); - byte [] block = new byte[1024]; - while (input.available() > 0) { - int size = input.read(block); - baos.write(block, 0, size); - } - command = new String(baos.toByteArray(), Charset.forName("ASCII")); - } - return command.split(";"); - } - - private Object getFileLocking(String file) { - synchronized (FILE_LOAD_LOCKING) { - Object o = FILE_LOAD_LOCKING.get(file); - if (o == null) - FILE_LOAD_LOCKING.put(file, o = new Object()); - return o; - } - } - -} diff --git a/src/resources/server_info/SynchronizedList.java b/src/resources/server_info/SynchronizedList.java deleted file mode 100644 index 294600405..000000000 --- a/src/resources/server_info/SynchronizedList.java +++ /dev/null @@ -1,182 +0,0 @@ -/************************************************************************************ - * 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 synchronized 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 synchronized License for more details. * - * * - * You should have received a copy of the GNU Affero General public synchronized License * - * along with Holocore. If not, see . * - * * - ***********************************************************************************/ -package resources.server_info; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; - -public class SynchronizedList implements List { - - private final List list; - - public SynchronizedList() { - this.list = new ArrayList<>(); - } - - public SynchronizedList(List list) { - this.list = list; - } - - public synchronized void forEach(Consumer action) { - list.forEach(action); - } - - public synchronized int size() { - return list.size(); - } - - public synchronized boolean isEmpty() { - return list.isEmpty(); - } - - public synchronized boolean contains(Object o) { - return list.contains(o); - } - - public synchronized Iterator iterator() { - return list.iterator(); - } - - public synchronized Object[] toArray() { - return list.toArray(); - } - - public synchronized T[] toArray(T[] a) { - return list.toArray(a); - } - - public synchronized boolean add(E e) { - return list.add(e); - } - - public synchronized boolean remove(Object o) { - return list.remove(o); - } - - public synchronized boolean containsAll(Collection c) { - return list.containsAll(c); - } - - public synchronized boolean addAll(Collection c) { - return list.addAll(c); - } - - public synchronized boolean addAll(int index, Collection c) { - return list.addAll(index, c); - } - - public synchronized boolean removeAll(Collection c) { - return list.removeAll(c); - } - - public synchronized boolean retainAll(Collection c) { - return list.retainAll(c); - } - - public synchronized void replaceAll(UnaryOperator operator) { - list.replaceAll(operator); - } - - public synchronized boolean removeIf(Predicate filter) { - return list.removeIf(filter); - } - - public synchronized void sort(Comparator c) { - list.sort(c); - } - - public synchronized void clear() { - list.clear(); - } - - public synchronized boolean equals(Object o) { - return list.equals(o); - } - - public synchronized int hashCode() { - return list.hashCode(); - } - - public synchronized E get(int index) { - return list.get(index); - } - - public synchronized E set(int index, E element) { - return list.set(index, element); - } - - public synchronized void add(int index, E element) { - list.add(index, element); - } - - public synchronized Stream stream() { - return list.stream(); - } - - public synchronized E remove(int index) { - return list.remove(index); - } - - public synchronized Stream parallelStream() { - return list.parallelStream(); - } - - public synchronized int indexOf(Object o) { - return list.indexOf(o); - } - - public synchronized int lastIndexOf(Object o) { - return list.lastIndexOf(o); - } - - public synchronized ListIterator listIterator() { - return list.listIterator(); - } - - public synchronized ListIterator listIterator(int index) { - return list.listIterator(index); - } - - public synchronized List subList(int fromIndex, int toIndex) { - return list.subList(fromIndex, toIndex); - } - - public synchronized Spliterator spliterator() { - return list.spliterator(); - } - -} diff --git a/src/resources/server_info/SynchronizedMap.java b/src/resources/server_info/SynchronizedMap.java deleted file mode 100644 index 7dbb55671..000000000 --- a/src/resources/server_info/SynchronizedMap.java +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************************************************ - * 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 synchronized 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 synchronized License for more details. * - * * - * You should have received a copy of the GNU Affero General public synchronized License * - * along with Holocore. If not, see . * - * * - ***********************************************************************************/ -package resources.server_info; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class SynchronizedMap implements Map { - - private final Map map; - - public SynchronizedMap() { - this.map = new HashMap<>(); - } - - public SynchronizedMap(Map map) { - this.map = map; - } - - public synchronized int hashCode() { - return map.hashCode(); - } - - public synchronized String toString() { - return map.toString(); - } - - public synchronized int size() { - return map.size(); - } - - public synchronized boolean isEmpty() { - return map.isEmpty(); - } - - public synchronized V get(Object key) { - return map.get(key); - } - - public synchronized boolean containsKey(Object key) { - return map.containsKey(key); - } - - public synchronized V put(K key, V value) { - return map.put(key, value); - } - - public synchronized void putAll(Map m) { - map.putAll(m); - } - - public synchronized V remove(Object key) { - return map.remove(key); - } - - public synchronized void clear() { - map.clear(); - } - - public synchronized boolean containsValue(Object value) { - return map.containsValue(value); - } - - public synchronized Set keySet() { - return map.keySet(); - } - - public synchronized Collection values() { - return map.values(); - } - - public synchronized Set> entrySet() { - return map.entrySet(); - } - - public synchronized V replace(K key, V value) { - return map.replace(key, value); - } - -} diff --git a/src/resources/server_info/SynchronizedSet.java b/src/resources/server_info/SynchronizedSet.java deleted file mode 100644 index ae69df8a3..000000000 --- a/src/resources/server_info/SynchronizedSet.java +++ /dev/null @@ -1,131 +0,0 @@ -/************************************************************************************ - * 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 synchronized 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 synchronized License for more details. * - * * - * You should have received a copy of the GNU Affero General public synchronized License * - * along with Holocore. If not, see . * - * * - ***********************************************************************************/ -package resources.server_info; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.Spliterator; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -public class SynchronizedSet implements Set { - - private final Set set; - - public SynchronizedSet() { - this.set = new HashSet<>(); - } - - public SynchronizedSet(Set set) { - this.set = set; - } - - public synchronized void forEach(Consumer action) { - set.forEach(action); - } - - public synchronized int size() { - return set.size(); - } - - public synchronized boolean isEmpty() { - return set.isEmpty(); - } - - public synchronized boolean contains(Object o) { - return set.contains(o); - } - - public synchronized Iterator iterator() { - return set.iterator(); - } - - public synchronized Object[] toArray() { - return set.toArray(); - } - - public synchronized T[] toArray(T[] a) { - return set.toArray(a); - } - - public synchronized boolean add(E e) { - return set.add(e); - } - - public synchronized boolean remove(Object o) { - return set.remove(o); - } - - public synchronized boolean containsAll(Collection c) { - return set.containsAll(c); - } - - public synchronized boolean addAll(Collection c) { - return set.addAll(c); - } - - public synchronized boolean retainAll(Collection c) { - return set.retainAll(c); - } - - public synchronized boolean removeAll(Collection c) { - return set.removeAll(c); - } - - public synchronized void clear() { - set.clear(); - } - - public synchronized boolean equals(Object o) { - return set.equals(o); - } - - public synchronized int hashCode() { - return set.hashCode(); - } - - public synchronized Spliterator spliterator() { - return set.spliterator(); - } - - public synchronized boolean removeIf(Predicate filter) { - return set.removeIf(filter); - } - - public synchronized Stream stream() { - return set.stream(); - } - - public synchronized Stream parallelStream() { - return set.parallelStream(); - } - -} diff --git a/src/services/CoreManager.java b/src/services/CoreManager.java index 7d85b35b7..935fe53b6 100644 --- a/src/services/CoreManager.java +++ b/src/services/CoreManager.java @@ -27,6 +27,11 @@ ***********************************************************************************/ package services; +import intents.network.InboundPacketIntent; +import intents.network.OutboundPacketIntent; +import intents.server.ServerManagementIntent; +import intents.server.ServerStatusIntent; + import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; @@ -39,10 +44,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.network.InboundPacketIntent; -import intents.network.OutboundPacketIntent; -import intents.server.ServerManagementIntent; -import intents.server.ServerStatusIntent; import network.packets.Packet; import network.packets.swg.admin.AdminShutdownServer; import network.packets.swg.zone.baselines.Baseline; @@ -51,17 +52,18 @@ import network.packets.swg.zone.object_controller.ObjectController; import resources.Galaxy; import resources.Galaxy.GalaxyStatus; import resources.config.ConfigFile; -import resources.control.Manager; import resources.control.ServerStatus; -import resources.server_info.Config; +import resources.server_info.DataManager; import resources.server_info.Log; import resources.server_info.Log.LogLevel; -import services.admin.OnlineInterfaceService; import services.galaxy.GalacticManager; import utilities.CrcDatabaseGenerator; import utilities.ScheduledUtilities; import utilities.ThreadUtilities; +import com.projectswg.common.control.Manager; +import com.projectswg.common.info.Config; + public class CoreManager extends Manager { private static final DateFormat LOG_FORMAT = new SimpleDateFormat("dd-MM-yy HH:mm:ss.SSS"); @@ -69,7 +71,6 @@ public class CoreManager extends Manager { private static final int GALAXY_ID = 1; private final ScheduledExecutorService shutdownService; - private final OnlineInterfaceService onlineInterfaceService; private final EngineManager engineManager; private final GalacticManager galacticManager; private final PrintStream packetStream; @@ -79,7 +80,7 @@ public class CoreManager extends Manager { private boolean shutdownRequested; public CoreManager(int adminServerPort) { - Config c = getConfig(ConfigFile.PRIMARY); + Config c = DataManager.getConfig(ConfigFile.PRIMARY); Log.setLogLevel(LogLevel.valueOf(c.getString("LOG-LEVEL", LogLevel.DEBUG.name()))); setupGalaxy(c); setupCrcDatabase(); @@ -90,11 +91,9 @@ public class CoreManager extends Manager { packetDebug = packetStream != null; shutdownService = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("core-shutdown-service")); shutdownRequested = false; - onlineInterfaceService = new OnlineInterfaceService(); engineManager = new EngineManager(); galacticManager = new GalacticManager(); - addChildService(onlineInterfaceService); addChildService(engineManager); addChildService(galacticManager); diff --git a/src/services/EngineManager.java b/src/services/EngineManager.java index 97780dbb0..1cf52ab38 100644 --- a/src/services/EngineManager.java +++ b/src/services/EngineManager.java @@ -32,13 +32,15 @@ import java.io.IOException; import resources.client_info.ServerFactory; import resources.config.ConfigFile; -import resources.control.Manager; -import resources.server_info.Config; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import services.network.NetworkManager; +import com.projectswg.common.control.Manager; +import com.projectswg.common.info.Config; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public class EngineManager extends Manager { private ShutdownService shutdownService; @@ -56,7 +58,7 @@ public class EngineManager extends Manager { @Override public boolean initialize() { - Config config = getConfig(ConfigFile.PRIMARY); + Config config = DataManager.getConfig(ConfigFile.PRIMARY); if (config.getInt("CLEAN-CHARACTER-DATA", 0) == 1) wipeCharacterDatabase(); diff --git a/src/services/ShutdownService.java b/src/services/ShutdownService.java index 85ec5c6bf..582c9c3cc 100644 --- a/src/services/ShutdownService.java +++ b/src/services/ShutdownService.java @@ -27,6 +27,10 @@ ***********************************************************************************/ package services; +import intents.chat.ChatBroadcastIntent; +import intents.chat.ChatBroadcastIntent.BroadcastType; +import intents.server.ServerStatusIntent; + import java.util.Locale; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -34,13 +38,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import intents.chat.ChatBroadcastIntent; -import intents.chat.ChatBroadcastIntent.BroadcastType; -import intents.server.ServerStatusIntent; import resources.control.ServerStatus; -import resources.control.Service; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; + public class ShutdownService extends Service { private static final String SHUTDOWN_PERIODIC = "The server will be shutting down in %d %s."; diff --git a/src/services/admin/OnlineInterfaceService.java b/src/services/admin/OnlineInterfaceService.java deleted file mode 100644 index 251ab70e4..000000000 --- a/src/services/admin/OnlineInterfaceService.java +++ /dev/null @@ -1,241 +0,0 @@ -/************************************************************************************ - * 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.admin; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import intents.PlayerEventIntent; -import resources.common.BCrypt; -import resources.config.ConfigFile; -import resources.control.Service; -import resources.server_info.Config; -import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; -import services.admin.http.HttpServer; -import services.admin.http.HttpServer.HttpServerCallback; -import services.admin.http.HttpSession; -import services.admin.http.HttpSocket; -import services.admin.http.HttpSocket.HttpRequest; -import services.admin.http.HttpsServer; -import utilities.ThreadUtilities; - -public class OnlineInterfaceService extends Service implements HttpServerCallback { - - private static final String GET_USER_SQL = "SELECT password, banned FROM users WHERE LOWER(username) = LOWER(?)"; - - private final WebserverData data; - private final WebserverHandler handler; - private final Runnable dataCollectionRunnable; - private RelationalDatabase database; - private PreparedStatement getUser; - private ScheduledExecutorService executor; - private HttpsServer httpsServer; - private HttpServer httpServer; - private boolean authorized; - - public OnlineInterfaceService() { - data = new WebserverData(); - handler = new WebserverHandler(data); - dataCollectionRunnable = () -> collectData(); - authorized = false; - } - - @Override - public boolean initialize() { - database = RelationalServerFactory.getServerDatabase("login/login.db"); - getUser = database.prepareStatement(GET_USER_SQL); - Config network = getConfig(ConfigFile.NETWORK); - authorized = !network.getString("HTTPS-KEYSTORE-PASSWORD", "").isEmpty(); - if (!authorized) - return super.initialize(); - httpServer = new HttpServer(getBindAddr(network, "HTTP-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTP-PORT", 8080)); - httpsServer = new HttpsServer(getBindAddr(network, "HTTPS-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTPS-PORT", 8081)); - httpServer.setMaxConnections(network.getInt("HTTP-MAX-CONNECTIONS", 2)); - httpsServer.setMaxConnections(network.getInt("HTTPS-MAX-CONNECTIONS", 5)); - if (!httpsServer.initialize(network)) { - Log.e("Failed to initialize HTTPS server! Incorrect password?"); - httpServer.stop(); - httpsServer.stop(); - super.initialize(); - return false; - } - executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("ServerInterface-DataCollection")); - registerForIntent(PlayerEventIntent.class, pei -> handlePlayerEventIntent(pei)); - return super.initialize(); - } - - @Override - public boolean start() { - if (authorized) { - httpServer.setServerCallback(this); - httpsServer.setServerCallback(this); - httpServer.start(); - httpsServer.start(); - executor.scheduleAtFixedRate(dataCollectionRunnable, 0, 1, TimeUnit.SECONDS); - Log.i("Web server is now online."); - } - return super.start(); - } - - @Override - public boolean stop() { - if (authorized) { - httpServer.stop(); - httpsServer.stop(); - executor.shutdownNow(); - try { - executor.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.e(e); - } - } - return super.stop(); - } - - @Override - public boolean terminate() { - database.close(); - return super.terminate(); - } - - @Override - public void onSocketCreated(HttpSocket socket) { - Log.i("Received connection from: %s:%d [%s]", socket.getInetAddress(), socket.getPort(), socket.isSecure() ? "secure" : "insecure"); - } - - @Override - public void onRequestReceived(HttpSocket socket, HttpRequest request) { - try { - if (request.getType().equals("POST")) { - String [] variables = request.getBody().split("&"); - if (variables.length == 2) { - Map varMap = new HashMap<>(); - for (String str : variables) { - String [] var = str.split("="); - if (var.length == 2) - varMap.put(var[0], var[1]); - } - if (varMap.containsKey("username") && varMap.containsKey("password")) - login(socket, varMap.get("username"), varMap.get("password")); - } - } - if (!socket.isSecure()) { - try { - socket.redirect(new URL(request.getURI().getPath()).toString()); - } catch (MalformedURLException e) { - Log.w("Malformed URL: " + request.getURI().getPath()); - } - return; - } - handler.handleRequest(socket, request); - } catch (IOException e) { - Log.e(e); - } - } - - private void handlePlayerEventIntent(PlayerEventIntent pei){ - switch (pei.getEvent()) { - case PE_ZONE_IN_CLIENT: - data.addOnlinePlayer(pei.getPlayer()); - break; - case PE_LOGGED_OUT: - data.removeOnlinePlayer(pei.getPlayer()); - break; - default: - break; - } - } - - private void collectData() { - data.updateResourceUsage(); - } - - private InetAddress getBindAddr(Config c, String firstTry, String secondTry) { - String t = firstTry; - try { - if (c.containsKey(firstTry)) - return InetAddress.getByName(c.getString(firstTry, "127.0.0.1")); - t = secondTry; - if (c.containsKey(secondTry)) - return InetAddress.getByName(c.getString(secondTry, "127.0.0.1")); - } catch (UnknownHostException e) { - Log.e("Unknown host for IP: " + t); - } - return null; - } - - private void login(HttpSocket socket, String username, String password) { - HttpSession session = socket.getSession(); - if (session == null) - return; - synchronized (getUser) { - try { - getUser.setString(1, username); - boolean prevAuthenticated = session.isAuthenticated(); - session.setAuthenticated(false); - try (ResultSet cursor = getUser.executeQuery()) { - session.setAuthenticated(cursor.next() && isUserValid(cursor, password)); - if (session.isAuthenticated()) { - if (!prevAuthenticated) - Log.i("[%s] Successfully logged in to online interface", username); - } else { - Log.w("[%s] Failed to login to online interface. Incorrect user/pass", username); - } - } - } catch (SQLException e) { - Log.e(e); - } - } - } - - private boolean isUserValid(ResultSet set, String password) throws SQLException { - if (password.isEmpty()) - return false; - if (set.getBoolean("banned")) - return false; - String psqlPass = set.getString("password"); - if (psqlPass.length() != 60 && !psqlPass.startsWith("$2")) - return psqlPass.equals(password); - password = BCrypt.hashpw(BCrypt.hashpw(password, psqlPass), psqlPass); - return psqlPass.equals(password); - } - -} diff --git a/src/services/admin/WebserverData.java b/src/services/admin/WebserverData.java deleted file mode 100644 index f82fcd4f7..000000000 --- a/src/services/admin/WebserverData.java +++ /dev/null @@ -1,131 +0,0 @@ -/************************************************************************************ - * 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.admin; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import resources.player.Player; - -class WebserverData { - - private static final Charset ASCII = Charset.forName("ASCII"); - - private final double [] memoryUsage; - private final double [] cpuUsage; - private final double [] systemMemoryUsage; - private final double [] systemCpuUsage; - private final Set onlinePlayers; - - public WebserverData() { - memoryUsage = new double[60 * 5]; - cpuUsage = new double[memoryUsage.length]; - systemMemoryUsage = new double[memoryUsage.length]; - systemCpuUsage = new double[memoryUsage.length]; - onlinePlayers = new HashSet<>(); - } - - @SuppressWarnings("restriction") - public void updateResourceUsage() { - OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); - if (os instanceof com.sun.management.OperatingSystemMXBean) { - com.sun.management.OperatingSystemMXBean castedOS = (com.sun.management.OperatingSystemMXBean) os; - updateMemory(castedOS); - updateCPU(castedOS); - } - } - - public void addOnlinePlayer(Player player) { - onlinePlayers.add(player); - } - - public void removeOnlinePlayer(Player player) { - onlinePlayers.remove(player); - } - - public double [] getMemoryUsage() { - return Arrays.copyOf(memoryUsage, memoryUsage.length); - } - - public double [] getCpuUsage() { - return Arrays.copyOf(cpuUsage, cpuUsage.length); - } - - public double [] getSystemMemoryUsage() { - return Arrays.copyOf(systemMemoryUsage, systemMemoryUsage.length); - } - - public double [] getSystemCpuUsage() { - return Arrays.copyOf(systemCpuUsage, systemCpuUsage.length); - } - - public Set getOnlinePlayers() { - return Collections.unmodifiableSet(onlinePlayers); - } - - public String getLog() throws IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("log.txt")), ASCII))) { - StringBuilder builder = new StringBuilder(); - while (reader.ready()) { - builder.append(reader.readLine() + System.lineSeparator()); - } - reader.close(); - return builder.toString(); - } - } - - @SuppressWarnings("restriction") - private void updateMemory(com.sun.management.OperatingSystemMXBean os) { - Runtime r = Runtime.getRuntime(); - appendData(memoryUsage, (double) r.totalMemory() / os.getTotalPhysicalMemorySize()); - appendData(systemMemoryUsage, 1 - ((double) os.getFreePhysicalMemorySize() / os.getTotalPhysicalMemorySize())); - } - - @SuppressWarnings("restriction") - private void updateCPU(com.sun.management.OperatingSystemMXBean os) { - appendData(cpuUsage, os.getProcessCpuLoad()); - appendData(systemCpuUsage, os.getSystemCpuLoad()); - } - - private void appendData(double [] data, double value) { - for (int i = 1; i < data.length; i++) - data[i-1] = data[i]; - data[data.length-1] = value; - } - -} diff --git a/src/services/admin/WebserverHandler.java b/src/services/admin/WebserverHandler.java deleted file mode 100644 index 61397ea89..000000000 --- a/src/services/admin/WebserverHandler.java +++ /dev/null @@ -1,362 +0,0 @@ -/************************************************************************************ - * 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.admin; - -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import resources.Location; -import resources.objects.SWGObject; -import resources.objects.cell.CellObject; -import resources.player.Player; -import resources.server_info.Log; -import services.admin.http.HttpImageType; -import services.admin.http.HttpSession; -import services.admin.http.HttpSocket; -import services.admin.http.HttpSocket.HttpRequest; -import services.admin.http.HttpStatusCode; - -class WebserverHandler { - - private static final Charset ASCII = Charset.forName("ASCII"); - - private static final String REQUIRED_PREFIX = new File("res/webserver").getAbsolutePath().replace('/', File.separatorChar); - private static final String INDEX_PATH = new File("res/webserver/index.html".replace('/', File.separatorChar)).getAbsolutePath(); - private static final String AUTHENTICATED_PATH = new File("res/webserver/authenticated.html".replace('/', File.separatorChar)).getAbsolutePath(); - - private final WebserverData data; - private final Pattern variablePattern; - - public WebserverHandler(WebserverData data) { - this.data = data; - variablePattern = Pattern.compile("\\$\\{.+\\}"); // Looks for variables: ${VAR_NAME} - } - - public void handleRequest(HttpSocket socket, HttpRequest request) throws IOException { - String file = request.getURI().toASCIIString(); - Map getVariables = new HashMap<>(); - if (file.contains("?")) { - String vars = file.substring(file.indexOf('?')+1); - String [] variables = vars.split("&"); - for (String var : variables) { - String [] parts = var.split("=", 2); - if (parts.length == 2) - getVariables.put(parts[0], parts[1]); - } - file = file.substring(0, file.indexOf('?')); - } - if (file.contains("#")) - file = file.substring(0, file.indexOf('#')); - switch (file) { - case "/memory_usage.png": - if (socket.getSession().isAuthenticated()) - socket.send(createMemoryUsage(), HttpImageType.PNG); - else - socket.send(HttpStatusCode.NOT_FOUND, request.getURI() + " is not found!"); - break; - default: { - byte [] response = parseFile(socket.getSession(), file, getVariables); - if (response == null) - socket.send(HttpStatusCode.NOT_FOUND, request.getURI() + " is not found!"); - else - socket.send(getFileType(file), response); - break; - } - } - } - - private BufferedImage createMemoryUsage() { - double [] memoryUsage = data.getMemoryUsage(); - final int graphHeight = 300; - BufferedImage image = new BufferedImage(900, graphHeight + 25, BufferedImage.TYPE_3BYTE_BGR); - Graphics2D g = image.createGraphics(); - Font font = g.getFont(); - g.setFont(font.deriveFont(Font.TRUETYPE_FONT, Math.min(image.getHeight()/10.0f, 25.0f))); - Map hints = new HashMap<>(); - hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g.setRenderingHints(new RenderingHints(hints)); - g.setColor(Color.WHITE); - renderGraph(g, image.getWidth(), image.getHeight(), graphHeight); - FontMetrics fm = g.getFontMetrics(); - String inUse = getMemoryInUse(); - int width = fm.stringWidth(inUse); - int x = 5; - x += drawStr(g, image.getHeight(), x, "Proc:"); - g.drawString(inUse, image.getWidth()-width - 5, image.getHeight() - 5); - x += drawDataSet(g, Color.RED, " M:", image.getWidth(), image.getHeight(), graphHeight, x, memoryUsage); - x += drawDataSet(g, Color.CYAN, " C:", image.getWidth(), image.getHeight(), graphHeight, x, data.getCpuUsage()); - g.setColor(Color.WHITE); - x += drawStr(g, image.getHeight(), x, " Sys:"); - x += drawDataSet(g, Color.YELLOW, " M:", image.getWidth(), image.getHeight(), graphHeight, x, data.getSystemMemoryUsage()); - drawDataSet(g, Color.GREEN, " C:", image.getWidth(), image.getHeight(), graphHeight, x, data.getSystemCpuUsage()); - return image; - } - - private void renderGraph(Graphics2D g, int width, int height, int graphHeight) { - g.drawRect(0, 0, width-1, graphHeight-1); - g.drawLine(0, height-1, width, height-1); - for (int i = 1; i < 10; i++) { - g.drawLine(0, (int) ((i/10.0)*graphHeight), 10, (int) ((i/10.0)*graphHeight)); - } - } - - private int drawDataSet(Graphics2D g, Color c, String pre, int width, int height, int graphHeight, int x, double [] data) { - g.setColor(c); - drawLines(g, data, width, graphHeight-1); - return drawStr(g, height, x, String.format("%s%.2f%%", pre, (int) (data[data.length-1] * 10000) / 100.0)); - } - - private int drawStr(Graphics2D g, int height, int x, String str) { - g.drawString(str, x, height - 5); - return g.getFontMetrics().stringWidth(str); - } - - private void drawLines(Graphics2D g, double [] data, int width, int height) { - int start = 0; - for (double d : data) { - if (d == 0) - start++; - else - break; - } - int prevX = -1; - int prevY = -1; - for (int i = start; i < data.length; i++) { - int x = (int) ((double) i / data.length * width); - int y = (int) ((1-data[i]) * height); - if (prevX != -1 && prevY != -1) - g.drawLine(prevX, prevY, x, y); - prevX = x; - prevY = y; - } - } - - private String getMemoryInUse() { - double memory = Runtime.getRuntime().totalMemory(); - String [] types = new String[]{"B", "KB", "MB", "GB"}; - String type = types[0]; - for (int i = 0; i < types.length && memory >= 1024; i++) { - memory /= 1024; - type = types[i]; - } - return String.format("In-Use: %.2f%s", memory, type); - } - - private String getFileType(String filepath) { - if (!filepath.contains(".")) - return "text/html"; - String type = filepath.substring(filepath.lastIndexOf('.')+1).toLowerCase(Locale.US); - switch (type) { - case "png": - case "gif": - case "jpg": - case "jpeg": - case "ico": - return "image/" + type; - default: - return "text/" + type; - } - } - - private byte [] parseFile(HttpSession session, String filepath, Map getVariables) throws IOException { - String type = getFileType(filepath); - File file = verifyAndModifyFile(session, filepath, type); - if (file == null) - return null; - if (type.equalsIgnoreCase("text/html")) - return parseHtmlFile(file, getVariables).getBytes(ASCII); - try (InputStream is = new FileInputStream(file)) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(is.available()); - byte [] buffer = new byte[Math.min(1024, is.available())]; - while (is.available() > 0) { - if (is.available() > buffer.length && buffer.length < 1024) - buffer = new byte[Math.min(1024, is.available())]; - int len = is.read(buffer); - baos.write(buffer, 0, len); - } - return baos.toByteArray(); - } - } - - private File verifyAndModifyFile(HttpSession session, String filepath, String type) { - File file = new File("res/webserver" + filepath); - if (File.separatorChar != '/') - file = new File(file.getAbsolutePath().replace('/', File.separatorChar)); - if (file.isDirectory()) - file = new File(file, "index.html"); - if (file.getAbsolutePath().equals(INDEX_PATH)) { - if (session.isAuthenticated()) { - file = new File(AUTHENTICATED_PATH); - } - } else if (!session.isAuthenticated() && !type.equals("text/css") && !type.equals("text/js") && !type.startsWith("image")) - return null; - if (!verifyPath(file)) { - Log.e("Cannot access %s - not a valid path", file); - return null; - } - return file; - } - - private boolean verifyPath(File file) { - if (!file.getAbsolutePath().startsWith(REQUIRED_PREFIX)) - return false; - if (!file.isFile()) - return false; - return true; - } - - private String parseHtmlFile(File file, Map getVariables) throws IOException { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { - String line = null; - StringBuilder builder = new StringBuilder(); - while (reader.ready()) { - line = reader.readLine(); - if (line == null) - break; - Matcher matcher = variablePattern.matcher(line); - while (matcher.find()) { - String var = matcher.group(); - var = var.substring(2, var.length()-1); - line = line.replaceAll("\\$\\{"+var+"\\}", getVariable(var, getVariables)); - matcher.reset(line); - } - builder.append(line + System.lineSeparator()); - } - return builder.toString(); - } - } - - private String getVariable(String var, Map getVariables) throws IOException { - var = var.toLowerCase(Locale.US); - switch (var) { - case "log": - return data.getLog().replace("\n", "\n
"); - case "online_player_count": - return getOnlinePlayerCount(); - case "online_players": - return getOnlinePlayerData(); - case "character_info": { - if (!getVariables.containsKey("character_id")) - return ""; - long i = Long.parseLong(getVariables.get("character_id")); - for (Player p : data.getOnlinePlayers()) { - if (p.getCreatureObject().getObjectId() == i) { - return getCreatureData(p.getCreatureObject()); - } - } - return "No Creature Found!"; - } - default: - return ""; - } - } - - private String getOnlinePlayerCount() { - Set players = data.getOnlinePlayers(); - return "Online Players: [" + players.size() + "]"; - } - - private String getOnlinePlayerData() { - Set players = data.getOnlinePlayers(); - StringBuilder ret = new StringBuilder(""); - for (Player p : players) { - ret.append(""); - long id = p.getCreatureObject().getObjectId(); - ret.append(String.format("", id, p.getUsername())); - ret.append(String.format("", id, p.getUserId())); - ret.append(String.format("", id, p.getCharacterName())); - ret.append(String.format("", id, p.getCreatureObject().getObjectId())); - ret.append(""); - } - ret.append("
UsernameUser IDCharacterCharacter ID
%s%d%s%d
"); - return ret.toString(); - } - - private String getCreatureData(SWGObject creature) { - StringBuilder str = new StringBuilder(""); - Location world = creature.getWorldLocation(); - str.append(String.format(" Object ID: %s
", creature.getObjectId())); - str.append(String.format(" Name: %s
", creature.getObjectName())); - str.append(String.format("World:
")); - str.append(addLocationData(world)); - if (creature.getParent() != null) { - Location cell = creature.getLocation(); - str.append(String.format("Local:
")); - str.append(addLocationData(cell)); - SWGObject parent = creature.getParent(); - if (parent != null) { - str.append(String.format(" Parent:
")); - str.append(addParentData(parent, " ")); - parent = parent.getParent(); - if (parent != null) { - str.append(String.format(" Grandparent:
")); - str.append(addParentData(parent, " ")); - } - } - } - return str.toString().replace(" ", " ").replace("", "
"); - } - - private String addParentData(SWGObject obj, String indent) { - StringBuilder str = new StringBuilder(""); - str.append(indent + String.format(" Object ID: %d
", obj.getObjectId())); - str.append(indent + String.format(" Template: %s
", obj.getTemplate())); - if (obj instanceof CellObject) { - str.append(indent + String.format(" Cell Index: %d
", ((CellObject) obj).getNumber())); - str.append(indent + String.format(" Cell Name: %s
", ((CellObject) obj).getCellName())); - } - return str.toString(); - } - - private String addLocationData(Location l) { - StringBuilder str = new StringBuilder(""); - str.append(String.format(" Location: %.2f, %.2f, %.2f [%s]
", l.getX(), l.getY(), l.getZ(), l.getTerrain())); - str.append(String.format("Orientation: %.2f, %.2f, %.2f, %.2f
", l.getOrientationX(), l.getOrientationY(), l.getOrientationZ(), l.getOrientationW())); - return str.toString(); - } - -} diff --git a/src/services/admin/http/HttpCookie.java b/src/services/admin/http/HttpCookie.java deleted file mode 100644 index 3022d0231..000000000 --- a/src/services/admin/http/HttpCookie.java +++ /dev/null @@ -1,239 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Set; - -public class HttpCookie implements Comparable { - - private static final String EXPIRES_FORMAT = "EEE, dd-MM-yyyy HH:mm:ss zzz"; // Wed, 18-05-2015 16:00:00 GMT - - private String key; - private String value; - private String domain; - private String path; - private long expires; - private Set flags; - - public HttpCookie(String value, CookieFlag ... flags) { - if (value == null) - throw new IllegalArgumentException("Value for cookie cannot be null!"); - this.key = null; - this.value = value; - if (flags.length == 0) - this.flags = EnumSet.noneOf(CookieFlag.class); - else - this.flags = EnumSet.copyOf(Arrays.asList(flags)); - this.domain = null; - this.path = null; - this.expires = 0; - } - - public HttpCookie(String key, String value, CookieFlag ... flags) { - this(value, flags); - this.key = key; - } - - public HttpCookie(String key, String value, long expires, CookieFlag ... flags) { - this(key, value, flags); - this.expires = expires; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - public String getDomain() { - return domain; - } - - public String getPath() { - return path; - } - - public long getExpires() { - return expires; - } - - public Set getFlags() { - return Collections.unmodifiableSet(flags); - } - - public boolean hasFlag(CookieFlag flag) { - return flags.contains(flag); - } - - public boolean hasExpired() { - return expires != 0 && System.currentTimeMillis() >= expires; - } - - public void setKey(String key) { - this.key = key; - } - - public void setValue(String value) { - this.value = value; - } - - public void setDomain(String domain) { - this.domain = domain; - } - - public void setPath(String path) { - this.path = path; - } - - public void setExpires(long expires) { - this.expires = expires; - } - - public void addFlag(CookieFlag flag) { - flags.add(flag); - } - - public void removeFlag(CookieFlag flag) { - flags.remove(flag); - } - - @Override - public int compareTo(HttpCookie c) { - if (key != null && c.key == null) - return 1; - if (key == null && c.key != null) - return -1; - if (!value.equals(c.value)) - return value.compareTo(c.value); - if (expires < c.expires) - return -1; - if (expires > c.expires) - return 1; - return 0; - } - - @Override - public int hashCode() { - int code = 0; - if (key != null) - code += key.hashCode(); - code ^= value.hashCode(); - if (expires != 0) - code ^= Long.valueOf(expires).hashCode(); - return code; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (!(obj instanceof HttpCookie)) - return false; - HttpCookie c = (HttpCookie) obj; - if (key != null && c.key == null) - return false; - if (c.key != null && key == null) - return false; - if (!c.value.equals(value)) - return false; - return c.expires == expires; - } - - public String encode() { - StringBuilder str = new StringBuilder(""); - if (key == null) - str.append(value); - else - str.append(encodeCookieString(key) + "=" + encodeCookieString(value)); - if (expires != 0) - str.append("; expires=" + encodeCookieString(new SimpleDateFormat(EXPIRES_FORMAT).format(expires))); - if (domain != null) - str.append("; domain=" + encodeCookieString(domain)); - if (path != null) - str.append("; path=" + encodeCookieString(path)); - for (CookieFlag flag : flags) - str.append("; " + flag.encode()); - return str.toString(); - } - - private String encodeCookieString(String str) { - return str.replace(" ", "%20").replace("\t", "%09").replace("\n", "%0A").replace("&", "%26").replace(";", "%3B"); - } - - public static HttpCookie [] decodeCookies(String str) { - String [] parts = str.split("; "); - HttpCookie [] cookies = new HttpCookie[parts.length]; - for (int i = 0; i < parts.length; i++) { - cookies[i] = decodeCookie(decodeCookieString(parts[i])); - } - return cookies; - } - - private static HttpCookie decodeCookie(String str) { - String [] parts = str.split("=", 2); - if (parts.length == 0) - return new HttpCookie(""); - if (parts.length == 1) - return new HttpCookie(parts[0]); - return new HttpCookie(parts[0], parts[1]); - } - - private static String decodeCookieString(String str) { - return str.replace("%20", " ").replace("%09", "\t").replace("%0A", "\n").replace("%26", "&").replace("%3B", ";"); - } - - public static enum CookieFlag { - /** - * Security feature where this cookie is only sent from the client - * over a secure HTTPS connection. - */ - SECURE ("secure"), - /** - * Security feature to avoid Cross Site Scripting (XSS), where this - * cookie is unable to be accessed through javascript - only via HTTP. - */ - HTTP_ONLY ("HttpOnly"); - - private String encoded; - - CookieFlag(String encoded) { - this.encoded = encoded; - } - - protected String encode() { - return encoded; - } - } - -} diff --git a/src/services/admin/http/HttpImageType.java b/src/services/admin/http/HttpImageType.java deleted file mode 100644 index 469864c1a..000000000 --- a/src/services/admin/http/HttpImageType.java +++ /dev/null @@ -1,34 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -public enum HttpImageType { - GIF, - PNG, - JPG -} diff --git a/src/services/admin/http/HttpServer.java b/src/services/admin/http/HttpServer.java deleted file mode 100644 index 136bff31b..000000000 --- a/src/services/admin/http/HttpServer.java +++ /dev/null @@ -1,263 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import resources.server_info.Log; -import services.admin.http.HttpSocket.HttpRequest; -import utilities.ThreadUtilities; - -public class HttpServer { - - private static final String TAG = "HttpServer"; - - private final InetAddress addr; - private final int port; - private final Map sessions; - - private ExecutorService executor; - private ServerSocket serverSocket; - private AtomicInteger currentConnections; - private HttpServerCallback callback; - private int maxConnections; - private boolean secure; - - protected HttpServer(InetAddress addr, int port, boolean secure) { - this.addr = addr; - this.port = port; - this.sessions = new HashMap<>(); - this.currentConnections = new AtomicInteger(0); - this.callback = null; - this.maxConnections = 2; - this.secure = secure; - } - - public HttpServer(InetAddress addr, int port) { - this(addr, port, false); - } - - public void start() { - executor = Executors.newCachedThreadPool(ThreadUtilities.newThreadFactory(getThreadFactoryName())); - try { - serverSocket = createSocket(); - startAcceptThread(serverSocket, secure); - } catch (IOException e) { - Log.e(e); - } - } - - public void stop() { - if (serverSocket != null) { - try { - serverSocket.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - if (executor != null) { - executor.shutdownNow(); - try { - executor.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.e(e); - } - } - } - - public void setServerCallback(HttpServerCallback callback) { - this.callback = callback; - } - - public void setMaxConnections(int maxConnections) { - this.maxConnections = maxConnections; - } - - public final InetAddress getBindAddress() { - return addr; - } - - public final int getBindPort() { - return port; - } - - protected String getThreadFactoryName() { - return "HttpServer-%d"; - } - - protected ServerSocket createSocket() throws IOException { - return new ServerSocket(port, 0, addr); - } - - private void startAcceptThread(ServerSocket serverSocket, boolean secure) { - executor.execute(() -> { - try { - acceptThread(serverSocket, secure); - } catch (Throwable t) { - t.printStackTrace(); - } - }); - } - - private void startSocketThread(HttpSocket socket) { - executor.execute(() -> { - try { - socketThread(socket); - } catch (Throwable t) { - t.printStackTrace(); - } - try { - socket.close(); - } catch (IOException e) { - Log.e(e); - } - synchronized (currentConnections) { - currentConnections.decrementAndGet(); - } - }); - } - - private void acceptThread(ServerSocket serverSocket, boolean secure) { - Log.i("Now listening over HTTP%s. Address: %s:%d", secure?"S":"", serverSocket.getInetAddress(), serverSocket.getLocalPort()); - while (serverSocket.isBound() && !serverSocket.isClosed()) { - try { - Socket socket = serverSocket.accept(); - HttpSocket httpSocket = new HttpSocket(socket, secure); - if (!httpSocket.isOpened()) { - httpSocket.close(); - continue; - } - if (currentConnections.get() >= maxConnections) { - httpSocket.send(HttpStatusCode.SERVICE_UNAVAILABLE, "The server has reached it's maximum connection limit: " + maxConnections); - socket.close(); - } else { - if (currentConnections.incrementAndGet() > maxConnections) { - socket.close(); - currentConnections.decrementAndGet(); - } else { - startSocketThread(httpSocket); - } - } - } catch (SocketException e) { - if (serverSocket.isClosed()) - break; - Log.e(e); - } catch (Throwable t) { - t.printStackTrace(); - } - } - Log.i("No longer listening over HTTP%s!", secure?"S":""); - } - - private void socketThread(HttpSocket socket) throws IOException { - onSocketCreated(socket); - HttpRequest request; - while (socket.isOpened() && !socket.isClosed()) { - request = socket.waitForRequest(); - if (request == null) - break; - if (request.getType() == null || request.getURI() == null || request.getHttpVersion() == null) { - socket.send(HttpStatusCode.BAD_REQUEST); - continue; - } - onRequestReceived(socket, request); - } - } - - private HttpSession getSessionForRequest(HttpRequest request) { - String token = null; - for (HttpCookie cookie : request.getCookies()) { - if (cookie.getKey() != null && cookie.getKey().equals("sessionToken")) { - if (sessions.containsKey(cookie.getValue())) - token = cookie.getValue(); - } - } - if (token == null) - token = generateSessionToken(); - HttpSession session = sessions.get(token); - if (session == null) { - session = new HttpSession(token); - sessions.put(token, session); - } - return session; - } - - private String generateSessionToken() { - Random r = new Random(); - StringBuilder builder = new StringBuilder(24); - int rand; - for (int i = 0; i < 24; i++) { - rand = r.nextInt(26*2+10); - if (rand < 26) - builder.append((char) ('A' + rand)); - else if (rand < 52) - builder.append((char) ('a' + (rand-26))); - else - builder.append((char) ('0' + (rand-52))); - } - return builder.toString(); - } - - private void onSocketCreated(HttpSocket socket) { - if (callback == null) - return; - try { - callback.onSocketCreated(socket); - } catch (Throwable t) { - t.printStackTrace(); - } - } - - private void onRequestReceived(HttpSocket socket, HttpRequest request) { - socket.setSession(getSessionForRequest(request)); - if (callback == null) - return; - try { - callback.onRequestReceived(socket, request); - } catch (Throwable t) { - t.printStackTrace(); - } - } - - public interface HttpServerCallback { - void onSocketCreated(HttpSocket socket); - void onRequestReceived(HttpSocket socket, HttpRequest request); - } - -} diff --git a/src/services/admin/http/HttpSession.java b/src/services/admin/http/HttpSession.java deleted file mode 100644 index 00b236587..000000000 --- a/src/services/admin/http/HttpSession.java +++ /dev/null @@ -1,52 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -public class HttpSession { - - private final String token; - private boolean authenticated; - - public HttpSession(String token) { - this.token = token; - this.authenticated = false; - } - - public String getToken() { - return token; - } - - public boolean isAuthenticated() { - return authenticated; - } - - public void setAuthenticated(boolean authenticated) { - this.authenticated = authenticated; - } - -} diff --git a/src/services/admin/http/HttpSocket.java b/src/services/admin/http/HttpSocket.java deleted file mode 100644 index 10c56ae54..000000000 --- a/src/services/admin/http/HttpSocket.java +++ /dev/null @@ -1,362 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.net.InetAddress; -import java.net.Socket; -import java.net.URI; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import javax.imageio.ImageIO; - -import resources.server_info.Log; -import services.admin.http.HttpCookie.CookieFlag; - -public class HttpSocket implements Closeable { - - private static final Charset ASCII = Charset.forName("ASCII"); - - private final Socket socket; - private final BufferedReader reader; - private final BufferedWriter writer; - private final OutputStream rawOutputStream; - private final boolean secure; - private HttpSession session; - - public HttpSocket(Socket socket, boolean secure) { - this.socket = socket; - this.reader = createBufferedReader(); - this.writer = createBufferedWriter(); - this.rawOutputStream = getOutputStream(); - this.secure = secure; - this.session = null; - } - - /** - * Waits for the next HTTP request to come in, then returns the request - * sent by the client - or null if the client closes the connection. - * @return the HttpRequest sent by the client, or null if the connection is - * closed - */ - public HttpRequest waitForRequest() { - while (!socket.isClosed()) { - String line = readLine(); - boolean hasData = line != null && !line.isEmpty(); - String [] req = null; - Map params = new HashMap<>(); - Set cookies = new HashSet<>(); - StringBuilder body = new StringBuilder(""); - while (line != null && !line.isEmpty()) { - if (req == null) - req = line.split(" ", 3); - String [] parts = line.split(": ", 2); - if (parts.length == 2) { - if (parts[0].equals("Cookie")) { - cookies.addAll(Arrays.asList(HttpCookie.decodeCookies(parts[1]))); - } else { - params.put(parts[0], parts[1]); - } - } - hasData = !line.isEmpty(); - line = readLine(); - } - if (params.containsKey("Content-Length")) { - Integer i = Integer.valueOf(params.get("Content-Length")); - if (i.intValue() > 0) { - String bodyStr = readBytes(i.intValue()); - if (bodyStr == null) - Log.e("Failed to read data in %s request: %s - returned null", req.length==0?"null":req[0], req.length<2?"null":req[1]); - else { - if (bodyStr.length() != i.intValue()) - Log.w("Failed to read all data in %s request: %s", req.length==0?"null":req[0], req.length<2?"null":req[1]); - body.append(bodyStr); - } - } - } - if (hasData) { - String type = null; - URI uri = null; - String version = null; - if (req.length >= 1) - type = req[0]; - if (req.length >= 2) - uri = URI.create(req[1]); - if (req.length >= 3) - version = req[2]; - return new HttpRequest(type, uri, version, params, cookies, body.toString()); - } - if (line == null) - break; - } - return null; - } - - public void setSession(HttpSession session) { - this.session = session; - } - - public HttpSession getSession() { - return session; - } - - public void redirect(String url) throws IOException { - Map params = new HashMap<>(); - params.put("Location", url); - send(HttpStatusCode.MOVED_PERMANENTLY, params, new HashSet<>(), "text/html", ""); - } - - public void send(HttpStatusCode code) throws IOException { - send(code, ""); - } - - public void send(String response) throws IOException { - send(HttpStatusCode.OK, response); - } - - public void send(String contentType, byte [] response) throws IOException { - send(HttpStatusCode.OK, new HashMap<>(), new HashSet<>(), contentType, response); - } - - public void send(BufferedImage image, HttpImageType type) throws IOException { - String contentType = "image"; - switch (type) { - case GIF: - contentType = "image/gif"; - break; - case JPG: - contentType = "image/jpeg"; - break; - case PNG: - contentType = "image/png"; - break; - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(image.getWidth() * image.getHeight() * 3); - if (!ImageIO.write(image, type.name().toLowerCase(Locale.US), baos)) - throw new IllegalArgumentException("Cannot write image with type: " + type); - send(HttpStatusCode.OK, new HashMap<>(), new HashSet<>(), contentType, baos.toByteArray()); - } - - public void send(HttpStatusCode code, String response) throws IOException { - if (code == HttpStatusCode.OK && response.isEmpty()) - code = HttpStatusCode.NO_CONTENT; - send(code, new HashMap<>(), new HashSet<>(), "text/html", response); - } - - private void send(HttpStatusCode code, Map params, Set cookies, String contentType, String response) throws IOException { - params.put("Content-Length", Integer.toString(response.length())); - params.put("Content-Type", contentType); - sendHeader(code, params, cookies); - writer.write(response); - writer.flush(); - } - - private void send(HttpStatusCode code, Map params, Set cookies, String contentType, byte [] data) throws IOException { - params.put("Content-Length", Integer.toString(data.length)); - params.put("Content-Type", contentType); - sendHeader(code, params, cookies); - writer.flush(); - rawOutputStream.write(data); - rawOutputStream.flush(); - } - - private void sendHeader(HttpStatusCode code, Map params, Set cookies) throws IOException { - if (session != null) - cookies.add(new HttpCookie("sessionToken", session.getToken(), CookieFlag.SECURE, CookieFlag.HTTP_ONLY)); - write("HTTP/1.1 %d %s", code.getCode(), code.getName()); - for (Entry param : params.entrySet()) - write(param.getKey() + ": " + param.getValue()); - for (HttpCookie cookie : cookies) - write("Set-Cookie: " + cookie.encode()); - writer.newLine(); - } - - private String readLine() { - try { - return reader.readLine(); - } catch (Exception e) { - return null; - } - } - - private String readBytes(int length) { - try { - if (length == 0) - return ""; - char [] data = new char[length]; - int read = reader.read(data); - if (read == -1) - return null; - return new String(data, 0, read); - } catch (IOException e) { - Log.e(e); - } - return null; - } - - @Override - public void close() throws IOException { - socket.close(); - } - - public boolean isOpened() { - return reader != null && writer != null; - } - - public boolean isClosed() { - return socket.isClosed(); - } - - public boolean isSecure() { - return secure; - } - - public InetAddress getInetAddress() { - return socket.getInetAddress(); - } - - public int getPort() { - return socket.getPort(); - } - - private void write(String str, Object ... args) throws IOException { - writer.write(String.format(str, args) + System.lineSeparator()); - } - - private BufferedReader createBufferedReader() { - try { - return new BufferedReader(new InputStreamReader(socket.getInputStream(), ASCII)); - } catch (IOException e) { - Log.e(e); - return null; - } - } - - private BufferedWriter createBufferedWriter() { - try { - return new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), ASCII)); - } catch (IOException e) { - Log.e(e); - return null; - } - } - - private OutputStream getOutputStream() { - try { - return socket.getOutputStream(); - } catch (IOException e) { - Log.e(e); - return null; - } - } - - public static class HttpRequest { - private final String type; - private final URI uri; - private final String httpVersion; - private final Map params; - private final Set cookies; - private final String body; - - private HttpRequest(String requestType, URI uri, String httpVersion, Map params, Set cookies, String body) { - this.type = requestType; - this.uri = uri; - this.httpVersion = httpVersion; - this.params = params; - this.cookies = cookies; - this.body = body; - } - - /** - * Gets the type of HTTP request sent. It could be one of: GET, POST, - * HEAD. - * @return the type of HTTP request - */ - public String getType() { - return type; - } - - /** - * Gets the requested URI sent - * @return the requested URI - */ - public URI getURI() { - return uri; - } - - /** - * Gets the HTTP version of the client, typical format is: HTTP/1.1 - * @return the HTTP version of the client - */ - public String getHttpVersion() { - return httpVersion; - } - - /** - * Gets all the paramters sent in the HTTP request, such as: - * "User-Agent", "Accept", etc. - * @return the HTTP request parameters - */ - public Map getParams() { - return Collections.unmodifiableMap(params); - } - - /** - * Gets all the cookies sent in the HTTP request - * @return the cookies - */ - public Set getCookies() { - return Collections.unmodifiableSet(cookies); - } - - /** - * Gets the request body - * @return the request body - */ - public String getBody() { - return body; - } - } - -} diff --git a/src/services/admin/http/HttpStatusCode.java b/src/services/admin/http/HttpStatusCode.java deleted file mode 100644 index ea28004fd..000000000 --- a/src/services/admin/http/HttpStatusCode.java +++ /dev/null @@ -1,75 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -public enum HttpStatusCode { - /** The request is OK (this is the standard response for successful HTTP requests) */ - OK (200, "OK"), - /** The request has been successfully processed, but is not returning any content */ - NO_CONTENT (204, "No Content"), - /** The requested page has moved to a new URL */ - MOVED_PERMANENTLY (301, "Moved Permanently"), - /** The requested page has moved temporarily to a new URL */ - FOUND (302, "Found"), - /** The requested page can be found under a different URL */ - SEE_OTHER (303, "See Other"), - /** The requested page has moved temporarily to a new URL */ - TEMPORARY_REDIRECT (307, "Temporary Redirect"), - /** The request cannot be fulfilled due to bad syntax */ - BAD_REQUEST (400, "Bad Request"), - /** The request was a legal request, but the server is refusing to respond to it. - For use when authentication is possible but has failed or not yet been provided */ - UNAUTHORIZED (401, "Unauthorized"), - /** The request was a legal request, but the server is refusing to respond to it */ - FORBIDDEN (403, "Forbidden"), - /** The requested page could not be found but may be available again in the future */ - NOT_FOUND (404, "Not Found"), - /** A request was made of a page using a request method not supported by that page */ - METHOD_NOT_ALLOWED (405, "Method Not Allowed"), - /** A generic error message, given when no more specific message is suitable */ - INTERNAL_ERROR (500, "Internal Error"), - /** The server is currently unavailable (overloaded or down) */ - SERVICE_UNAVAILABLE (503, ""); - - private int code; - private String name; - - HttpStatusCode(int code, String name) { - this.code = code; - this.name = name; - } - - public int getCode() { - return code; - } - - public String getName() { - return name; - } - -} diff --git a/src/services/admin/http/HttpsServer.java b/src/services/admin/http/HttpsServer.java deleted file mode 100644 index baea49fe8..000000000 --- a/src/services/admin/http/HttpsServer.java +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************************************************ - * 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.admin.http; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocketFactory; - -import resources.server_info.Config; -import resources.server_info.Log; - -public class HttpsServer extends HttpServer { - - private static final String SERVER_KEYSTORE = "server.keystore"; - - private SSLContext sslContext; - private SSLServerSocketFactory sslServerSocketFactory; - - public HttpsServer(InetAddress addr, int port) { - super(addr, port, true); - } - - public boolean initialize(Config config) { - String pass = config.getString("HTTPS-KEYSTORE-PASSWORD", ""); - System.setProperty("javax.net.ssl.keyStore", SERVER_KEYSTORE); - System.setProperty("javax.net.ssl.keyStorePassword", pass); - try { - sslContext = SSLContext.getInstance("TLSv1.2"); - KeyManager [] managers = createKeyManagers(pass.toCharArray()); - if (managers == null) - return false; - sslContext.init(managers, null, null); - sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); - return true; - } catch (Exception e) { - Log.e(e); - return false; - } - } - - protected String getThreadFactoryName() { - return "HttpsServer-%d"; - } - - protected ServerSocket createSocket() throws IOException { - try { - return sslServerSocketFactory.createServerSocket(getBindPort(), 0, getBindAddress()); - } catch (IOException e) { - Log.e("Failed to start HTTPS server!"); - Log.e(e); - } - return null; - } - - private KeyManager [] createKeyManagers(char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException { - KeyStore ks = KeyStore.getInstance("JKS"); - InputStream ksIs = new FileInputStream(SERVER_KEYSTORE); - try { - ks.load(ksIs, password); - } catch (IOException e) { - return null; // Password invalid - } finally { - ksIs.close(); - } - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, password); - return kmf.getKeyManagers(); - } - -} diff --git a/src/services/chat/ChatMailService.java b/src/services/chat/ChatMailService.java index 827ad70fa..34b28b7a6 100644 --- a/src/services/chat/ChatMailService.java +++ b/src/services/chat/ChatMailService.java @@ -47,7 +47,6 @@ import network.packets.swg.zone.chat.ChatPersistentMessageToClient; import network.packets.swg.zone.chat.ChatPersistentMessageToServer; import network.packets.swg.zone.chat.ChatRequestPersistentMessage; import resources.chat.ChatResult; -import resources.control.Service; import resources.encodables.player.Mail; import resources.objects.SWGObject; import resources.objects.player.PlayerObject; @@ -55,13 +54,15 @@ import resources.player.Player; import resources.server_info.CachedObjectDatabase; import resources.server_info.Log; import resources.server_info.ObjectDatabase; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import services.CoreManager; import services.chat.ChatManager.ChatRange; import services.chat.ChatManager.ChatType; import services.player.PlayerManager; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + public class ChatMailService extends Service { private final ObjectDatabase mails; diff --git a/src/services/chat/ChatManager.java b/src/services/chat/ChatManager.java index cb009b44e..a72fb93b0 100644 --- a/src/services/chat/ChatManager.java +++ b/src/services/chat/ChatManager.java @@ -27,16 +27,17 @@ ***********************************************************************************/ package services.chat; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Locale; - import intents.NotifyPlayersPacketIntent; import intents.PlayerEventIntent; import intents.chat.ChatAvatarRequestIntent; import intents.chat.ChatBroadcastIntent; import intents.chat.SpatialChatIntent; import intents.network.GalacticPacketIntent; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Locale; + import network.packets.Packet; import network.packets.swg.SWGPacket; import network.packets.swg.zone.chat.ChatFriendsListUpdate; @@ -49,8 +50,6 @@ import network.packets.swg.zone.object_controller.SpatialChat; import resources.Terrain; import resources.chat.ChatAvatar; import resources.chat.ChatResult; -import resources.control.Assert; -import resources.control.Manager; import resources.encodables.OutOfBandPackage; import resources.encodables.ProsePackage; import resources.objects.SWGObject; @@ -58,8 +57,11 @@ import resources.objects.player.PlayerObject; import resources.player.Player; import resources.player.PlayerState; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.control.Manager; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; public class ChatManager extends Manager { diff --git a/src/services/chat/ChatRoomContainer.java b/src/services/chat/ChatRoomContainer.java index 12e130f8c..351ecf9b0 100644 --- a/src/services/chat/ChatRoomContainer.java +++ b/src/services/chat/ChatRoomContainer.java @@ -33,9 +33,10 @@ import java.util.List; import java.util.Map; import resources.chat.ChatRoom; -import resources.control.Assert; import resources.server_info.Log; +import com.projectswg.common.debug.Assert; + public class ChatRoomContainer { private final Object mapMutex; diff --git a/src/services/chat/ChatRoomHandler.java b/src/services/chat/ChatRoomHandler.java index a197cd630..db5507167 100644 --- a/src/services/chat/ChatRoomHandler.java +++ b/src/services/chat/ChatRoomHandler.java @@ -33,6 +33,10 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + import network.packets.swg.zone.chat.ChatOnDestroyRoom; import network.packets.swg.zone.chat.ChatOnEnteredRoom; import network.packets.swg.zone.chat.ChatOnLeaveRoom; @@ -43,15 +47,12 @@ import resources.chat.ChatResult; import resources.chat.ChatRoom; import resources.client_info.ServerFactory; import resources.client_info.visitors.DatatableData; -import resources.control.Assert; import resources.encodables.OutOfBandPackage; import resources.player.AccessLevel; import resources.player.Player; import resources.server_info.CachedObjectDatabase; import resources.server_info.Log; import resources.server_info.ObjectDatabase; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import services.CoreManager; import services.chat.ChatManager.ChatRange; import services.chat.ChatManager.ChatType; diff --git a/src/services/chat/ChatRoomService.java b/src/services/chat/ChatRoomService.java index 9ae9b4b27..08dd0b660 100644 --- a/src/services/chat/ChatRoomService.java +++ b/src/services/chat/ChatRoomService.java @@ -61,9 +61,10 @@ import network.packets.swg.zone.insertion.ChatRoomList; import resources.chat.ChatAvatar; import resources.chat.ChatResult; import resources.chat.ChatRoom; -import resources.control.Service; -import resources.player.Player; import resources.encodables.OutOfBandPackage; +import resources.player.Player; + +import com.projectswg.common.control.Service; public class ChatRoomService extends Service { diff --git a/src/services/collections/CollectionBadgeManager.java b/src/services/collections/CollectionBadgeManager.java index cf3b96899..7cac13184 100644 --- a/src/services/collections/CollectionBadgeManager.java +++ b/src/services/collections/CollectionBadgeManager.java @@ -29,16 +29,15 @@ package services.collections; -import java.util.BitSet; - import intents.GrantBadgeIntent; import intents.GrantClickyCollectionIntent; import intents.object.DestroyObjectIntent; + +import java.util.BitSet; + import network.packets.swg.zone.PlayMusicMessage; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; -import resources.control.Intent; -import resources.control.Manager; import resources.objects.SWGObject; import resources.objects.collections.ClickyCollectionItem; import resources.objects.collections.CollectionItem; @@ -47,6 +46,8 @@ import resources.objects.player.PlayerObject; import resources.player.Player; import utilities.IntentFactory; +import com.projectswg.common.control.Manager; + public class CollectionBadgeManager extends Manager { // TODO grant item rewards and/or schematics diff --git a/src/services/collections/CollectionService.java b/src/services/collections/CollectionService.java index d190cfd9e..c55563cff 100644 --- a/src/services/collections/CollectionService.java +++ b/src/services/collections/CollectionService.java @@ -31,15 +31,6 @@ import intents.GrantClickyCollectionIntent; import intents.radial.RadialRequestIntent; import intents.radial.RadialResponseIntent; import intents.radial.RadialSelectionIntent; -import resources.control.Intent; -import resources.control.Service; -import resources.objects.collections.ClickyCollectionItem; -import resources.objects.collections.CollectionItem; -import resources.radial.RadialOption; -import resources.radial.Radials; -import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -47,6 +38,16 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import resources.objects.collections.ClickyCollectionItem; +import resources.objects.collections.CollectionItem; +import resources.radial.RadialOption; +import resources.radial.Radials; +import resources.server_info.Log; + +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + /** * Created by skylerlehan on 8/21/16. */ diff --git a/src/services/collections/ExplorationBadgeService.java b/src/services/collections/ExplorationBadgeService.java index 4769f3ca9..7d337b4e0 100644 --- a/src/services/collections/ExplorationBadgeService.java +++ b/src/services/collections/ExplorationBadgeService.java @@ -28,6 +28,9 @@ ***********************************************************************************/ package services.collections; +import intents.GrantBadgeIntent; +import intents.player.PlayerTransformedIntent; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.Locale; @@ -35,14 +38,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import intents.GrantBadgeIntent; -import intents.player.PlayerTransformedIntent; import resources.Point3D; -import resources.control.Service; import resources.objects.creature.CreatureObject; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; public class ExplorationBadgeService extends Service { diff --git a/src/services/combat/CombatManager.java b/src/services/combat/CombatManager.java index b65ff5951..83d5b5906 100644 --- a/src/services/combat/CombatManager.java +++ b/src/services/combat/CombatManager.java @@ -27,6 +27,14 @@ ***********************************************************************************/ package services.combat; +import intents.BuffIntent; +import intents.chat.ChatBroadcastIntent; +import intents.chat.ChatCommandIntent; +import intents.combat.CreatureKilledIntent; +import intents.combat.DeathblowIntent; +import intents.object.DestroyObjectIntent; +import intents.object.ObjectCreatedIntent; + import java.awt.Color; import java.util.Collection; import java.util.HashMap; @@ -41,13 +49,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import intents.BuffIntent; -import intents.chat.ChatBroadcastIntent; -import intents.chat.ChatCommandIntent; -import intents.combat.CreatureKilledIntent; -import intents.combat.DeathblowIntent; -import intents.object.DestroyObjectIntent; -import intents.object.ObjectCreatedIntent; import network.packets.swg.zone.PlayClientEffectObjectMessage; import network.packets.swg.zone.object_controller.ShowFlyText; import network.packets.swg.zone.object_controller.ShowFlyText.Scale; @@ -63,7 +64,6 @@ import resources.combat.TrailLocation; import resources.commands.CombatCommand; import resources.common.CRC; import resources.common.RGB; -import resources.control.Manager; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; @@ -75,6 +75,8 @@ import resources.server_info.Log; import services.objects.ObjectCreator; import utilities.ThreadUtilities; +import com.projectswg.common.control.Manager; + public class CombatManager extends Manager { private static final byte INCAP_TIMER = 10; // Amount of seconds to be incapacitated diff --git a/src/services/combat/CombatXpService.java b/src/services/combat/CombatXpService.java index d866345e5..f14783b4a 100644 --- a/src/services/combat/CombatXpService.java +++ b/src/services/combat/CombatXpService.java @@ -37,20 +37,17 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureDifficulty; import resources.objects.creature.CreatureObject; import resources.objects.group.GroupObject; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; -/** - * - * @author mads - */ +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public class CombatXpService extends Service { private final Map xpData; diff --git a/src/services/combat/CorpseService.java b/src/services/combat/CorpseService.java index 3fd510c8a..4f95ecf8c 100644 --- a/src/services/combat/CorpseService.java +++ b/src/services/combat/CorpseService.java @@ -31,12 +31,6 @@ import intents.BuffIntent; import intents.FactionIntent; import intents.PlayerEventIntent; import intents.chat.ChatBroadcastIntent; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import network.packets.swg.zone.PlayClientEffectObjectMessage; import intents.combat.CreatureKilledIntent; import intents.object.DestroyObjectIntent; import intents.object.ObjectCreatedIntent; @@ -49,16 +43,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import network.packets.swg.zone.PlayClientEffectObjectMessage; import resources.Location; import resources.Posture; import resources.PvpFaction; import resources.PvpStatus; import resources.Terrain; import resources.client_info.ClientFactory; -import resources.control.Service; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; @@ -67,8 +64,6 @@ import resources.objects.cell.CellObject; import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; import resources.sui.SuiButtons; import resources.sui.SuiEvent; @@ -76,6 +71,10 @@ import resources.sui.SuiListBox; import resources.sui.SuiWindow; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + /** * The {@code CorpseService} removes corpses from the world a while after * they've died. It also lets players clone at a cloning facility. diff --git a/src/services/commands/BuffService.java b/src/services/commands/BuffService.java index cc0f2409d..4045ebbba 100644 --- a/src/services/commands/BuffService.java +++ b/src/services/commands/BuffService.java @@ -27,6 +27,11 @@ ***********************************************************************************/ package services.commands; +import intents.BuffIntent; +import intents.PlayerEventIntent; +import intents.SkillModIntent; +import intents.combat.CreatureKilledIntent; + import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashSet; @@ -35,15 +40,8 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import intents.BuffIntent; -import intents.PlayerEventIntent; -import intents.SkillModIntent; -import intents.combat.CreatureKilledIntent; import network.packets.swg.zone.PlayClientEffectObjectMessage; import resources.common.CRC; -import resources.concurrency.PswgBasicScheduledThread; -import resources.control.Assert; -import resources.control.Service; import resources.objects.creature.Buff; import resources.objects.creature.CreatureObject; import resources.server_info.Log; @@ -52,6 +50,10 @@ import services.commands.buff.BuffData; import services.commands.buff.BuffMap; import utilities.Scripts; +import com.projectswg.common.concurrency.PswgBasicScheduledThread; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; + public class BuffService extends Service { /* diff --git a/src/services/commands/CommandService.java b/src/services/commands/CommandService.java index 3ef5b343c..82b9c8c78 100644 --- a/src/services/commands/CommandService.java +++ b/src/services/commands/CommandService.java @@ -32,7 +32,9 @@ import intents.chat.ChatBroadcastIntent; import intents.chat.ChatCommandIntent; import intents.network.GalacticPacketIntent; import intents.player.PlayerTransformedIntent; + import java.io.FileNotFoundException; + import network.packets.Packet; import network.packets.swg.zone.object_controller.CommandQueueDequeue; import network.packets.swg.zone.object_controller.CommandQueueEnqueue; @@ -46,8 +48,6 @@ import resources.commands.Command; import resources.commands.ICmdCallback; import resources.commands.callbacks.*; import resources.common.CRC; -import resources.control.Intent; -import resources.control.Service; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; @@ -71,15 +71,17 @@ import java.util.concurrent.Executors; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; + +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.control.Service; + import network.packets.swg.zone.object_controller.CommandTimer; import resources.combat.DelayAttackEggPosition; import resources.combat.HitType; import resources.commands.DefaultPriority; import resources.objects.creature.CreatureObject; -import resources.server_info.SynchronizedMap; import utilities.ThreadUtilities; - public class CommandService extends Service { private final Map commands; // NOTE: CRC's are all lowercased for commands! diff --git a/src/services/commands/EntertainmentService.java b/src/services/commands/EntertainmentService.java index 8cf815636..05d0cb2c3 100644 --- a/src/services/commands/EntertainmentService.java +++ b/src/services/commands/EntertainmentService.java @@ -27,6 +27,14 @@ ***********************************************************************************/ package services.commands; +import intents.DanceIntent; +import intents.FlourishIntent; +import intents.PlayerEventIntent; +import intents.WatchIntent; +import intents.chat.ChatBroadcastIntent; +import intents.experience.ExperienceIntent; +import intents.player.PlayerTransformedIntent; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -36,19 +44,11 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.DanceIntent; -import intents.FlourishIntent; -import intents.PlayerEventIntent; -import intents.WatchIntent; -import intents.chat.ChatBroadcastIntent; -import intents.experience.ExperienceIntent; -import intents.player.PlayerTransformedIntent; import network.packets.swg.zone.object_controller.Animation; import resources.Location; import resources.Posture; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; -import resources.control.Service; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; @@ -56,6 +56,8 @@ import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.server_info.Log; +import com.projectswg.common.control.Service; + /** * * @author Mads diff --git a/src/services/commands/buff/BuffData.java b/src/services/commands/buff/BuffData.java index 65948def9..5899a4e92 100644 --- a/src/services/commands/buff/BuffData.java +++ b/src/services/commands/buff/BuffData.java @@ -30,7 +30,8 @@ package services.commands.buff; import java.util.Locale; import resources.common.CRC; -import resources.control.Assert; + +import com.projectswg.common.debug.Assert; public class BuffData { diff --git a/src/services/commands/buff/BuffMap.java b/src/services/commands/buff/BuffMap.java index ebf856dd6..f9f3f7cc8 100644 --- a/src/services/commands/buff/BuffMap.java +++ b/src/services/commands/buff/BuffMap.java @@ -33,7 +33,8 @@ import java.util.Map; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; import resources.common.CRC; -import resources.server_info.SynchronizedMap; + +import com.projectswg.common.concurrency.SynchronizedMap; public class BuffMap { diff --git a/src/services/dev/DeveloperService.java b/src/services/dev/DeveloperService.java index 9192fc7a9..7749b0e84 100644 --- a/src/services/dev/DeveloperService.java +++ b/src/services/dev/DeveloperService.java @@ -32,12 +32,14 @@ import resources.Location; import resources.PvpFlag; import resources.Terrain; import resources.config.ConfigFile; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.custom.DefaultAIObject; import resources.objects.tangible.TangibleObject; +import resources.server_info.DataManager; import services.objects.ObjectCreator; +import com.projectswg.common.control.Service; + public class DeveloperService extends Service { public DeveloperService() { @@ -48,7 +50,7 @@ public class DeveloperService extends Service { public boolean start() { setupDeveloperArea(); - if (getConfig(ConfigFile.FEATURES).getBoolean("CHARACTER-BUILDER", false)) + if (DataManager.getConfig(ConfigFile.FEATURES).getBoolean("CHARACTER-BUILDER", false)) setupCharacterBuilders(); return super.start(); diff --git a/src/services/experience/ExperienceManager.java b/src/services/experience/ExperienceManager.java index bded45a5c..b041389cc 100644 --- a/src/services/experience/ExperienceManager.java +++ b/src/services/experience/ExperienceManager.java @@ -27,26 +27,29 @@ ***********************************************************************************/ package services.experience; +import intents.experience.ExperienceIntent; +import intents.experience.LevelChangedIntent; + import java.awt.Color; import java.util.HashMap; import java.util.Map; -import intents.experience.ExperienceIntent; -import intents.experience.LevelChangedIntent; import network.packets.swg.zone.object_controller.ShowFlyText; import network.packets.swg.zone.object_controller.ShowFlyText.Scale; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; import resources.common.RGB; import resources.config.ConfigFile; -import resources.control.Manager; import resources.encodables.OutOfBandPackage; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.creature.CreatureObject; import resources.objects.player.PlayerObject; +import resources.server_info.DataManager; import resources.server_info.Log; +import com.projectswg.common.control.Manager; + /** * The {@code ExperienceManager} listens for {@link ExperienceIntent} and * grants XP based on it. @@ -63,7 +66,7 @@ public final class ExperienceManager extends Manager { skillManager = new SkillManager(); skillTemplateService = new SkillTemplateService(); levelXpMap = new HashMap<>(); - xpMultiplier = getConfig(ConfigFile.FEATURES).getDouble("XP-MULTIPLIER", 1); + xpMultiplier = DataManager.getConfig(ConfigFile.FEATURES).getDouble("XP-MULTIPLIER", 1); registerForIntent(ExperienceIntent.class, ei -> handleExperienceIntent(ei)); diff --git a/src/services/experience/ExpertiseService.java b/src/services/experience/ExpertiseService.java index 51acefe25..dc84a3ae7 100644 --- a/src/services/experience/ExpertiseService.java +++ b/src/services/experience/ExpertiseService.java @@ -42,17 +42,18 @@ import network.packets.Packet; import network.packets.swg.zone.ExpertiseRequestMessage; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; -import resources.control.Intent; -import resources.control.Service; import resources.objects.creature.CreatureObject; import resources.objects.player.PlayerObject; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; import resources.sui.SuiButtons; import resources.sui.SuiMessageBox; +import com.projectswg.common.control.Intent; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + /** * * @author Mads diff --git a/src/services/experience/SkillManager.java b/src/services/experience/SkillManager.java index 786b84a7a..f4de904f3 100644 --- a/src/services/experience/SkillManager.java +++ b/src/services/experience/SkillManager.java @@ -27,23 +27,25 @@ ***********************************************************************************/ package services.experience; +import intents.SkillModIntent; +import intents.experience.GrantSkillIntent; +import intents.network.GalacticPacketIntent; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import intents.SkillModIntent; -import intents.experience.GrantSkillIntent; -import intents.network.GalacticPacketIntent; import network.packets.Packet; import network.packets.swg.zone.object_controller.ChangeRoleIconChoice; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; -import resources.control.Assert; -import resources.control.Manager; import resources.objects.creature.CreatureObject; import resources.server_info.Log; +import com.projectswg.common.control.Manager; +import com.projectswg.common.debug.Assert; + /** * * @author Mads diff --git a/src/services/experience/SkillTemplateService.java b/src/services/experience/SkillTemplateService.java index 10ffa7ac4..da12ab56e 100644 --- a/src/services/experience/SkillTemplateService.java +++ b/src/services/experience/SkillTemplateService.java @@ -27,17 +27,18 @@ ***********************************************************************************/ package services.experience; +import intents.GrantBadgeIntent; +import intents.experience.GrantSkillIntent; +import intents.experience.LevelChangedIntent; +import intents.object.CreateStaticItemIntent; +import intents.object.ObjectCreatedIntent; + import java.awt.Color; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import intents.GrantBadgeIntent; -import intents.experience.GrantSkillIntent; -import intents.experience.LevelChangedIntent; -import intents.object.CreateStaticItemIntent; -import intents.object.ObjectCreatedIntent; import network.packets.swg.zone.PlayClientEffectObjectMessage; import network.packets.swg.zone.PlayMusicMessage; import network.packets.swg.zone.object_controller.ShowFlyText; @@ -46,7 +47,6 @@ import resources.Race; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; import resources.common.RGB; -import resources.control.Service; import resources.encodables.StringId; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; @@ -57,6 +57,8 @@ import resources.server_info.Log; import services.objects.ObjectCreator; import services.objects.StaticItemService; +import com.projectswg.common.control.Service; + /** * This is a service that listens for {@link LevelChangedIntent} and grants * everything linked to a skillbox. diff --git a/src/services/faction/FactionService.java b/src/services/faction/FactionService.java index 6a801fe25..f4e71ace6 100644 --- a/src/services/faction/FactionService.java +++ b/src/services/faction/FactionService.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package services.faction; +import intents.FactionIntent; +import intents.chat.ChatBroadcastIntent; + import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -35,20 +38,19 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.FactionIntent; -import intents.chat.ChatBroadcastIntent; import network.packets.swg.zone.UpdatePvpStatusMessage; import network.packets.swg.zone.chat.ChatSystemMessage; import network.packets.swg.zone.chat.ChatSystemMessage.SystemChatType; import resources.PvpFaction; import resources.PvpFlag; import resources.PvpStatus; -import resources.control.Service; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import resources.player.Player; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; + public final class FactionService extends Service { private final ScheduledExecutorService executor; diff --git a/src/services/galaxy/ConnectionService.java b/src/services/galaxy/ConnectionService.java index 0d276af4c..edc54bac7 100644 --- a/src/services/galaxy/ConnectionService.java +++ b/src/services/galaxy/ConnectionService.java @@ -27,18 +27,16 @@ ***********************************************************************************/ package services.galaxy; +import intents.PlayerEventIntent; +import intents.network.GalacticPacketIntent; + import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.PlayerEventIntent; -import intents.network.GalacticPacketIntent; import network.packets.swg.zone.HeartBeat; -import resources.control.Assert; -import resources.control.Intent; -import resources.control.Service; import resources.network.DisconnectReason; import resources.objects.creature.CreatureObject; import resources.objects.player.PlayerObject; @@ -46,10 +44,14 @@ import resources.player.Player; import resources.player.PlayerEvent; import resources.player.PlayerFlags; import resources.server_info.Log; -import resources.server_info.SynchronizedSet; import services.CoreManager; import utilities.ThreadUtilities; +import com.projectswg.common.concurrency.SynchronizedSet; +import com.projectswg.common.control.Intent; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; + public class ConnectionService extends Service { private static final double DISAPPEAR_THRESHOLD = TimeUnit.MINUTES.toMillis(3); // Time after the LD diff --git a/src/services/galaxy/EnvironmentService.java b/src/services/galaxy/EnvironmentService.java index 88bf64fb9..912c4f99c 100644 --- a/src/services/galaxy/EnvironmentService.java +++ b/src/services/galaxy/EnvironmentService.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package services.galaxy; +import intents.NotifyPlayersPacketIntent; +import intents.PlayerEventIntent; + import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -34,19 +37,18 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.NotifyPlayersPacketIntent; -import intents.PlayerEventIntent; import main.ProjectSWG; import network.packets.swg.SWGPacket; import network.packets.swg.zone.ServerTimeMessage; import network.packets.swg.zone.ServerWeatherMessage; import resources.Terrain; import resources.WeatherType; -import resources.control.Service; import resources.player.Player; import resources.player.PlayerEvent; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; + public final class EnvironmentService extends Service { private final long cycleDuration; diff --git a/src/services/galaxy/GalacticManager.java b/src/services/galaxy/GalacticManager.java index b97ed7795..b22b5784a 100644 --- a/src/services/galaxy/GalacticManager.java +++ b/src/services/galaxy/GalacticManager.java @@ -27,16 +27,14 @@ ***********************************************************************************/ package services.galaxy; -import java.util.Map; - import intents.network.ConnectionClosedIntent; import intents.network.ConnectionOpenedIntent; import intents.network.GalacticPacketIntent; import intents.network.InboundPacketIntent; -import resources.control.Assert; -import resources.control.Manager; + +import java.util.Map; + import resources.player.Player; -import resources.server_info.SynchronizedMap; import services.CoreManager; import services.chat.ChatManager; import services.dev.DeveloperService; @@ -44,7 +42,11 @@ import services.galaxy.travel.TravelService; import services.objects.ObjectManager; import services.objects.UniformBoxService; import services.player.PlayerManager; -import utilities.IntentChain; + +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.control.IntentChain; +import com.projectswg.common.control.Manager; +import com.projectswg.common.debug.Assert; public class GalacticManager extends Manager { @@ -95,9 +97,7 @@ public class GalacticManager extends Manager { } private void handleConnectionOpenedIntent(ConnectionOpenedIntent coi){ - IntentChain chain = new IntentChain(); - chain.waitUntilComplete(coi); - prevIntentMap.put(((ConnectionOpenedIntent) coi).getNetworkId(), chain); + prevIntentMap.put(((ConnectionOpenedIntent) coi).getNetworkId(), new IntentChain(coi)); } private void handleConnectionClosedIntent(ConnectionClosedIntent cci){ diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 3a8f3800d..872c006d8 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -27,18 +27,19 @@ ***********************************************************************************/ package services.galaxy; -import resources.control.Manager; -import services.commands.BuffService; -import services.collections.CollectionService; import services.collections.CollectionBadgeManager; +import services.collections.CollectionService; import services.combat.CombatManager; +import services.commands.BuffService; import services.commands.CommandService; import services.commands.EntertainmentService; import services.experience.ExperienceManager; import services.faction.FactionService; import services.galaxy.terminals.TerminalService; -import services.sui.SuiService; import services.group.GroupService; +import services.sui.SuiService; + +import com.projectswg.common.control.Manager; public class GameManager extends Manager { @@ -47,7 +48,7 @@ public class GameManager extends Manager { private final SuiService suiService; private final CollectionService collectionService; private final CollectionBadgeManager collectionBadgeManager; - private final EnvironmentService weatherService; + private final EnvironmentService environmentService; private final TerminalService terminalManager; private final FactionService factionService; private final GroupService groupService; @@ -63,7 +64,7 @@ public class GameManager extends Manager { suiService = new SuiService(); collectionService = new CollectionService(); collectionBadgeManager = new CollectionBadgeManager(); - weatherService = new EnvironmentService(); + environmentService = new EnvironmentService(); terminalManager = new TerminalService(); factionService = new FactionService(); groupService = new GroupService(); @@ -78,7 +79,7 @@ public class GameManager extends Manager { addChildService(suiService); addChildService(collectionService); addChildService(collectionBadgeManager); - addChildService(weatherService); + addChildService(environmentService); addChildService(terminalManager); addChildService(factionService); addChildService(groupService); diff --git a/src/services/galaxy/SkillModService.java b/src/services/galaxy/SkillModService.java index 3d6d4029d..023c90aaa 100644 --- a/src/services/galaxy/SkillModService.java +++ b/src/services/galaxy/SkillModService.java @@ -28,9 +28,10 @@ package services.galaxy; import intents.SkillModIntent; -import resources.control.Service; import resources.objects.creature.CreatureObject; +import com.projectswg.common.control.Service; + public class SkillModService extends Service { public SkillModService() { diff --git a/src/services/galaxy/terminals/TerminalService.java b/src/services/galaxy/terminals/TerminalService.java index 6cfd064ac..5ee202de8 100644 --- a/src/services/galaxy/terminals/TerminalService.java +++ b/src/services/galaxy/terminals/TerminalService.java @@ -27,6 +27,11 @@ ***********************************************************************************/ package services.galaxy.terminals; +import intents.radial.RadialRegisterIntent; +import intents.radial.RadialRequestIntent; +import intents.radial.RadialResponseIntent; +import intents.radial.RadialSelectionIntent; + import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -35,16 +40,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import intents.radial.RadialRegisterIntent; -import intents.radial.RadialRequestIntent; -import intents.radial.RadialResponseIntent; -import intents.radial.RadialSelectionIntent; -import resources.control.Service; import resources.radial.RadialOption; import resources.radial.Radials; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; public class TerminalService extends Service { diff --git a/src/services/galaxy/travel/TravelService.java b/src/services/galaxy/travel/TravelService.java index 75cb585ff..c63dfa98d 100644 --- a/src/services/galaxy/travel/TravelService.java +++ b/src/services/galaxy/travel/TravelService.java @@ -27,6 +27,15 @@ ***********************************************************************************/ package services.galaxy.travel; +import intents.chat.ChatBroadcastIntent; +import intents.network.GalacticPacketIntent; +import intents.object.DestroyObjectIntent; +import intents.object.ObjectCreatedIntent; +import intents.object.ObjectTeleportIntent; +import intents.travel.TicketPurchaseIntent; +import intents.travel.TicketUseIntent; +import intents.travel.TravelPointSelectionIntent; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -43,12 +52,6 @@ import network.packets.Packet; import network.packets.swg.zone.EnterTicketPurchaseModeMessage; import network.packets.swg.zone.PlanetTravelPointListRequest; import network.packets.swg.zone.PlanetTravelPointListResponse; -import intents.chat.ChatBroadcastIntent; -import intents.network.GalacticPacketIntent; -import intents.object.DestroyObjectIntent; -import intents.object.ObjectCreatedIntent; -import intents.object.ObjectTeleportIntent; -import intents.travel.*; import resources.Location; import resources.Posture; import resources.Terrain; @@ -56,7 +59,6 @@ import resources.TravelPoint; import resources.client_info.ClientFactory; import resources.client_info.visitors.DatatableData; import resources.config.ConfigFile; -import resources.control.Service; import resources.encodables.ProsePackage; import resources.encodables.StringId; import resources.objects.SWGObject; @@ -64,17 +66,20 @@ import resources.objects.creature.CreatureObject; import resources.objects.staticobject.StaticObject; import resources.objects.tangible.OptionFlag; import resources.player.Player; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import resources.sui.SuiButtons; import resources.sui.SuiListBox; import resources.sui.SuiMessageBox; -import services.galaxy.travel.TravelGroup; import services.galaxy.travel.TravelGroup.ShuttleStatus; import services.objects.ObjectCreator; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.Config; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + public class TravelService extends Service { private static final String DB_TABLE_NAME = "travel"; @@ -98,9 +103,10 @@ public class TravelService extends Service { travelFeeTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/travel/travel.iff"); travel = new HashMap<>(); - ticketPriceFactor = getConfig(ConfigFile.FEATURES).getDouble("TICKET-PRICE-FACTOR", 1); - groundTime = new AtomicInteger(getConfig(ConfigFile.FEATURES).getInt("SHUTTLE-GROUND-TIME", 120)); - airTime = new AtomicInteger(getConfig(ConfigFile.FEATURES).getInt("SHUTTLE-AIR-TIME", 60)); + Config config = DataManager.getConfig(ConfigFile.FEATURES); + ticketPriceFactor = config.getDouble("TICKET-PRICE-FACTOR", 1); + groundTime = new AtomicInteger(config.getInt("SHUTTLE-GROUND-TIME", 120)); + airTime = new AtomicInteger(config.getInt("SHUTTLE-AIR-TIME", 60)); createGalaxyTravel("object/creature/npc/theme_park/shared_player_shuttle.iff", 17000); createGalaxyTravel("object/creature/npc/theme_park/shared_player_transport.iff", 21000); diff --git a/src/services/group/GroupService.java b/src/services/group/GroupService.java index 9bee32124..3cc477e6a 100644 --- a/src/services/group/GroupService.java +++ b/src/services/group/GroupService.java @@ -26,9 +26,6 @@ ******************************************************************************/ package services.group; -import java.util.Map; -import java.util.Set; - import intents.GroupEventIntent; import intents.PlayerEventIntent; import intents.chat.ChatBroadcastIntent; @@ -36,18 +33,23 @@ import intents.chat.ChatRoomUpdateIntent; import intents.chat.ChatRoomUpdateIntent.UpdateType; import intents.object.DestroyObjectIntent; import intents.object.ObjectCreatedIntent; + +import java.util.Map; +import java.util.Set; + import resources.chat.ChatAvatar; -import resources.control.Assert; -import resources.control.Service; import resources.encodables.ProsePackage; import resources.objects.creature.CreatureObject; import resources.objects.group.GroupObject; import resources.player.Player; import resources.player.Player.PlayerServer; -import resources.server_info.SynchronizedMap; import services.objects.ObjectCreator; import utilities.IntentFactory; +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; + public class GroupService extends Service { private final Map groups; diff --git a/src/services/map/CityService.java b/src/services/map/CityService.java index 6869656ee..31c262102 100644 --- a/src/services/map/CityService.java +++ b/src/services/map/CityService.java @@ -27,6 +27,10 @@ ***********************************************************************************/ package services.map; +import intents.PlayerEventIntent; +import intents.network.GalacticPacketIntent; +import intents.object.ObjectCreatedIntent; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -34,21 +38,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import intents.PlayerEventIntent; -import intents.network.GalacticPacketIntent; -import intents.object.ObjectCreatedIntent; import network.packets.Packet; import network.packets.swg.zone.object_controller.DataTransform; import resources.Terrain; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import resources.player.Player; import resources.player.PlayerEvent; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; public class CityService extends Service { diff --git a/src/services/map/MapManager.java b/src/services/map/MapManager.java index 70d426767..96fd26f8d 100644 --- a/src/services/map/MapManager.java +++ b/src/services/map/MapManager.java @@ -29,16 +29,6 @@ package services.map; import intents.network.GalacticPacketIntent; import intents.object.ObjectCreatedIntent; -import network.packets.Packet; -import network.packets.swg.SWGPacket; -import network.packets.swg.zone.spatial.GetMapLocationsMessage; -import network.packets.swg.zone.spatial.GetMapLocationsResponseMessage; -import resources.client_info.ClientFactory; -import resources.client_info.ServerFactory; -import resources.client_info.visitors.DatatableData; -import resources.control.Manager; -import resources.objects.SWGObject; -import resources.player.Player; import java.util.ArrayList; import java.util.HashMap; @@ -47,6 +37,19 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import network.packets.Packet; +import network.packets.swg.SWGPacket; +import network.packets.swg.zone.spatial.GetMapLocationsMessage; +import network.packets.swg.zone.spatial.GetMapLocationsResponseMessage; +import resources.client_info.ClientFactory; +import resources.client_info.ServerFactory; +import resources.client_info.visitors.DatatableData; +import resources.objects.SWGObject; +import resources.player.Player; + +import com.projectswg.common.control.Manager; +import com.sun.prism.PhongMaterial.MapType; + public class MapManager extends Manager { private final Map mapCategories; diff --git a/src/services/network/InboundNetworkManager.java b/src/services/network/InboundNetworkManager.java index 010507ef0..8ea7a5974 100644 --- a/src/services/network/InboundNetworkManager.java +++ b/src/services/network/InboundNetworkManager.java @@ -33,9 +33,10 @@ import java.net.SocketAddress; import java.util.Arrays; import network.NetworkClient; -import resources.control.Service; import resources.server_info.Log; +import com.projectswg.common.control.Service; + public class InboundNetworkManager extends Service { private final ClientManager clientManager; diff --git a/src/services/network/NetworkClientManager.java b/src/services/network/NetworkClientManager.java index a71655a02..c90d4c731 100644 --- a/src/services/network/NetworkClientManager.java +++ b/src/services/network/NetworkClientManager.java @@ -27,6 +27,8 @@ ***********************************************************************************/ package services.network; +import intents.network.CloseConnectionIntent; + import java.io.IOException; import java.net.BindException; import java.net.InetAddress; @@ -34,21 +36,22 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; -import intents.network.CloseConnectionIntent; import main.ProjectSWG.CoreException; import network.AdminNetworkClient; import network.NetworkClient; import network.packets.swg.holo.HoloConnectionStopped.ConnectionStoppedReason; import resources.config.ConfigFile; -import resources.control.Manager; import resources.network.NetBuffer; import resources.network.NetworkCallback; import resources.network.TCPServer; import resources.network.UDPServer; import resources.network.UDPServer.UDPPacket; +import resources.server_info.DataManager; import resources.server_info.Log; import services.CoreManager; +import com.projectswg.common.control.Manager; + public class NetworkClientManager extends Manager { private final InboundNetworkManager inboundManager; @@ -125,11 +128,11 @@ public class NetworkClientManager extends Manager { } private int getBindPort() { - return getConfig(ConfigFile.NETWORK).getInt("BIND-PORT", 44463); + return DataManager.getConfig(ConfigFile.NETWORK).getInt("BIND-PORT", 44463); } private int getBufferSize() { - return getConfig(ConfigFile.NETWORK).getInt("BUFFER-SIZE", 4096); + return DataManager.getConfig(ConfigFile.NETWORK).getInt("BUFFER-SIZE", 4096); } private void onUdpPacket(UDPPacket packet) { diff --git a/src/services/network/NetworkManager.java b/src/services/network/NetworkManager.java index 0a5100b83..615e0fa25 100644 --- a/src/services/network/NetworkManager.java +++ b/src/services/network/NetworkManager.java @@ -27,7 +27,7 @@ ***********************************************************************************/ package services.network; -import resources.control.Manager; +import com.projectswg.common.control.Manager; public class NetworkManager extends Manager { diff --git a/src/services/network/NetworkProtocol.java b/src/services/network/NetworkProtocol.java index af1d6d70b..2da42d1a9 100644 --- a/src/services/network/NetworkProtocol.java +++ b/src/services/network/NetworkProtocol.java @@ -31,13 +31,14 @@ import java.io.EOFException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import resources.control.Assert; -import resources.network.NetBufferStream; import network.PacketType; import network.encryption.Compression; import network.packets.Packet; import network.packets.swg.SWGPacket; import network.packets.swg.zone.object_controller.ObjectController; +import resources.network.NetBufferStream; + +import com.projectswg.common.debug.Assert; public class NetworkProtocol { diff --git a/src/services/network/OutboundNetworkManager.java b/src/services/network/OutboundNetworkManager.java index 28947893c..93667cf33 100644 --- a/src/services/network/OutboundNetworkManager.java +++ b/src/services/network/OutboundNetworkManager.java @@ -29,10 +29,11 @@ package services.network; import intents.network.OutboundPacketIntent; import network.NetworkClient; -import resources.control.Assert; -import resources.control.Service; import resources.network.TCPServer; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; + public class OutboundNetworkManager extends Service { private final ClientManager clientManager; diff --git a/src/services/objects/ClientBuildoutService.java b/src/services/objects/ClientBuildoutService.java index ff01d514e..afb1c8550 100644 --- a/src/services/objects/ClientBuildoutService.java +++ b/src/services/objects/ClientBuildoutService.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package services.objects; +import intents.object.ObjectCreatedIntent; +import intents.player.PlayerTransformedIntent; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -42,25 +45,25 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import intents.object.ObjectCreatedIntent; -import intents.player.PlayerTransformedIntent; import resources.Location; import resources.Terrain; import resources.buildout.BuildoutArea; import resources.buildout.BuildoutArea.BuildoutAreaBuilder; import resources.buildout.BuildoutAreaGrid; import resources.config.ConfigFile; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.SWGObject.ObjectClassification; import resources.objects.building.BuildingObject; import resources.objects.cell.CellObject; import resources.server_info.CrcDatabase; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + public class ClientBuildoutService extends Service { private static final String GET_BUILDOUT_AREAS = "SELECT * FROM areas ORDER BY area_name ASC, event ASC"; @@ -85,7 +88,7 @@ public class ClientBuildoutService extends Service { long startTime = StandardLog.onStartLoad("client objects"); try { loadAreas(getEvents()); - if (getConfig(ConfigFile.PRIMARY).getBoolean("LOAD-OBJECTS", true)) + if (DataManager.getConfig(ConfigFile.PRIMARY).getBoolean("LOAD-OBJECTS", true)) objects = loadObjects(); else objects = new HashMap<>(); @@ -209,7 +212,7 @@ public class ClientBuildoutService extends Service { private List getEvents() { List events = new ArrayList<>(); - String eventStr = getConfig(ConfigFile.FEATURES).getString("EVENTS", ""); + String eventStr = DataManager.getConfig(ConfigFile.FEATURES).getString("EVENTS", ""); String [] eventArray = eventStr.split(","); for (String event : eventArray) { event = event.toLowerCase(Locale.US); diff --git a/src/services/objects/ObjectAwareness.java b/src/services/objects/ObjectAwareness.java index a9f09bfd1..b9804a577 100644 --- a/src/services/objects/ObjectAwareness.java +++ b/src/services/objects/ObjectAwareness.java @@ -52,9 +52,6 @@ import resources.Location; import resources.Point3D; import resources.Terrain; import resources.config.ConfigFile; -import resources.control.Assert; -import resources.control.Intent; -import resources.control.Service; import resources.network.DisconnectReason; import resources.objects.SWGObject; import resources.objects.awareness.AwarenessHandler; @@ -64,8 +61,13 @@ import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.player.PlayerEvent; import resources.player.PlayerState; +import resources.server_info.DataManager; import resources.server_info.Log; +import com.projectswg.common.control.Intent; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; + public class ObjectAwareness extends Service implements TerrainMapCallback { private static final Location GONE_LOCATION = new Location(0, 0, 0, Terrain.GONE); @@ -76,7 +78,7 @@ public class ObjectAwareness extends Service implements TerrainMapCallback { public ObjectAwareness() { awarenessHandler = new AwarenessHandler(this); dataTransformHandler = new DataTransformHandler(); - dataTransformHandler.setSpeedCheck(getConfig(ConfigFile.FEATURES).getBoolean("SPEED-HACK-CHECK", true)); + dataTransformHandler.setSpeedCheck(DataManager.getConfig(ConfigFile.FEATURES).getBoolean("SPEED-HACK-CHECK", true)); registerForIntent(PlayerEventIntent.class, pei -> handlePlayerEventIntent(pei)); registerForIntent(ObjectCreatedIntent.class, oci -> handleObjectCreatedIntent(oci)); diff --git a/src/services/objects/ObjectManager.java b/src/services/objects/ObjectManager.java index c6cd62041..2afd812c5 100644 --- a/src/services/objects/ObjectManager.java +++ b/src/services/objects/ObjectManager.java @@ -27,6 +27,11 @@ ***********************************************************************************/ package services.objects; +import intents.RequestZoneInIntent; +import intents.network.GalacticPacketIntent; +import intents.object.DestroyObjectIntent; +import intents.object.ObjectCreatedIntent; + import java.util.Hashtable; import java.util.Map; import java.util.concurrent.Executors; @@ -34,15 +39,10 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import intents.object.DestroyObjectIntent; -import intents.object.ObjectCreatedIntent; -import intents.RequestZoneInIntent; -import intents.network.GalacticPacketIntent; import network.packets.Packet; import network.packets.swg.ErrorMessage; import network.packets.swg.zone.insertion.SelectCharacter; import network.packets.swg.zone.object_controller.IntendedTarget; -import resources.control.Manager; import resources.objects.SWGObject; import resources.objects.building.BuildingObject; import resources.objects.cell.CellObject; @@ -59,6 +59,8 @@ import services.player.PlayerManager; import services.spawn.SpawnerService; import services.spawn.StaticService; +import com.projectswg.common.control.Manager; + public class ObjectManager extends Manager { private final ObjectAwareness objectAwareness; diff --git a/src/services/objects/RadialService.java b/src/services/objects/RadialService.java index 46b99ab40..5a5bfac86 100644 --- a/src/services/objects/RadialService.java +++ b/src/services/objects/RadialService.java @@ -27,20 +27,20 @@ ***********************************************************************************/ package services.objects; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import intents.network.GalacticPacketIntent; import intents.radial.RadialRegisterIntent; import intents.radial.RadialRequestIntent; import intents.radial.RadialResponseIntent; import intents.radial.RadialSelectionIntent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import network.packets.Packet; import network.packets.swg.zone.ObjectMenuSelect; import network.packets.swg.zone.object_controller.ObjectMenuRequest; import network.packets.swg.zone.object_controller.ObjectMenuResponse; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.player.Player; @@ -49,6 +49,8 @@ import resources.radial.RadialOption; import resources.server_info.Log; import services.galaxy.GalacticManager; +import com.projectswg.common.control.Service; + public class RadialService extends Service { private final Set templatesRegistered; diff --git a/src/services/objects/StaticItemService.java b/src/services/objects/StaticItemService.java index a88fe40bc..6f0b700b7 100644 --- a/src/services/objects/StaticItemService.java +++ b/src/services/objects/StaticItemService.java @@ -27,30 +27,33 @@ ***********************************************************************************/ package services.objects; +import intents.chat.ChatBroadcastIntent; +import intents.object.CreateStaticItemIntent; +import intents.object.ObjectCreatedIntent; +import intents.server.ConfigChangedIntent; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -import intents.chat.ChatBroadcastIntent; -import intents.object.CreateStaticItemIntent; -import intents.object.ObjectCreatedIntent; -import intents.server.ConfigChangedIntent; import network.packets.swg.zone.object_controller.ShowLootBox; import resources.client_info.ClientFactory; import resources.combat.DamageType; import resources.config.ConfigFile; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.objects.weapon.WeaponObject; import resources.objects.weapon.WeaponType; import resources.player.Player; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + /** * @author mads */ @@ -73,7 +76,7 @@ public final class StaticItemService extends Service { @Override public boolean initialize() { - boolean configEnable = getConfig(ConfigFile.FEATURES).getBoolean(CONFIG_OPTION_NAME, true); + boolean configEnable = DataManager.getConfig(ConfigFile.FEATURES).getBoolean(CONFIG_OPTION_NAME, true); if (configEnable) { if (!loadStaticItems()) diff --git a/src/services/objects/UniformBoxService.java b/src/services/objects/UniformBoxService.java index 17b7e4cb6..fb2b00a5a 100644 --- a/src/services/objects/UniformBoxService.java +++ b/src/services/objects/UniformBoxService.java @@ -27,23 +27,25 @@ ***********************************************************************************/ package services.objects; +import intents.object.CreateStaticItemIntent; +import intents.object.DestroyObjectIntent; +import intents.radial.RadialSelectionIntent; + import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import intents.object.CreateStaticItemIntent; -import intents.object.DestroyObjectIntent; -import intents.radial.RadialSelectionIntent; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.player.Player; import resources.radial.RadialItem; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; public class UniformBoxService extends Service { diff --git a/src/services/player/CharacterCreationService.java b/src/services/player/CharacterCreationService.java index bc72b1594..0477ad07d 100644 --- a/src/services/player/CharacterCreationService.java +++ b/src/services/player/CharacterCreationService.java @@ -44,32 +44,34 @@ import network.packets.Packet; import network.packets.swg.login.creation.ClientCreateCharacter; import network.packets.swg.login.creation.ClientVerifyAndLockNameRequest; import network.packets.swg.login.creation.ClientVerifyAndLockNameResponse; +import network.packets.swg.login.creation.ClientVerifyAndLockNameResponse.ErrorMessage; import network.packets.swg.login.creation.CreateCharacterFailure; +import network.packets.swg.login.creation.CreateCharacterFailure.NameFailureReason; import network.packets.swg.login.creation.CreateCharacterSuccess; import network.packets.swg.login.creation.RandomNameRequest; import network.packets.swg.login.creation.RandomNameResponse; -import network.packets.swg.login.creation.ClientVerifyAndLockNameResponse.ErrorMessage; -import network.packets.swg.login.creation.CreateCharacterFailure.NameFailureReason; import resources.Race; import resources.client_info.ClientFactory; import resources.client_info.visitors.ProfTemplateData; import resources.config.ConfigFile; -import resources.control.Assert; -import resources.control.Service; import resources.objects.creature.CreatureObject; import resources.player.AccessLevel; import resources.player.Player; import resources.player.PlayerEvent; import resources.player.PlayerState; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import resources.zone.NameFilter; import services.objects.ObjectManager; import services.player.TerrainZoneInsertion.SpawnInformation; import services.player.creation.CharacterCreation; import utilities.namegen.SWGNameGenerator; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public class CharacterCreationService extends Service { private static final String CREATE_CHARACTER_SQL = "INSERT INTO players (id, name, race, userId) VALUES (?, ?, ?, ?)"; @@ -115,7 +117,7 @@ public class CharacterCreationService extends Service { @Override public boolean start() { - creationRestriction.setCreationsPerPeriod(getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS-PER-PERIOD", 2)); + creationRestriction.setCreationsPerPeriod(DataManager.getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS-PER-PERIOD", 2)); return super.start(); } @@ -180,7 +182,7 @@ public class CharacterCreationService extends Service { private void handleApproveNameRequest(PlayerManager playerMgr, Player player, ClientVerifyAndLockNameRequest request) { String name = request.getName(); ErrorMessage err = getNameValidity(name, player.getAccessLevel() != AccessLevel.PLAYER); - int max = getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 0); + int max = DataManager.getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 0); if (max != 0 && getCharacterCount(player.getUserId()) >= max) err = ErrorMessage.SERVER_CHARACTER_CREATION_MAX_CHARS; if (err == ErrorMessage.NAME_APPROVED_MODIFIED) @@ -213,7 +215,7 @@ public class CharacterCreationService extends Service { return null; } // Too many characters - int max = getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 0); + int max = DataManager.getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 0); if (max != 0 && getCharacterCount(player.getUserId()) >= max) { sendCharCreationFailure(player, create, ErrorMessage.SERVER_CHARACTER_CREATION_MAX_CHARS); return null; @@ -325,7 +327,7 @@ public class CharacterCreationService extends Service { } private CreatureObject createCharacter(ObjectManager objManager, Player player, ClientCreateCharacter create) { - String spawnLocation = getConfig(ConfigFile.PRIMARY).getString("PRIMARY-SPAWN-LOCATION", "tat_moseisley"); + String spawnLocation = DataManager.getConfig(ConfigFile.PRIMARY).getString("PRIMARY-SPAWN-LOCATION", "tat_moseisley"); SpawnInformation info = insertion.generateSpawnLocation(spawnLocation); if (info == null) { Log.e("Failed to get spawn information for location: " + spawnLocation); diff --git a/src/services/player/LoginService.java b/src/services/player/LoginService.java index 6a66cf8e6..a3614c31f 100644 --- a/src/services/player/LoginService.java +++ b/src/services/player/LoginService.java @@ -46,13 +46,13 @@ import network.packets.swg.ErrorMessage; import network.packets.swg.login.CharacterCreationDisabled; import network.packets.swg.login.EnumerateCharacterId; import network.packets.swg.login.EnumerateCharacterId.SWGCharacter; -import network.packets.swg.login.creation.DeleteCharacterRequest; -import network.packets.swg.login.creation.DeleteCharacterResponse; import network.packets.swg.login.LoginClientId; import network.packets.swg.login.LoginClientToken; import network.packets.swg.login.LoginClusterStatus; import network.packets.swg.login.LoginEnumCluster; import network.packets.swg.login.LoginIncorrectClientId; +import network.packets.swg.login.creation.DeleteCharacterRequest; +import network.packets.swg.login.creation.DeleteCharacterResponse; import network.packets.swg.zone.GameServerLagResponse; import network.packets.swg.zone.LagRequest; import network.packets.swg.zone.ServerNowEpochTime; @@ -60,20 +60,22 @@ import resources.Galaxy; import resources.Race; import resources.common.BCrypt; import resources.config.ConfigFile; -import resources.control.Assert; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.creature.CreatureObject; import resources.player.AccessLevel; import resources.player.Player; -import resources.player.PlayerState; import resources.player.Player.PlayerServer; -import resources.server_info.Config; +import resources.player.PlayerState; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import services.CoreManager; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.Config; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public class LoginService extends Service { private static final String REQUIRED_VERSION = "20111130-15:46"; @@ -137,7 +139,7 @@ public class LoginService extends Service { } private String getServerString() { - Config c = getConfig(ConfigFile.NETWORK); + Config c = DataManager.getConfig(ConfigFile.NETWORK); String name = c.getString("LOGIN-SERVER-NAME", "LoginServer"); int id = c.getInt("LOGIN-SERVER-ID", 1); return name + ":" + id; @@ -169,7 +171,7 @@ public class LoginService extends Service { Assert.test(player.getPlayerServer() == PlayerServer.NONE); player.setPlayerState(PlayerState.LOGGING_IN); player.setPlayerServer(PlayerServer.LOGIN); - final boolean doClientCheck = getConfig(ConfigFile.NETWORK).getBoolean("LOGIN-VERSION-CHECKS", true); + final boolean doClientCheck = DataManager.getConfig(ConfigFile.NETWORK).getBoolean("LOGIN-VERSION-CHECKS", true); if (!id.getVersion().equals(REQUIRED_VERSION) && doClientCheck) { onLoginClientVersionError(player, id); return; @@ -259,7 +261,7 @@ public class LoginService extends Service { cluster.addGalaxy(g); clusterStatus.addGalaxy(g); } - cluster.setMaxCharacters(getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 2)); + cluster.setMaxCharacters(DataManager.getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 2)); player.sendPacket(new ServerNowEpochTime((int)(System.currentTimeMillis()/1E3))); player.sendPacket(token); player.sendPacket(cluster); diff --git a/src/services/player/PlayerManager.java b/src/services/player/PlayerManager.java index 9ab9c8188..f21577b2c 100644 --- a/src/services/player/PlayerManager.java +++ b/src/services/player/PlayerManager.java @@ -27,26 +27,28 @@ ***********************************************************************************/ package services.player; +import intents.NotifyPlayersPacketIntent; +import intents.PlayerEventIntent; +import intents.network.ConnectionClosedIntent; +import intents.network.ConnectionOpenedIntent; + import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.BiConsumer; -import intents.NotifyPlayersPacketIntent; -import intents.PlayerEventIntent; -import intents.network.ConnectionClosedIntent; -import intents.network.ConnectionOpenedIntent; import network.packets.Packet; import resources.Terrain; -import resources.control.Assert; -import resources.control.Manager; import resources.player.Player; import resources.player.Player.PlayerServer; import resources.player.PlayerEvent; import resources.player.PlayerState; -import resources.server_info.SynchronizedMap; import services.CoreManager; +import com.projectswg.common.concurrency.SynchronizedMap; +import com.projectswg.common.control.Manager; +import com.projectswg.common.debug.Assert; + public class PlayerManager extends Manager { private final Map players; diff --git a/src/services/player/TerrainZoneInsertion.java b/src/services/player/TerrainZoneInsertion.java index 61d9677a9..1c7241f99 100644 --- a/src/services/player/TerrainZoneInsertion.java +++ b/src/services/player/TerrainZoneInsertion.java @@ -33,8 +33,9 @@ import java.sql.SQLException; import resources.Location; import resources.Terrain; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; + +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; public class TerrainZoneInsertion { diff --git a/src/services/player/ZoneManager.java b/src/services/player/ZoneManager.java index e2153204c..550b380fa 100644 --- a/src/services/player/ZoneManager.java +++ b/src/services/player/ZoneManager.java @@ -31,6 +31,10 @@ import intents.GalacticIntent; import intents.PlayerEventIntent; import intents.chat.ChatBroadcastIntent; import intents.network.GalacticPacketIntent; + +import java.io.File; +import java.io.IOException; + import network.packets.Packet; import network.packets.swg.login.AccountFeatureBits; import network.packets.swg.login.ClientIdMsg; @@ -50,18 +54,17 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import resources.config.ConfigFile; -import resources.control.Manager; import resources.objects.creature.CreatureMood; import resources.objects.player.PlayerObject; import resources.objects.waypoint.WaypointObject; import resources.objects.waypoint.WaypointObject.WaypointColor; import resources.player.Player; -import resources.player.PlayerEvent; import resources.player.Player.PlayerServer; +import resources.player.PlayerEvent; +import resources.server_info.DataManager; import resources.server_info.Log; -import java.io.File; -import java.io.IOException; +import com.projectswg.common.control.Manager; public class ZoneManager extends Manager { @@ -155,7 +158,7 @@ public class ZoneManager extends Manager { } private void sendMessageOfTheDay(Player player) { - String message = getConfig(ConfigFile.FEATURES).getString("FIRST-ZONE-MESSAGE", ""); + String message = DataManager.getConfig(ConfigFile.FEATURES).getString("FIRST-ZONE-MESSAGE", ""); if(!message.isEmpty()) // If the message isn't nothing new ChatBroadcastIntent(player, message).broadcast(); // Send it diff --git a/src/services/player/creation/CharacterCreation.java b/src/services/player/creation/CharacterCreation.java index ee3de1179..b0390ff4c 100644 --- a/src/services/player/creation/CharacterCreation.java +++ b/src/services/player/creation/CharacterCreation.java @@ -27,17 +27,17 @@ ***********************************************************************************/ package services.player.creation; -import java.util.Calendar; - import intents.experience.GrantSkillIntent; import intents.object.ObjectCreatedIntent; + +import java.util.Calendar; + import network.packets.swg.login.creation.ClientCreateCharacter; import resources.PvpFlag; import resources.Race; import resources.client_info.ClientFactory; import resources.client_info.visitors.ProfTemplateData; import resources.containers.ContainerPermissionsType; -import resources.control.Assert; import resources.objects.SWGObject; import resources.objects.building.BuildingObject; import resources.objects.cell.CellObject; @@ -52,6 +52,8 @@ import services.objects.ObjectCreator; import services.objects.ObjectManager; import services.player.TerrainZoneInsertion.SpawnInformation; +import com.projectswg.common.debug.Assert; + public class CharacterCreation { private final ObjectManager objManager; diff --git a/src/services/spawn/SpawnerService.java b/src/services/spawn/SpawnerService.java index b5ca80e02..7501e094b 100644 --- a/src/services/spawn/SpawnerService.java +++ b/src/services/spawn/SpawnerService.java @@ -27,6 +27,10 @@ ***********************************************************************************/ package services.spawn; +import intents.object.DestroyObjectIntent; +import intents.object.ObjectCreatedIntent; +import intents.server.ConfigChangedIntent; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; @@ -35,15 +39,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import intents.object.DestroyObjectIntent; -import intents.object.ObjectCreatedIntent; -import intents.server.ConfigChangedIntent; import resources.Location; import resources.PvpFlag; import resources.Terrain; import resources.config.ConfigFile; import resources.containers.ContainerPermissionsType; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureDifficulty; @@ -51,9 +51,8 @@ import resources.objects.creature.CreatureObject; import resources.objects.custom.AIBehavior; import resources.objects.custom.DefaultAIObject; import resources.objects.tangible.OptionFlag; +import resources.server_info.DataManager; import resources.server_info.Log; -import resources.server_info.RelationalDatabase; -import resources.server_info.RelationalServerFactory; import resources.server_info.StandardLog; import resources.spawn.Spawner; import resources.spawn.SpawnerType; @@ -61,6 +60,10 @@ import services.objects.ObjectCreator; import services.objects.ObjectManager; import utilities.ThreadUtilities; +import com.projectswg.common.control.Service; +import com.projectswg.common.info.RelationalDatabase; +import com.projectswg.common.info.RelationalServerFactory; + public final class SpawnerService extends Service { private static final String GET_ALL_SPAWNERS_SQL = "SELECT static.x, static.y, static.z, static.heading, " // static columns @@ -88,7 +91,7 @@ public final class SpawnerService extends Service { @Override public boolean initialize() { - if(getConfig(ConfigFile.FEATURES).getBoolean("SPAWN-EGGS-ENABLED", true)) + if(DataManager.getConfig(ConfigFile.FEATURES).getBoolean("SPAWN-EGGS-ENABLED", true)) loadSpawners(); return super.initialize(); diff --git a/src/services/spawn/StaticService.java b/src/services/spawn/StaticService.java index 7d0634f03..38a689f8f 100644 --- a/src/services/spawn/StaticService.java +++ b/src/services/spawn/StaticService.java @@ -39,15 +39,16 @@ import java.util.List; import java.util.Map; import resources.Location; -import resources.control.Assert; -import resources.control.Service; import resources.objects.SWGObject; import resources.objects.building.BuildingObject; import resources.server_info.Log; -import resources.server_info.RelationalServerData; -import resources.server_info.RelationalServerFactory; import services.objects.ObjectCreator; +import com.projectswg.common.control.Service; +import com.projectswg.common.debug.Assert; +import com.projectswg.common.info.RelationalServerData; +import com.projectswg.common.info.RelationalServerFactory; + public class StaticService extends Service { private static final String GET_SUPPORTING_SQL = "SELECT spawns.* FROM spawns, types WHERE types.iff = ? AND spawns.iff_type = types.iff_type"; diff --git a/src/services/sui/SuiService.java b/src/services/sui/SuiService.java index 8455869e1..24dd90730 100644 --- a/src/services/sui/SuiService.java +++ b/src/services/sui/SuiService.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package services.sui; +import intents.network.GalacticPacketIntent; +import intents.sui.SuiWindowIntent; + import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashMap; @@ -34,14 +37,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import intents.network.GalacticPacketIntent; -import intents.sui.SuiWindowIntent; import network.packets.Packet; import network.packets.swg.SWGPacket; import network.packets.swg.zone.server_ui.SuiCreatePageMessage; import network.packets.swg.zone.server_ui.SuiEventNotification; import network.packets.swg.zone.server_ui.SuiForceClosePage; -import resources.control.Service; import resources.player.Player; import resources.server_info.Log; import resources.sui.ISuiCallback; @@ -50,6 +50,8 @@ import resources.sui.SuiComponent; import resources.sui.SuiEvent; import utilities.Scripts; +import com.projectswg.common.control.Service; + public class SuiService extends Service { private final Map> windows; diff --git a/src/utilities/IntentChain.java b/src/utilities/IntentChain.java deleted file mode 100644 index 9ce51de48..000000000 --- a/src/utilities/IntentChain.java +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************************************************ - * 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 utilities; - -import resources.control.Intent; - -public class IntentChain { - - private final Object mutex; - private Intent i; - - public IntentChain() { - mutex = new Object(); - i = null; - } - - public void reset() { - synchronized (mutex) { - i = null; - } - } - - public void waitUntilComplete(Intent i) { - synchronized (mutex) { - this.i = i; - } - } - - public void broadcastAfter(Intent i) { - synchronized (mutex) { - i.broadcastAfterIntent(this.i); - this.i = i; - } - } - -} diff --git a/src/utilities/IntentFactory.java b/src/utilities/IntentFactory.java index 587bfe467..4ced27797 100644 --- a/src/utilities/IntentFactory.java +++ b/src/utilities/IntentFactory.java @@ -27,6 +27,9 @@ ***********************************************************************************/ package utilities; +import intents.chat.ChatBroadcastIntent; +import intents.chat.ChatBroadcastIntent.BroadcastType; +import intents.chat.PersistentMessageIntent; import network.packets.swg.zone.chat.ChatSystemMessage; import resources.Terrain; import resources.encodables.OutOfBandPackage; @@ -36,11 +39,10 @@ import resources.encodables.player.Mail; import resources.objects.SWGObject; import resources.objects.waypoint.WaypointObject; import resources.player.Player; -import intents.chat.ChatBroadcastIntent; -import intents.chat.ChatBroadcastIntent.BroadcastType; -import intents.chat.PersistentMessageIntent; import resources.server_info.Log; +import com.projectswg.common.control.IntentChain; + /** * @author Mads * This class is to be used exclusively in cases where using the intents directly isn't practical. diff --git a/test/resources/control/TestIntentQueue.java b/test/resources/control/TestIntentQueue.java deleted file mode 100644 index 5b23c66eb..000000000 --- a/test/resources/control/TestIntentQueue.java +++ /dev/null @@ -1,49 +0,0 @@ -package resources.control; - -import java.util.Arrays; - -import intents.LoginEventIntent; -import intents.PlayerEventIntent; -import intents.object.ObjectCreatedIntent; -import intents.object.ObjectTeleportIntent; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class TestIntentQueue { - - @Test - public void testQueue() { - Intent [] intents = new Intent[5]; - intents[0] = new PlayerEventIntent(null, null); - intents[1] = new ObjectTeleportIntent(null, null); - intents[2] = new ObjectCreatedIntent(null); - intents[3] = new ObjectCreatedIntent(null); - intents[4] = new LoginEventIntent(0, null); - IntentQueue queue = new IntentQueue(); - long start = System.nanoTime(); - queue.addAll(Arrays.asList(intents)); - long end = System.nanoTime(); - System.out.println((end-start)/1E6/5 + "ms"); - int iterI = 0; - Assert.assertEquals(queue.size(), 5); - Assert.assertFalse(queue.isEmpty()); - for (Intent i : queue) { - Assert.assertEquals("Failed for intent #"+iterI, intents[iterI], i); - iterI++; - } - Assert.assertEquals(queue.size(), 5); - Assert.assertFalse(queue.isEmpty()); - for (int i = 0; i < intents.length; i++) { - Intent intent = queue.poll(); - Assert.assertEquals(5 - i - 1, queue.size()); - Assert.assertEquals("Failed for intent #"+i, intents[i], intent); - } - Assert.assertEquals(queue.size(), 0); - Assert.assertTrue(queue.isEmpty()); - } - -} diff --git a/test/resources/objects/awareness/TestAwareness.java b/test/resources/objects/awareness/TestAwareness.java index ec2f01527..7470e23b9 100644 --- a/test/resources/objects/awareness/TestAwareness.java +++ b/test/resources/objects/awareness/TestAwareness.java @@ -32,6 +32,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; +import com.projectswg.common.info.RelationalServerFactory; + import utilities.CrcDatabaseGenerator; @RunWith(Suite.class) @@ -45,6 +47,7 @@ public class TestAwareness { @BeforeClass public static void setupCrcDatabase() { + RelationalServerFactory.setBasePath("serverdata/"); CrcDatabaseGenerator.generate(false); } diff --git a/test/resources/services/TestConfig.java b/test/resources/services/TestConfig.java index 4ba803346..f9ba6999b 100644 --- a/test/resources/services/TestConfig.java +++ b/test/resources/services/TestConfig.java @@ -35,9 +35,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import resources.server_info.Config; import resources.server_info.Log; +import com.projectswg.common.info.Config; + @RunWith(JUnit4.class) public class TestConfig {