Added integration tests and improved awareness speed

This commit is contained in:
Obique
2018-06-29 14:08:55 -05:00
parent 202100e4d5
commit c956413337
38 changed files with 1195 additions and 141 deletions

View File

@@ -24,6 +24,7 @@ apply plugin: 'com.github.johnrengelman.shadow'
sourceSets {
display { }
utility { }
integration { }
}
test {
@@ -63,6 +64,13 @@ dependencies {
utilityCompile sourceSets.main.output
utilityCompile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.23.1'
utilityCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.6.3'
integrationCompile project(':pswgcommon')
integrationCompile project(':client-holocore')
integrationCompile sourceSets.main.output
integrationCompile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.23.1'
integrationCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.6.3'
integrationCompile 'junit:junit:4.12'
}
task CreateConvertLoginJar(type: ShadowJar) {

12
client-holocore/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
.settings
# IDE-based
Client Holocore.iml
out
.idea
# Gradle
build
.gradle
gradle
gradlew*

3
client-holocore/.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "pswgcommon"]
path = pswgcommon
url = git@bitbucket.org:projectswg/pswgcommon.git

View File

@@ -0,0 +1,16 @@
plugins {
id 'java'
}
sourceCompatibility = 9
targetCompatibility = 9
repositories {
jcenter()
}
dependencies {
compile project(':pswgcommon')
testCompile 'junit:junit:4.12'
}

Binary file not shown.

View File

@@ -0,0 +1 @@
include ':pswgcommon'

View File

@@ -0,0 +1,67 @@
package com.projectswg.connection;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.NetBufferStream;
class HolocoreProtocol {
public static final String VERSION = "2018-02-04";
private static final byte [] EMPTY_PACKET = new byte[0];
private final NetBufferStream inboundStream;
public HolocoreProtocol() {
this.inboundStream = new NetBufferStream();
}
public void reset() {
inboundStream.reset();
}
public NetBuffer assemble(byte [] raw) {
NetBuffer data = NetBuffer.allocate(raw.length + 4); // large array
data.addArrayLarge(raw);
data.flip();
return data;
}
public boolean addToBuffer(byte [] data) {
synchronized (inboundStream) {
inboundStream.write(data);
return hasPacket();
}
}
public byte [] disassemble() {
synchronized (inboundStream) {
if (inboundStream.remaining() < 4) {
return EMPTY_PACKET;
}
inboundStream.mark();
int messageLength = inboundStream.getInt();
if (inboundStream.remaining() < messageLength) {
inboundStream.rewind();
return EMPTY_PACKET;
}
byte [] data = inboundStream.getArray(messageLength);
inboundStream.compact();
return data;
}
}
public boolean hasPacket() {
synchronized (inboundStream) {
if (inboundStream.remaining() < 4)
return false;
inboundStream.mark();
try {
int messageLength = inboundStream.getInt();
return inboundStream.remaining() >= messageLength;
} finally {
inboundStream.rewind();
}
}
}
}

View File

@@ -0,0 +1,320 @@
package com.projectswg.connection;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted;
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped;
import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion;
import me.joshlarson.jlcommon.log.Log;
import me.joshlarson.jlcommon.network.TCPSocket;
import me.joshlarson.jlcommon.network.TCPSocket.TCPSocketCallback;
import me.joshlarson.jlcommon.network.UDPServer;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Locale;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class HolocoreSocket {
private static final int BUFFER_SIZE = 128 * 1024;
private final SWGProtocol swgProtocol;
private final AtomicReference<ServerConnectionStatus> status;
private final UDPServer udpServer;
private final BlockingQueue<DatagramPacket> udpInboundQueue;
private final BlockingQueue<RawPacket> inboundQueue;
private TCPSocket socket;
private StatusChangedCallback callback;
private InetSocketAddress address;
public HolocoreSocket(InetAddress addr, int port) {
this.swgProtocol = new SWGProtocol();
this.status = new AtomicReference<>(ServerConnectionStatus.DISCONNECTED);
this.udpInboundQueue = new LinkedBlockingQueue<>();
this.inboundQueue = new LinkedBlockingQueue<>();
this.udpServer = createUDPServer();
this.socket = null;
this.callback = null;
this.address = new InetSocketAddress(addr, port);
}
/**
* Shuts down any miscellaneous resources--such as the query UDP server
*/
public void terminate() {
if (udpServer != null)
udpServer.close();
}
/**
* Sets a callback for when the status of the server socket changes
* @param callback the callback
*/
public void setStatusChangedCallback(StatusChangedCallback callback) {
this.callback = callback;
}
/**
* Sets the remote address this socket will attempt to connect to
* @param addr the destination address
* @param port the destination port
*/
public void setRemoteAddress(InetAddress addr, int port) {
this.address = new InetSocketAddress(addr, port);
}
/**
* Returns the remote address this socket is pointing to
* @return the remote address as an InetSocketAddress
*/
public InetSocketAddress getRemoteAddress() {
return address;
}
/**
* Gets the current connection state of the socket
* @return the connection state
*/
public ServerConnectionStatus getConnectionState() {
return status.get();
}
/**
* Returns whether or not this socket is disconnected
* @return TRUE if disconnected, FALSE otherwise
*/
public boolean isDisconnected() {
return status.get() == ServerConnectionStatus.DISCONNECTED;
}
/**
* Returns whether or not this socket is connecting
* @return TRUE if connecting, FALSE otherwise
*/
public boolean isConnecting() {
return status.get() == ServerConnectionStatus.CONNECTING;
}
/**
* Returns whether or not this socket is connected
* @return TRUE if connected, FALSE otherwise
*/
public boolean isConnected() {
return status.get() == ServerConnectionStatus.CONNECTED;
}
/**
* Retrieves the server status via a UDP query, with the default timeout of 2000ms
* @return the server status as a string
*/
public String getServerStatus() {
return getServerStatus(2000);
}
/**
* Retrives the server status via a UDP query, with the specified timeout
* @param timeout the timeout in milliseconds
* @return the server status as a string
*/
public String getServerStatus(long timeout) {
Log.t("Requesting server status from %s", address);
if (!udpServer.isRunning()) {
try {
udpServer.bind();
} catch (SocketException e) {
return "UNKNOWN";
}
}
udpServer.send(address, new byte[]{1});
try {
DatagramPacket packet = udpInboundQueue.poll(timeout, TimeUnit.MILLISECONDS);
if (packet == null)
return "OFFLINE";
NetBuffer data = NetBuffer.wrap(packet.getData());
data.getByte();
return data.getAscii();
} catch (InterruptedException e) {
Log.w("Interrupted while waiting for server status response");
return "UNKNOWN";
}
}
/**
* Attempts to connect to the remote server. This call is a blocking function that will not
* return until it has either successfully connected or has failed. It starts by initializing a
* TCP connection, then initializes the Holocore connection, then returns.
* @param timeout the timeout for the connect call
* @return TRUE if successful and connected, FALSE on error
*/
public boolean connect(int timeout) {
TCPSocket socket = new TCPSocket(address, BUFFER_SIZE);
return finishConnection(socket, timeout);
}
private boolean finishConnection(TCPSocket socket, int timeout) {
updateStatus(ServerConnectionStatus.CONNECTING, ServerConnectionChangedReason.NONE);
try {
socket.createConnection();
socket.getSocket().setKeepAlive(true);
socket.getSocket().setPerformancePreferences(0, 1, 2);
socket.getSocket().setTrafficClass(0x10); // Low Delay bit
socket.getSocket().setSoLinger(true, 3);
socket.startConnection();
socket.setCallback(new TCPSocketCallback() {
@Override
public void onIncomingData(TCPSocket socket, byte[] data) {
swgProtocol.addToBuffer(data);
while (true) {
RawPacket packet = swgProtocol.disassemble();
if (packet != null)
inboundQueue.offer(packet);
else
break;
}
}
@Override
public void onDisconnected(TCPSocket socket) { updateStatus(ServerConnectionStatus.DISCONNECTED, ServerConnectionChangedReason.UNKNOWN); }
@Override
public void onConnected(TCPSocket socket) { updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE); }
});
this.socket = socket;
waitForConnect(timeout);
return true;
} catch (IOException e) {
Log.e(e);
updateStatus(ServerConnectionStatus.DISCONNECTED, getReason(e.getMessage()));
socket.disconnect();
}
return false;
}
/**
* Attempts to disconnect from the server with the specified reason. Before this socket is
* closed, it will send a HoloConnectionStopped packet to notify the remote server.
* @param reason the reason for disconnecting
* @return TRUE if successfully disconnected, FALSE on error
*/
public boolean disconnect(ServerConnectionChangedReason reason) {
TCPSocket socket = this.socket;
if (socket != null)
return socket.disconnect();
return true;
}
/**
* Attempts to send a byte array to the remote server. This method blocks until it has
* completely sent or has failed.
* @param raw the byte array to send
* @return TRUE on success, FALSE on failure
*/
public boolean send(byte [] raw) {
TCPSocket socket = this.socket;
if (socket != null)
return socket.send(swgProtocol.assemble(raw).getBuffer());
return false;
}
/**
* Attempts to receive a packet from the remote server. This method blocks until a packet is
* recieved or has failed.
* @return the RawPacket containing the CRC of the SWG message and the raw data array, or NULL
* on error
*/
public RawPacket receive() {
try {
return inboundQueue.take();
} catch (InterruptedException e) {
return null;
}
}
/**
* Returns whether or not there is a packet ready to be received without blocking
* @return TRUE if there is a packet, FALSE otherwise
*/
public boolean hasPacket() {
return !inboundQueue.isEmpty();
}
private void waitForConnect(int timeout) throws SocketException {
send(new HoloSetProtocolVersion(HolocoreProtocol.VERSION).encode().array());
socket.getSocket().setSoTimeout(timeout);
try {
while (isConnecting()) {
RawPacket packet = receive();
if (packet == null)
continue;
handlePacket(packet.getCrc(), packet.getData());
}
if (isConnected())
send(new HoloConnectionStarted().encode().array());
} finally {
socket.getSocket().setSoTimeout(0);
}
}
private void handlePacket(int crc, byte [] raw) {
if (crc == HoloConnectionStarted.CRC) {
updateStatus(ServerConnectionStatus.CONNECTED, ServerConnectionChangedReason.NONE);
} else if (crc == HoloConnectionStopped.CRC) {
HoloConnectionStopped packet = new HoloConnectionStopped();
packet.decode(NetBuffer.wrap(raw));
switch (packet.getReason()) {
case INVALID_PROTOCOL:
disconnect(ServerConnectionChangedReason.INVALID_PROTOCOL);
break;
default:
disconnect(ServerConnectionChangedReason.NONE);
break;
}
}
}
private void updateStatus(ServerConnectionStatus status, ServerConnectionChangedReason reason) {
ServerConnectionStatus old = this.status.getAndSet(status);
if (old != status && callback != null)
callback.onConnectionStatusChanged(old, status, reason);
}
private ServerConnectionChangedReason getReason(String message) {
message = message.toLowerCase(Locale.US);
if (message.contains("broken pipe"))
return ServerConnectionChangedReason.BROKEN_PIPE;
if (message.contains("connection reset"))
return ServerConnectionChangedReason.CONNECTION_RESET;
if (message.contains("connection refused"))
return ServerConnectionChangedReason.CONNECTION_REFUSED;
if (message.contains("address in use"))
return ServerConnectionChangedReason.ADDR_IN_USE;
if (message.contains("socket closed"))
return ServerConnectionChangedReason.SOCKET_CLOSED;
if (message.contains("no route to host"))
return ServerConnectionChangedReason.NO_ROUTE_TO_HOST;
return ServerConnectionChangedReason.UNKNOWN;
}
public interface StatusChangedCallback {
void onConnectionStatusChanged(ServerConnectionStatus oldStatus, ServerConnectionStatus newStatus, ServerConnectionChangedReason reason);
}
private UDPServer createUDPServer() {
try {
UDPServer server = new UDPServer(new InetSocketAddress(0), 1500, udpInboundQueue::add);
server.bind();
return server;
} catch (SocketException e) {
Log.e(e);
}
return null;
}
}

View File

@@ -0,0 +1,41 @@
/***********************************************************************************
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
* *
* This file is part of the ProjectSWG Launcher. *
* *
* This program 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. *
* *
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>. *
* *
***********************************************************************************/
package com.projectswg.connection;
public class RawPacket {
private final int crc;
private final byte[] data;
public RawPacket(int crc, byte[] data) {
this.crc = crc;
this.data = data;
}
public int getCrc() {
return crc;
}
public byte[] getData() {
return data;
}
}

View File

@@ -0,0 +1,38 @@
package com.projectswg.connection;
import com.projectswg.common.network.NetBuffer;
class SWGProtocol {
private final HolocoreProtocol holocore;
public SWGProtocol() {
holocore = new HolocoreProtocol();
}
public void reset() {
holocore.reset();
}
public NetBuffer assemble(byte [] packet) {
return holocore.assemble(packet);
}
public boolean addToBuffer(byte [] data) {
return holocore.addToBuffer(data);
}
public RawPacket disassemble() {
byte [] packet = holocore.disassemble();
if (packet.length < 6)
return null;
NetBuffer data = NetBuffer.wrap(packet);
data.getShort();
return new RawPacket(data.getInt(), packet);
}
public boolean hasPacket() {
return holocore.hasPacket();
}
}

View File

@@ -0,0 +1,16 @@
package com.projectswg.connection;
public enum ServerConnectionChangedReason {
NONE,
CLIENT_DISCONNECT,
SOCKET_CLOSED,
CONNECT_TIMEOUT,
INVALID_PROTOCOL,
BROKEN_PIPE,
CONNECTION_RESET,
CONNECTION_REFUSED,
ADDR_IN_USE,
NO_ROUTE_TO_HOST,
OTHER_SIDE_TERMINATED,
UNKNOWN
}

View File

@@ -0,0 +1,7 @@
package com.projectswg.connection;
public enum ServerConnectionStatus {
CONNECTING,
CONNECTED,
DISCONNECTED
}

View File

@@ -1,5 +1,6 @@
#Thu Jun 28 08:06:24 CDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip

View File

@@ -1,2 +1,3 @@
rootProject.name = 'holocore'
include 'pswgcommon'
include ':pswgcommon'
include ':client-holocore'

View File

@@ -0,0 +1,48 @@
package com.projectswg.holocore;
import com.projectswg.connection.HolocoreSocket;
import com.projectswg.connection.ServerConnectionChangedReason;
import me.joshlarson.jlcommon.concurrency.BasicThread;
import me.joshlarson.jlcommon.concurrency.Delay;
import java.net.InetAddress;
public class ProjectSWGRunner {
private final BasicThread runner;
public ProjectSWGRunner() {
this.runner = new BasicThread("holocore", () -> ProjectSWG.run(new String[0]));
}
public void start() {
runner.start();
{
HolocoreSocket socket = new HolocoreSocket(InetAddress.getLoopbackAddress(), 44463);
long start = System.nanoTime();
boolean connected = false;
while (System.nanoTime() - start <= 60E9) { // 60s max wait
connected = socket.getServerStatus().equals("UP");
if (connected)
break;
Delay.sleepSeconds(1);
}
if (connected) {
start = System.nanoTime();
while (System.nanoTime() - start <= 60E9) { // 60s max wait
if (socket.connect(1000)) {
socket.disconnect(ServerConnectionChangedReason.CLIENT_DISCONNECT);
break;
}
}
}
socket.terminate();
}
}
public void stop() {
runner.stop(true);
runner.awaitTermination(5000);
}
}

View File

@@ -0,0 +1,25 @@
package com.projectswg.holocore.integration.resources;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ClientRunner {
protected HolocoreClient client;
@Before
public void initializeClient() {
client = new HolocoreClient();
Assert.assertTrue(client.login("Obique", "pass"));
}
@After
public void terminateClient() {
client.disconnect();
}
}

View File

@@ -0,0 +1,90 @@
package com.projectswg.holocore.integration.resources;
import com.projectswg.common.data.customization.CustomizationString;
import com.projectswg.common.data.customization.CustomizationVariable;
import com.projectswg.common.network.packets.swg.login.creation.*;
import org.junit.Assert;
public class ClientUtilities {
private ClientUtilities() {
}
public static void createCharacter(HolocoreClient client) {
client.send(new RandomNameRequest());
RandomNameResponse randomNameResponse = (RandomNameResponse) client.receive();
Assert.assertNotNull(randomNameResponse);
client.send(new ClientVerifyAndLockNameRequest(randomNameResponse.getRace(), randomNameResponse.getRandomName()));
ClientVerifyAndLockNameResponse verifiedResponse = (ClientVerifyAndLockNameResponse) client.receive();
Assert.assertNotNull(verifiedResponse);
ClientCreateCharacter create = new ClientCreateCharacter();
create.setCharCustomization(createCharacterCustomization());
create.setName(randomNameResponse.getRandomName());
create.setRace(randomNameResponse.getRace());
create.setStart("mos_eisley");
create.setHair("object/tangible/hair/human/hair_human_male_s02.iff");
create.setHairCustomization(createHairCustomization());
create.setClothes("combat_brawler");
create.setHeight(0.9648422f);
create.setTutorial(false);
create.setProfession("smuggler_1a");
create.setStartingPhase("class_smuggler_phase1_novice");
client.send(create);
CreateCharacterSuccess success = (CreateCharacterSuccess) client.receive();
Assert.assertNotNull(success);
client.addCharacter(success.getId(), create.getName());
client.zoneIn(success.getId());
}
private static CustomizationString createCharacterCustomization() {
CustomizationString str = new CustomizationString();
str.put("/shared_owner/blend_lipfullness_0", new CustomizationVariable(33));
str.put("/shared_owner/blend_lipfullness_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_chinsize_0", new CustomizationVariable(208));
str.put("/shared_owner/blend_chinsize_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_fat", new CustomizationVariable(0));
str.put("/shared_owner/blend_ears_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_ears_0", new CustomizationVariable(191));
str.put("/shared_owner/blend_noselength_0", new CustomizationVariable(0));
str.put("/shared_owner/blend_noselength_1", new CustomizationVariable(40));
str.put("/shared_owner/blend_jaw_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_jaw_0", new CustomizationVariable(156));
str.put("/shared_owner/blend_eyeshape_1", new CustomizationVariable(32));
str.put("/shared_owner/blend_nosewidth_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_eyeshape_0", new CustomizationVariable(0));
str.put("/shared_owner/blend_nosewidth_0", new CustomizationVariable(35));
str.put("/shared_owner/index_color_skin", new CustomizationVariable(35));
str.put("/shared_owner/blend_cheeks_1", new CustomizationVariable(5));
str.put("/shared_owner/blend_eyedirection_1", new CustomizationVariable(16));
str.put("/shared_owner/blend_skinny", new CustomizationVariable(26));
str.put("/shared_owner/blend_cheeks_0", new CustomizationVariable(0));
str.put("/shared_owner/blend_eyedirection_0", new CustomizationVariable(0));
str.put("/shared_owner/blend_nosedepth_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_nosedepth_0", new CustomizationVariable(107));
str.put("/shared_owner/blend_lipwidth_0", new CustomizationVariable(23));
str.put("/shared_owner/blend_lipwidth_1", new CustomizationVariable(0));
str.put("/shared_owner/blend_muscle", new CustomizationVariable(144));
str.put("/shared_owner/blend_eyesize_0", new CustomizationVariable(117));
str.put("/shared_owner/blend_eyesize_1", new CustomizationVariable(0));
str.put("/private/index_style_beard", new CustomizationVariable(0));
str.put("/private/index_style_freckles", new CustomizationVariable(0));
str.put("/private/index_age", new CustomizationVariable(0));
str.put("/private/index_color_skin", new CustomizationVariable(0));
str.put("/private/index_color_2", new CustomizationVariable(1));
str.put("/private/index_color_3", new CustomizationVariable(0));
str.put("/private/index_color_facial_hair", new CustomizationVariable(1));
str.put("/private/index_style_eyebrow", new CustomizationVariable(0));
return str;
}
private static CustomizationString createHairCustomization() {
CustomizationString str = new CustomizationString();
str.put("/private/index_color_1", new CustomizationVariable(1));
return str;
}
}

View File

@@ -0,0 +1,191 @@
package com.projectswg.holocore.integration.resources;
import com.projectswg.common.data.CRC;
import com.projectswg.common.data.location.Location;
import com.projectswg.common.data.location.Terrain;
import com.projectswg.common.network.NetBuffer;
import com.projectswg.common.network.packets.PacketType;
import com.projectswg.common.network.packets.SWGPacket;
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginRequestPacket;
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginResponsePacket;
import com.projectswg.common.network.packets.swg.login.EnumerateCharacterId.SWGCharacter;
import com.projectswg.common.network.packets.swg.zone.CmdSceneReady;
import com.projectswg.common.network.packets.swg.zone.SceneCreateObjectByCrc;
import com.projectswg.common.network.packets.swg.zone.SceneEndBaselines;
import com.projectswg.common.network.packets.swg.zone.baselines.Baseline;
import com.projectswg.common.network.packets.swg.zone.insertion.CmdStartScene;
import com.projectswg.common.network.packets.swg.zone.insertion.SelectCharacter;
import com.projectswg.connection.HolocoreSocket;
import com.projectswg.connection.RawPacket;
import com.projectswg.connection.ServerConnectionChangedReason;
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import me.joshlarson.jlcommon.concurrency.BasicThread;
import me.joshlarson.jlcommon.concurrency.Delay;
import org.junit.Assert;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
public class HolocoreClient {
private final HolocoreSocket socket;
private final BasicThread listenThread;
private final AtomicReference<Location> location;
private final AtomicReference<Terrain> terrain;
private final Map<Long, SWGObject> objectsAware;
private final Map<Long, SWGObject> objectsInProgress;
private final Map<Long, String> characters;
private final BlockingQueue<SWGPacket> packets;
private final AtomicLong characterId;
private final AtomicBoolean zonedIn;
public HolocoreClient() {
this(new InetSocketAddress("localhost", 44463));
}
public HolocoreClient(InetSocketAddress address) {
this.socket = new HolocoreSocket(address.getAddress(), address.getPort());
this.listenThread = new BasicThread("holocore-client-listen", this::listen);
this.location = new AtomicReference<>(null);
this.terrain = new AtomicReference<>(null);
this.objectsAware = new ConcurrentHashMap<>();
this.objectsInProgress = new ConcurrentHashMap<>();
this.characters = new ConcurrentHashMap<>();
this.packets = new LinkedBlockingQueue<>();
this.characterId = new AtomicLong(0);
this.zonedIn = new AtomicBoolean(false);
}
public long getCharacterId() {
return characterId.get();
}
public String getCharacterName() {
return characters.get(getCharacterId());
}
public void addCharacter(long id, String name) {
this.characters.put(id, name);
}
public boolean login(String username, String password) {
Assert.assertTrue(socket.connect(5000));
listenThread.start();
send(new HoloLoginRequestPacket(username, password));
HoloLoginResponsePacket response = receiveNext(PacketType.HOLO_LOGIN_RESPONSE);
return response.isSuccess();
}
public void zoneIn(long characterId) {
this.characterId.set(characterId);
send(new SelectCharacter(characterId));
}
public void waitForZoneIn() {
long start = System.nanoTime();
while ((!zonedIn.get() || !objectsAware.containsKey(getCharacterId())) && System.nanoTime() - start < 10E9) {
Delay.sleepMilli(100);
}
Assert.assertTrue(zonedIn.get());
Assert.assertTrue(objectsAware.containsKey(getCharacterId()));
}
public void disconnect() {
listenThread.stop(false);
socket.disconnect(ServerConnectionChangedReason.CLIENT_DISCONNECT);
socket.terminate();
listenThread.awaitTermination(1000);
}
public void send(SWGPacket packet) {
socket.send(packet.encode().array());
}
@SuppressWarnings("unchecked")
public <T extends SWGPacket> T receiveNext(PacketType type) {
SWGPacket packet;
while ((packet = receive()) != null) {
if (packet.getPacketType() == type) {
return (T) packet;
}
}
return null;
}
public SWGPacket receive() {
try {
return packets.poll(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return null;
}
}
private void listen() {
RawPacket received;
while ((received = socket.receive()) != null) {
SWGPacket packet = PacketType.getForCrc(received.getCrc());
Assert.assertNotNull(packet);
packet.decode(NetBuffer.wrap(received.getData()));
process(packet);
this.packets.add(packet);
}
}
private void process(SWGPacket packet) {
switch (packet.getPacketType()) {
case HOLO_LOGIN_RESPONSE:
processLoginResponse((HoloLoginResponsePacket) packet);
break;
case CMD_START_SCENE:
zonedIn.set(true);
location.set(((CmdStartScene) packet).getLocation());
terrain.set(((CmdStartScene) packet).getLocation().getTerrain());
send(new CmdSceneReady());
break;
case SCENE_CREATE_OBJECT_BY_CRC:
processSceneCreateObject((SceneCreateObjectByCrc) packet);
break;
case BASELINE:
processBaseline((Baseline) packet);
break;
case SCENE_END_BASELINES:
processEndBaselines((SceneEndBaselines) packet);
break;
}
}
private void processLoginResponse(HoloLoginResponsePacket login) {
for (SWGCharacter character : login.getCharacters()) {
characters.put(character.getId(), character.getName());
}
}
private void processSceneCreateObject(SceneCreateObjectByCrc create) {
SWGObject obj = ObjectCreator.createObjectFromTemplate(CRC.getString(create.getObjectCrc()));
obj.setLocation(Location.builder(create.getLocation()).setTerrain(terrain.get()).build());
objectsInProgress.put(create.getObjectId(), obj);
}
private void processBaseline(Baseline base) {
SWGObject obj = objectsInProgress.get(base.getObjectId());
Objects.requireNonNull(obj);
obj.parseBaseline(base);
}
private void processEndBaselines(SceneEndBaselines end) {
SWGObject obj = objectsInProgress.get(end.getObjectId());
Objects.requireNonNull(obj);
objectsAware.put(end.getObjectId(), obj);
}
}

View File

@@ -0,0 +1,29 @@
package com.projectswg.holocore.integration.test;
import com.projectswg.holocore.ProjectSWGRunner;
import com.projectswg.holocore.integration.test.login.TestLogin;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses(value = {
TestLogin.class
})
public class TestIntegration {
private static final ProjectSWGRunner HOLOCORE = new ProjectSWGRunner();
@BeforeClass
public static void startHolocore() {
HOLOCORE.start();
}
@AfterClass
public static void stopHolocore() {
HOLOCORE.stop();
}
}

View File

@@ -0,0 +1,19 @@
package com.projectswg.holocore.integration.test.login;
import com.projectswg.common.network.packets.swg.login.creation.DeleteCharacterRequest;
import com.projectswg.holocore.integration.resources.ClientRunner;
import com.projectswg.holocore.integration.resources.ClientUtilities;
import org.junit.Test;
public class TestCharacterCreation extends ClientRunner {
@Test
public void testCreateCharacter() {
ClientUtilities.createCharacter(client);
System.out.println("Created character: " + client.getCharacterName() + " with id " + client.getCharacterId());
client.waitForZoneIn();
System.out.println("Zoned in");
client.send(new DeleteCharacterRequest(0, client.getCharacterId()));
}
}

View File

@@ -0,0 +1,13 @@
package com.projectswg.holocore.integration.test.login;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses(value = {
TestCharacterCreation.class
})
public class TestLogin {
}

View File

@@ -78,7 +78,7 @@ public class ProjectSWG {
return GALAXY;
}
private static int run(String [] args) {
static int run(String [] args) {
File logDirectory = new File("log");
if (!logDirectory.isDirectory() && !logDirectory.mkdir())
Log.w("Failed to make log directory!");

View File

@@ -32,6 +32,7 @@ import java.util.Collection;
import java.util.Collections;
public enum AwarenessType {
SELF,
OBJECT,
GROUP,
TRADE;

View File

@@ -63,14 +63,16 @@ public class ObjectAware {
for (SWGObject removed : oldAware) {
if (objects.contains(removed))
continue;
if (removed.getAwareness().removeAware(type, object)) {
if (removed == object || removed.getAwareness().removeAware(type, object)) {
object.onObjectLeaveAware(removed);
flush = true;
}
}
for (SWGObject added : objects) {
if (added.getAwareness().addAware(type, object)) {
if (oldAware.contains(added))
continue;
if (added == object || added.getAwareness().addAware(type, object)) {
object.onObjectEnterAware(added);
flush = true;
}
@@ -134,24 +136,12 @@ public class ObjectAware {
private boolean notAware(SWGObject test) {
for (Collection<SWGObject> aware : awareness.values()) {
for (SWGObject obj : aware) {
if (obj == test)
return false;
}
if (aware.contains(test))
return false;
}
return true;
}
private Map<SWGObject, Integer> getAwareCounts() {
Map<SWGObject, Integer> counts = new HashMap<>();
for (Collection<SWGObject> aware : awareness.values()) {
for (SWGObject obj : aware) {
counts.put(obj, counts.getOrDefault(obj, 0) + 1);
}
}
return counts;
}
private static Set<SWGObject> createSet() {
return ConcurrentHashMap.newKeySet();
}

View File

@@ -94,7 +94,6 @@ public class TerrainMap {
else
aware = new HashSet<>(superParent.getAware(AwarenessType.OBJECT));
aware.removeIf(AwarenessUtilities::notInAwareness);
recursiveAdd(aware, obj);
return aware;
}
@@ -121,14 +120,4 @@ public class TerrainMap {
}
}
private static void recursiveAdd(@NotNull Collection<SWGObject> aware, @NotNull SWGObject obj) {
aware.add(obj);
for (SWGObject child : obj.getSlottedObjects()) {
recursiveAdd(aware, child);
}
for (SWGObject child : obj.getContainedObjects()) {
recursiveAdd(aware, child);
}
}
}

View File

@@ -123,6 +123,8 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
setSlot(requiredSlot, object);
}
}
onAddedChild(object);
object.parent = this;
object.setTerrain(getTerrain());
}
@@ -145,6 +147,7 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
}
// Remove as parent
onRemovedChild(object);
object.parent = null;
object.slotArrangement = -1;
}
@@ -251,6 +254,18 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
}
}
protected void onAddedChild(SWGObject child) {
SWGObject parent = this.parent;
if (parent != null)
parent.onAddedChild(child);
}
protected void onRemovedChild(SWGObject child) {
SWGObject parent = this.parent;
if (parent != null)
parent.onRemovedChild(child);
}
public boolean isVisible(SWGObject target) {
if (target == null)
return true;

View File

@@ -38,6 +38,7 @@ import com.projectswg.common.network.packets.swg.zone.object_controller.PostureU
import com.projectswg.holocore.resources.support.data.collections.SWGList;
import com.projectswg.holocore.resources.support.data.collections.SWGSet;
import com.projectswg.holocore.resources.support.global.network.BaselineBuilder;
import com.projectswg.holocore.resources.support.objects.awareness.AwarenessType;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.player.PlayerObject;
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
@@ -56,7 +57,7 @@ public class CreatureObject extends TangibleObject {
private transient long lastReserveOperation = 0;
private final CreatureObjectAwareness awareness = new CreatureObjectAwareness();
private final CreatureObjectAwareness awareness = new CreatureObjectAwareness(this);
private final CreatureObjectClientServerNP creo4 = new CreatureObjectClientServerNP();
private final CreatureObjectSharedNP creo6 = new CreatureObjectSharedNP();
private final Map<CreatureObject, Integer> damageMap = new HashMap<>();
@@ -84,6 +85,7 @@ public class CreatureObject extends TangibleObject {
public CreatureObject(long objectId) {
super(objectId, BaselineType.CREO);
initBaseAttributes();
getAwareness().setAware(AwarenessType.SELF, List.of(this));
}
@Override
@@ -100,7 +102,7 @@ public class CreatureObject extends TangibleObject {
public void flushObjectsAware() {
if (isPlayer())
awareness.flushAware(getOwner());
awareness.flushAware();
}
public void resetObjectsAware() {
@@ -118,6 +120,8 @@ public class CreatureObject extends TangibleObject {
@Override
public void addObject(SWGObject obj) {
if (obj instanceof PlayerObject)
getAware().forEach(awareness::addAware);
super.addObject(obj);
if (obj.getSlotArrangement() != -1 && !(obj instanceof PlayerObject)) {
addEquipment(obj);
@@ -148,6 +152,26 @@ public class CreatureObject extends TangibleObject {
}
}
@Override
protected void onAddedChild(SWGObject child) {
super.onAddedChild(child);
if (isPlayer()) {
List<SWGObject> children = new ArrayList<>(getAwareness().getAware(AwarenessType.SELF));
children.add(child);
getAwareness().setAware(AwarenessType.SELF, children);
}
}
@Override
protected void onRemovedChild(SWGObject child) {
super.onRemovedChild(child);
if (isPlayer()) {
List<SWGObject> children = new ArrayList<>(getAwareness().getAware(AwarenessType.SELF));
children.remove(child);
getAwareness().setAware(AwarenessType.SELF, children);
}
}
@Override
protected int calculateLoadRange() {
if (isLoggedInPlayer())

View File

@@ -42,11 +42,13 @@ import java.util.*;
public class CreatureObjectAwareness {
private final CreatureObject creature;
private final Set<SWGObject> aware;
private final Set<SWGObject> pendingAdd;
private final Set<SWGObject> pendingRemove;
public CreatureObjectAwareness() {
public CreatureObjectAwareness(CreatureObject creature) {
this.creature = creature;
this.aware = new HashSet<>();
this.pendingAdd = new HashSet<>();
this.pendingRemove = new HashSet<>();
@@ -62,6 +64,7 @@ public class CreatureObjectAwareness {
}
public synchronized void removeAware(@NotNull SWGObject obj) {
assert obj != creature;
if (pendingAdd.remove(obj) || !aware.contains(obj))
return;
if (pendingRemove.add(obj)) {
@@ -70,7 +73,8 @@ public class CreatureObjectAwareness {
}
}
public synchronized void flushAware(Player target) {
public synchronized void flushAware() {
Player target = creature.getOwner();
List<SWGObject> create = getCreateList();
List<SWGObject> destroy = getDestroyList();
@@ -92,6 +96,9 @@ public class CreatureObjectAwareness {
}
popStackUntil(target, createStack, null);
}
assert aware.contains(creature) : "not aware of creature";
assert aware.contains(creature.getSlottedObject("ghost")) : "not aware of ghost";
}
public synchronized void resetObjectsAware() {

View File

@@ -105,19 +105,19 @@ public abstract class AIObject extends CreatureObject {
public void addPrimaryWeapon(WeaponObject weapon) {
this.primaryWeapons.add(weapon);
weapon.moveToContainer(hiddenInventory);
weapon.systemMove(hiddenInventory);
}
public void addSecondaryWeapon(WeaponObject weapon) {
this.secondaryWeapons.add(weapon);
weapon.moveToContainer(hiddenInventory);
weapon.systemMove(hiddenInventory);
}
@Override
public void setEquippedWeapon(WeaponObject weapon) {
WeaponObject equipped = getEquippedWeapon();
if (equipped != null)
equipped.moveToContainer(hiddenInventory);
equipped.systemMove(hiddenInventory);
weapon.moveToContainer(this);
super.setEquippedWeapon(weapon);
}

View File

@@ -32,6 +32,8 @@ import com.projectswg.common.data.encodables.tangible.Race;
import com.projectswg.common.data.info.Config;
import com.projectswg.common.network.packets.SWGPacket;
import com.projectswg.common.network.packets.swg.ErrorMessage;
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginRequestPacket;
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginResponsePacket;
import com.projectswg.common.network.packets.swg.login.*;
import com.projectswg.common.network.packets.swg.login.EnumerateCharacterId.SWGCharacter;
import com.projectswg.common.network.packets.swg.login.creation.DeleteCharacterRequest;
@@ -94,7 +96,9 @@ public class LoginService extends Service {
@IntentHandler
private void handleInboundPacketIntent(InboundPacketIntent gpi) {
SWGPacket p = gpi.getPacket();
if (p instanceof LoginClientId) {
if (p instanceof HoloLoginRequestPacket) {
handleLogin(gpi.getPlayer(), (HoloLoginRequestPacket) p);
} else if (p instanceof LoginClientId) {
handleLogin(gpi.getPlayer(), (LoginClientId) p);
} else if (p instanceof DeleteCharacterRequest) {
handleCharDeletion(gpi.getPlayer(), (DeleteCharacterRequest) p);
@@ -112,6 +116,33 @@ public class LoginService extends Service {
return name + ':' + id;
}
private void handleLogin(Player player, HoloLoginRequestPacket loginRequest) {
if (player.getPlayerState() == PlayerState.LOGGED_IN) { // Client occasionally sends multiple login requests
sendLoginSuccessPacket(player);
return;
}
assert player.getPlayerState() == PlayerState.CONNECTED;
assert player.getPlayerServer() == PlayerServer.NONE;
player.setPlayerState(PlayerState.LOGGING_IN);
player.setPlayerServer(PlayerServer.LOGIN);
UserMetadata user = userDatabase.getUser(loginRequest.getUsername());
player.setUsername(loginRequest.getUsername());
if (user == null) {
onInvalidUserPass(player, loginRequest);
player.sendPacket(new HoloLoginResponsePacket(false, "Incorrect username"));
} else if (user.isBanned()) {
onLoginBanned(player, loginRequest);
player.sendPacket(new HoloLoginResponsePacket(false, "Sorry, you're banned!"));
} else if (isUserValid(user, loginRequest.getPassword())) {
onSuccessfulLogin(user, player, loginRequest);
player.sendPacket(new HoloLoginResponsePacket(true, "", getGalaxies(), getCharacters(user.getUsername())));
} else {
onInvalidUserPass(player, loginRequest);
player.sendPacket(new HoloLoginResponsePacket(false, "Incorrect password"));
}
}
private void handleLagRequest(Player player) {
player.sendPacket(new GameServerLagResponse());
}
@@ -142,28 +173,34 @@ public class LoginService extends Service {
onLoginClientVersionError(player, id);
return;
}
UserMetadata user = userDatabase.getUser(id.getUsername());
if (user == null)
onInvalidUserPass(player, id, false);
else if (user.isBanned())
player.setUsername(id.getUsername());
if (user == null) {
onInvalidUserPass(player, id);
player.sendPacket(new ErrorMessage("Login Failed!", "Incorrect username", false));
player.sendPacket(new LoginIncorrectClientId(getServerString(), REQUIRED_VERSION));
} else if (user.isBanned()) {
onLoginBanned(player, id);
else if (isUserValid(user, id.getPassword()))
player.sendPacket(new ErrorMessage("Login Failed!", "Sorry, you're banned!", false));
} else if (isUserValid(user, id.getPassword())) {
onSuccessfulLogin(user, player, id);
else
onInvalidUserPass(player, id, true);
sendLoginSuccessPacket(player);
} else {
onInvalidUserPass(player, id);
player.sendPacket(new ErrorMessage("Login Failed!", "Incorrect password", false));
player.sendPacket(new LoginIncorrectClientId(getServerString(), REQUIRED_VERSION));
}
}
private void onLoginClientVersionError(Player player, LoginClientId id) {
Log.i("%s cannot login due to invalid version code: %s, expected %s from %s", player.getUsername(), id.getVersion(), REQUIRED_VERSION, id.getSocketAddress());
String type = "Login Failed!";
String message = "Invalid Client Version Code: " + id.getVersion();
player.sendPacket(new ErrorMessage(type, message, false));
player.sendPacket(new ErrorMessage("Login Failed!", "Invalid Client Version Code: " + id.getVersion(), false));
player.setPlayerState(PlayerState.DISCONNECTED);
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_INVALID_VERSION_CODE).broadcast();
}
private void onSuccessfulLogin(UserMetadata user, Player player, LoginClientId id) {
player.setUsername(user.getUsername());
private void onSuccessfulLogin(UserMetadata user, Player player, SWGPacket loginRequest) {
switch(user.getAccessLevel()) {
case "player": player.setAccessLevel(AccessLevel.PLAYER); break;
case "warden": player.setAccessLevel(AccessLevel.WARDEN); break;
@@ -173,26 +210,18 @@ public class LoginService extends Service {
default: player.setAccessLevel(AccessLevel.PLAYER); break;
}
player.setPlayerState(PlayerState.LOGGED_IN);
sendLoginSuccessPacket(player);
Log.i("%s connected to the login server from %s", player.getUsername(), id.getSocketAddress());
Log.i("%s connected to the login server from %s", player.getUsername(), loginRequest.getSocketAddress());
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_SUCCESS).broadcast();
}
private void onLoginBanned(Player player, LoginClientId id) {
String type = "Login Failed!";
String message = "Sorry, you're banned!";
player.sendPacket(new ErrorMessage(type, message, false));
Log.i("%s cannot login due to a ban, from %s", player.getUsername(), id.getSocketAddress());
private void onLoginBanned(Player player, SWGPacket loginRequest) {
Log.i("%s cannot login due to a ban, from %s", player.getUsername(), loginRequest.getSocketAddress());
player.setPlayerState(PlayerState.DISCONNECTED);
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_BANNED).broadcast();
}
private void onInvalidUserPass(Player player, LoginClientId id, boolean usernameValid) {
String type = "Login Failed!";
String message = usernameValid ? "Incorrect password" : "Incorrect username";
player.sendPacket(new ErrorMessage(type, message, false));
player.sendPacket(new LoginIncorrectClientId(getServerString(), REQUIRED_VERSION));
Log.i("%s cannot login due to invalid user/pass from %s", id.getUsername(), id.getSocketAddress());
private void onInvalidUserPass(Player player, SWGPacket loginRequest) {
Log.i("%s cannot login due to invalid user/pass from %s", player.getUsername(), loginRequest.getSocketAddress());
player.setPlayerState(PlayerState.DISCONNECTED);
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_INVALID_USER_PASS).broadcast();
}

View File

@@ -56,6 +56,7 @@ import me.joshlarson.jlcommon.log.Log;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

View File

@@ -66,6 +66,7 @@ import me.joshlarson.jlcommon.control.Service;
import me.joshlarson.jlcommon.log.Log;
import java.util.Collections;
import java.util.List;
public class AwarenessService extends Service {
@@ -184,11 +185,13 @@ public class AwarenessService extends Service {
}
synchronized (creature.getAwarenessLock()) {
creature.systemMove(parent, loc);
// Safely clear awareness
creature.setOwner(null);
creature.setAware(AwarenessType.OBJECT, Collections.emptyList());
creature.setAware(AwarenessType.OBJECT, List.of());
creature.resetObjectsAware();
creature.setOwner(player);
creature.systemMove(parent, loc);
startZone(creature, firstZone);
creature.addObjectsAware();
awareness.updateObject(creature);

View File

@@ -0,0 +1,48 @@
package com.projectswg.holocore.utilities;
import me.joshlarson.jlcommon.log.Log;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PerformanceAnalyzer {
private static final Map<String, PerformanceAnalyzer> ANALYZERS = new ConcurrentHashMap<>();
private final long [] previousRuns;
private final String name;
private int index;
private PerformanceAnalyzer(String name, int outputFrequency) {
this.previousRuns = new long[outputFrequency];
this.name = name;
this.index = 0;
}
public synchronized void recordTime(long time) {
previousRuns[index++] = time;
if (index >= previousRuns.length) {
long min = Long.MAX_VALUE, max = Long.MIN_VALUE, avg = 0;
for (long run : previousRuns) {
avg += run;
if (run < min)
min = run;
if (run > max)
max = run;
}
avg /= previousRuns.length;
Log.d("%s: [%.3fms - %.3fms] Avg: %.3fms", this, min/1E6, max/1E6, avg/1E6);
index = 0;
}
}
@Override
public String toString() {
return "PerformanceAnalyzer["+name+']';
}
public static PerformanceAnalyzer getAnalyzer(String name, int outputFrequency) {
return ANALYZERS.computeIfAbsent(name, n -> new PerformanceAnalyzer(n, outputFrequency));
}
}

View File

@@ -37,8 +37,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
@@ -57,7 +56,7 @@ public class TestObjectAware extends TestRunnerNoIntents {
tangible2 = new GenericTangibleObject(2);
creature1 = new GenericCreatureObject(3);
creature2 = new GenericCreatureObject(4);
assertNotObservingSelf();
assertObservingSelf();
assertFalse(tangible1.getAware().contains(tangible2));
assertFalse(tangible2.getAware().contains(tangible1));
assertFalse(creature1.getObservers().contains(creature2.getOwner()));
@@ -73,29 +72,26 @@ public class TestObjectAware extends TestRunnerNoIntents {
@Override public void onObjectEnterAware(SWGObject aware) { onEnter.incrementAndGet(); }
@Override public void onObjectLeaveAware(SWGObject aware) { onLeave.incrementAndGet(); }
};
test.setSlot("ghost", ghost);
ghost.systemMove(test);
onEnter.set(0);
tangible1.setAware(AwarenessType.OBJECT, Arrays.asList(test, ghost));
tangible1.setAware(AwarenessType.OBJECT, List.of(test, ghost));
assertEquals(1, onEnter.get());
tangible1.setAware(AwarenessType.OBJECT, Collections.emptyList());
tangible1.setAware(AwarenessType.OBJECT, List.of());
assertEquals(1, onEnter.get());
assertEquals(1, onLeave.get());
onEnter.set(0);
onLeave.set(0);
test.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible1));
ghost.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible1));
test.setAware(AwarenessType.OBJECT, List.of(tangible1));
ghost.setAware(AwarenessType.OBJECT, List.of(tangible1));
assertEquals(1, onEnter.get());
test.setAware(AwarenessType.OBJECT, Collections.emptyList());
ghost.setAware(AwarenessType.OBJECT, Collections.emptyList());
assertEquals(1, onEnter.get());
assertEquals(1, onLeave.get());
}
@Test
public void testSingleBucketAware() {
tangible1.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible2));
tangible1.setAware(AwarenessType.OBJECT, List.of(tangible2));
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
assertTrue(tangible1.getAware(AwarenessType.OBJECT).contains(tangible2));
@@ -103,7 +99,7 @@ public class TestObjectAware extends TestRunnerNoIntents {
assertFalse(tangible1.getAware(AwarenessType.GROUP).contains(tangible2));
assertFalse(tangible2.getAware(AwarenessType.GROUP).contains(tangible1));
tangible1.setAware(AwarenessType.OBJECT, Collections.emptyList());
tangible1.setAware(AwarenessType.OBJECT, List.of());
assertFalse(tangible1.getAware().contains(tangible2));
assertFalse(tangible2.getAware().contains(tangible1));
assertFalse(tangible1.getAware(AwarenessType.OBJECT).contains(tangible2));
@@ -112,59 +108,59 @@ public class TestObjectAware extends TestRunnerNoIntents {
assertFalse(tangible2.getAware(AwarenessType.GROUP).contains(tangible1));
// Add both directions
tangible1.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible2));
tangible1.setAware(AwarenessType.OBJECT, List.of(tangible2));
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
tangible2.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible1));
tangible2.setAware(AwarenessType.OBJECT, List.of(tangible1));
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
tangible1.setAware(AwarenessType.OBJECT, Collections.emptyList());
tangible1.setAware(AwarenessType.OBJECT, List.of());
assertFalse(tangible1.getAware().contains(tangible2));
assertFalse(tangible2.getAware().contains(tangible1));
}
@Test
public void testSingleBucketObservers() {
creature1.setAware(AwarenessType.OBJECT, Collections.singletonList(creature2));
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of(creature2));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature1.setAware(AwarenessType.OBJECT, Collections.emptyList());
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of());
assertObservingSelf();
assertFalse(creature1.getObservers().contains(creature2.getOwner()));
assertFalse(creature2.getObservers().contains(creature1.getOwner()));
// Add both directions
creature1.setAware(AwarenessType.OBJECT, Collections.singletonList(creature2));
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of(creature2));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature2.setAware(AwarenessType.OBJECT, Collections.singletonList(creature1));
assertNotObservingSelf();
creature2.setAware(AwarenessType.OBJECT, List.of(creature1));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature1.setAware(AwarenessType.OBJECT, Collections.emptyList());
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of());
assertObservingSelf();
assertFalse(creature1.getObservers().contains(creature2.getOwner()));
assertFalse(creature2.getObservers().contains(creature1.getOwner()));
}
@Test
public void testDoubleBucketAware() {
tangible1.setAware(AwarenessType.OBJECT, Collections.singletonList(tangible2));
tangible1.setAware(AwarenessType.OBJECT, List.of(tangible2));
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
tangible1.setAware(AwarenessType.GROUP, Collections.singletonList(tangible2));
tangible1.setAware(AwarenessType.GROUP, List.of(tangible2));
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
tangible1.setAware(AwarenessType.OBJECT, Collections.emptyList());
tangible1.setAware(AwarenessType.OBJECT, List.of());
assertTrue(tangible1.getAware().contains(tangible2));
assertTrue(tangible2.getAware().contains(tangible1));
assertFalse(tangible1.getAware(AwarenessType.OBJECT).contains(tangible2));
@@ -172,7 +168,7 @@ public class TestObjectAware extends TestRunnerNoIntents {
assertTrue(tangible1.getAware(AwarenessType.GROUP).contains(tangible2));
assertTrue(tangible2.getAware(AwarenessType.GROUP).contains(tangible1));
tangible2.setAware(AwarenessType.GROUP, Collections.emptyList());
tangible2.setAware(AwarenessType.GROUP, List.of());
assertFalse(tangible1.getAware().contains(tangible2));
assertFalse(tangible2.getAware().contains(tangible1));
assertFalse(tangible1.getAware(AwarenessType.OBJECT).contains(tangible2));
@@ -183,49 +179,49 @@ public class TestObjectAware extends TestRunnerNoIntents {
@Test
public void testDoubleBucketObservers() {
creature1.setAware(AwarenessType.OBJECT, Collections.singletonList(creature2));
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of(creature2));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature1.setAware(AwarenessType.GROUP, Collections.singletonList(creature2));
assertNotObservingSelf();
creature1.setAware(AwarenessType.GROUP, List.of(creature2));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature1.setAware(AwarenessType.OBJECT, Collections.emptyList());
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of());
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature2.setAware(AwarenessType.GROUP, Collections.emptyList());
assertNotObservingSelf();
creature2.setAware(AwarenessType.GROUP, List.of());
assertObservingSelf();
assertFalse(creature1.getObservers().contains(creature2.getOwner()));
assertFalse(creature2.getObservers().contains(creature1.getOwner()));
// Add both directions
creature1.setAware(AwarenessType.OBJECT, Collections.singletonList(creature2));
assertNotObservingSelf();
creature1.setAware(AwarenessType.OBJECT, List.of(creature2));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature2.setAware(AwarenessType.GROUP, Collections.singletonList(creature1));
assertNotObservingSelf();
creature2.setAware(AwarenessType.GROUP, List.of(creature1));
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature2.setAware(AwarenessType.OBJECT, Collections.emptyList());
assertNotObservingSelf();
creature2.setAware(AwarenessType.OBJECT, List.of());
assertObservingSelf();
assertTrue(creature1.getObservers().contains(creature2.getOwner()));
assertTrue(creature2.getObservers().contains(creature1.getOwner()));
creature1.setAware(AwarenessType.GROUP, Collections.emptyList());
assertNotObservingSelf();
creature1.setAware(AwarenessType.GROUP, List.of());
assertObservingSelf();
assertFalse(creature1.getObservers().contains(creature2.getOwner()));
assertFalse(creature2.getObservers().contains(creature1.getOwner()));
}
private void assertNotObservingSelf() {
assertFalse(creature1.getObservers().contains(creature1.getOwner()));
assertFalse(creature2.getObservers().contains(creature2.getOwner()));
private void assertObservingSelf() {
assertTrue(creature1.getObservers().contains(creature1.getOwner()));
assertTrue(creature2.getObservers().contains(creature2.getOwner()));
}
}

View File

@@ -29,13 +29,13 @@ package com.projectswg.holocore.resources.support.objects.awareness;
import com.projectswg.common.data.location.Location;
import com.projectswg.common.data.location.Terrain;
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.building.BuildingObject;
import com.projectswg.holocore.resources.support.objects.swg.cell.CellObject;
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
import com.projectswg.holocore.resources.support.objects.swg.waypoint.WaypointObject;
import com.projectswg.holocore.runners.TestRunnerNoIntents;
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
import com.projectswg.holocore.test_resources.GenericCreatureObject;
import com.projectswg.holocore.test_resources.GenericTangibleObject;
import org.junit.Assert;
@@ -43,9 +43,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(JUnit4.class)
public class TestObjectAwareness extends TestRunnerNoIntents {
@@ -86,8 +89,10 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
testCell1.moveToContainer(testBuilding1);
testCell2.moveToContainer(testBuilding2);
inventoryObject.moveToContainer(player.getSlottedObject("inventory"));
testInventoryObject.moveToContainer(testPlayer.getSlottedObject("inventory"));
inventoryObject.setArrangement(List.of(List.of("inventory")));
testInventoryObject.setArrangement(List.of(List.of("inventory")));
inventoryObject.moveToContainer(player);
testInventoryObject.moveToContainer(testPlayer);
testPlayer.setLocation(buildTatooine(40, 40));
testTangible.setLocation(buildTatooine(50, 50));
@@ -134,7 +139,7 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
Assert.assertEquals(0, player.getLoadRange());
moveNoAssert(TestLocation.SSI);
assertAware(Collections.singletonList(player));
assertAware(List.of(player));
player.setHasOwner(true);
Assert.assertNotEquals(0, player.getLoadRange());
@@ -157,7 +162,7 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
initialize();
move(TestLocation.SSI);
awareness.destroyObject(testBuilding1);
assertAware(Arrays.asList(player, testPlayer, testTangible));
assertAware(List.of(player, testPlayer, testTangible));
}
@Test
@@ -184,7 +189,7 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
}
private void assertAware(Collection<SWGObject> awareExpected) {
Collection<SWGObject> awareActual = player.getObjectsAware();
Collection<SWGObject> awareActual = player.getAware();
// Ensure it doesn't contain the unexpected
for (SWGObject a : awareActual) {
@@ -192,8 +197,6 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
continue;
assertTrue("Not supposed to be aware of object: " + a, awareExpected.contains(a));
}
assertFalse("Test inventory object should not be visible", awareActual.contains(testInventoryObject));
assertTrue("Inventory object should always be visible", awareActual.contains(inventoryObject));
// Ensure it contains the expected
for (SWGObject a : awareExpected) {
@@ -212,9 +215,9 @@ public class TestObjectAwareness extends TestRunnerNoIntents {
private Collection<SWGObject> getExpectedAware(TestAwareSet awareSet) {
switch (awareSet) {
case NONE: return Collections.singletonList(player);
case TATOOINE: return Arrays.asList(player, testPlayer, testTangible, testBuilding1, testPlayer.getSlottedObject("ghost"));
case NABOO: return Arrays.asList(player, testBuilding2);
case NONE: return List.of(player, inventoryObject);
case TATOOINE: return List.of(player, inventoryObject, testPlayer, testTangible, testBuilding1, testPlayer.getSlottedObject("ghost"));
case NABOO: return List.of(player, inventoryObject, testBuilding2);
}
throw new RuntimeException("Invalid test aware set: " + awareSet);
}

View File

@@ -30,6 +30,7 @@ package com.projectswg.holocore.resources.support.objects.swg.creature;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
import com.projectswg.holocore.resources.support.global.player.Player;
import com.projectswg.holocore.test_resources.GenericCreatureObject;
import com.projectswg.holocore.test_resources.GenericTangibleObject;
import org.junit.Assert;
import org.junit.Before;
@@ -41,16 +42,18 @@ import org.junit.runners.JUnit4;
public class TestCreatureObjectAwareness {
private CreatureObjectAwareness awareness;
private CreatureObject creature;
private TangibleObject testObject1;
private TangibleObject testObject2;
private Player dummy;
@Before
public void initialize() {
awareness = new CreatureObjectAwareness();
testObject1 = new GenericTangibleObject(1);
testObject2 = new GenericTangibleObject(2);
dummy = new Player();
creature = new GenericCreatureObject(1);
creature.setOwner(new Player());
awareness = new CreatureObjectAwareness(creature);
testObject1 = new GenericTangibleObject(2);
testObject2 = new GenericTangibleObject(3);
}
@Test
@@ -69,7 +72,7 @@ public class TestCreatureObjectAwareness {
@Test
public void testSingleObjectAddFlushRemove() {
awareness.addAware(testObject1);
awareness.flushAware(dummy);
awareness.flushAware();
assertCreate();
assertDestroy();
awareness.removeAware(testObject1);
@@ -88,7 +91,7 @@ public class TestCreatureObjectAwareness {
testObject1.moveToContainer(testObject2);
awareness.addAware(testObject1);
awareness.addAware(testObject2);
awareness.flushAware(dummy);
awareness.flushAware();
assertCreate();
assertDestroy();
awareness.removeAware(testObject1);
@@ -108,7 +111,7 @@ public class TestCreatureObjectAwareness {
testObject1.moveToContainer(testObject2);
awareness.addAware(testObject1);
awareness.addAware(testObject2);
awareness.flushAware(dummy);
awareness.flushAware();
assertCreate();
assertDestroy();
awareness.removeAware(testObject1);
@@ -121,11 +124,11 @@ public class TestCreatureObjectAwareness {
testObject1.moveToContainer(testObject2);
awareness.addAware(testObject1);
awareness.addAware(testObject2);
awareness.flushAware(dummy);
awareness.flushAware();
assertCreate();
assertDestroy();
awareness.removeAware(testObject2);
awareness.flushAware(dummy);
awareness.flushAware();
awareness.removeAware(testObject1);
assertDestroy();
}

View File

@@ -37,7 +37,6 @@ import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureOb
import com.projectswg.holocore.resources.support.objects.swg.player.PlayerObject;
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
@@ -78,11 +77,11 @@ public class GenericCreatureObject extends CreatureObject {
public void setupAsCharacter() {
setSlots(List.of("inventory", "datapad", "hangar", "default_weapon", "mission_bag", "hat", "hair", "earring_r", "earring_l", "eyes", "mouth", "neck", "cloak", "back", "chest1", "chest2", "chest3_r", "chest3_l", "bicep_r", "bicep_l", "bracer_lower_r", "bracer_upper_r", "bracer_lower_l", "bracer_upper_l", "wrist_r", "wrist_l", "gloves", "hold_r", "hold_l", "ring_r", "ring_l", "utility_belt", "pants1", "pants2", "shoes", "ghost", "bank", "appearance_inventory", "cybernetic_hand_l", "cybernetic_hand_r"));
setArrangement(Collections.singletonList(Collections.singletonList("rider")));
setArrangement(List.of(List.of("rider")));
setGameObjectType(GameObjectType.GOT_CREATURE_CHARACTER);
PlayerObject playerObject = new PlayerObject(-getObjectId());
playerObject.setArrangement(Collections.singletonList(Collections.singletonList("ghost")));
playerObject.setArrangement(List.of(List.of("ghost")));
playerObject.moveToContainer(this);
createInventoryObject("inventory");
createInventoryObject("datapad");
@@ -100,7 +99,7 @@ public class GenericCreatureObject extends CreatureObject {
private void createInventoryObject(String slot) {
SWGObject obj = new TangibleObject(GENERATED_IDS.incrementAndGet());
obj.setArrangement(Collections.singletonList(Collections.singletonList(slot)));
obj.setArrangement(List.of(List.of(slot)));
obj.setContainerPermissions(ContainerPermissionsType.INVENTORY);
obj.moveToContainer(this);
}