mirror of
https://bitbucket.org/projectswg/holocore.git
synced 2026-01-15 22:04:17 -05:00
Added integration tests and improved awareness speed
This commit is contained in:
@@ -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
12
client-holocore/.gitignore
vendored
Normal 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
3
client-holocore/.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "pswgcommon"]
|
||||
path = pswgcommon
|
||||
url = git@bitbucket.org:projectswg/pswgcommon.git
|
||||
16
client-holocore/build.gradle
Normal file
16
client-holocore/build.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
sourceCompatibility = 9
|
||||
targetCompatibility = 9
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':pswgcommon')
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
BIN
client-holocore/lib/lz4-1.3.0.jar
Normal file
BIN
client-holocore/lib/lz4-1.3.0.jar
Normal file
Binary file not shown.
1
client-holocore/settings.gradle
Normal file
1
client-holocore/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':pswgcommon'
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.projectswg.connection;
|
||||
|
||||
public enum ServerConnectionStatus {
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTED
|
||||
}
|
||||
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
Submodule pswgcommon updated: 90119697d1...454e995ad3
@@ -1,2 +1,3 @@
|
||||
rootProject.name = 'holocore'
|
||||
include 'pswgcommon'
|
||||
include ':pswgcommon'
|
||||
include ':client-holocore'
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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!");
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public enum AwarenessType {
|
||||
SELF,
|
||||
OBJECT,
|
||||
GROUP,
|
||||
TRADE;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user