diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java index 22d61c5..fd8f86a 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java @@ -67,11 +67,14 @@ public class ArkOneController implements TcpHandler { // Parse the incoming data into individual commands List commands = ArkOneParser.ParseReceivedMessage(xmlData); - // Parse out the plugin from the routing string (if it exists) - String[] routingString = ArkOneParser.ParseRoutingStrings(xmlData); - // Handle each command for (String command : commands) { + // Remove the null character from the end if it exists + command = command.replace("\0", ""); + + // Parse out the plugin from the routing string (if it exists) + List routingString = ArkOneParser.ParseRoutingStrings(command); + try { Element commandInfo = (Element)ArkOneParser.ParseCommand(command); switch(commandInfo.getNodeName()) { @@ -210,30 +213,41 @@ public class ArkOneController implements TcpHandler { responses.add(multiplayerPlugin.ReadyPlay()); break; case "ms": - responses.add(multiplayerPlugin.MessageOpponent(commandInfo, connection, routingString[1])); + responses.add(multiplayerPlugin.MessageOpponent(commandInfo, connection, routingString.get(1))); break; case "pa": responses.add(multiplayerPlugin.PlayAgain()); // ---------------------------- Conflict Commands --------------------------- \\ case "jn": - if (routingString[1].equals("2")) { - //TODO: IMPLEMENT CHAT - For now throw unhandled - responses.add(""); - System.out.println("[ArkOne][ERROR] Unhandled command: " + commandInfo.getNodeName()); - //responses.add(chatPlugin.JoinChat()); - } else { - responses.add(multiplayerPlugin.JoinGame()); + switch(routingString.get(1)) { + case "2": + //TODO: IMPLEMENT CHAT - For now throw unhandled + responses.add(""); + System.out.println("[ArkOne][ERROR] Unhandled command: " + commandInfo.getNodeName()); + //responses.add(chatPlugin.JoinChat()); + break; + case "5": + responses.add(rainbowShootoutPlugin.JoinGame(commandInfo, connection)); + break; + default: + responses.add(""); + System.out.println("[ArkOne][Error] Unhandled 'jn' route to plugin: " + routingString.get(1)); + break; } break; case "sp": - switch(routingString[1]) { + switch(routingString.get(1)) { case "5": responses.add(rainbowShootoutPlugin.ShotParameters(commandInfo)); break; case "7": responses.add(galaxyPlugin.SaveProfile(commandInfo, connection)); break; + default: + responses.add(""); + System.out.println("[ArkOne][Error] Unhandled 'sp' route to plugin: " + routingString.get(1)); + break; } break; diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java index 177c6ea..7175701 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java @@ -97,7 +97,19 @@ public class ArkOneParser { return null; } - public static String[] ParseRoutingStrings(String command) { + public static List ParseRoutingStrings(String command) { + List routeInfo = new ArrayList<>(); + String routingString = ""; + if (command.endsWith("#")) { + routingString = command.substring(command.lastIndexOf(">") + 1, command.lastIndexOf("#")); + } + + if (!routingString.equals("")) { + String[] routingData = routingString.split("\\|"); + routeInfo.addAll(Arrays.asList(routingData)); + } + + return routeInfo; } } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/Multiplayer/RainbowShootoutPlugin.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/Multiplayer/RainbowShootoutPlugin.java index 9111fe0..0c26d5a 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/Multiplayer/RainbowShootoutPlugin.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/Multiplayer/RainbowShootoutPlugin.java @@ -2,8 +2,11 @@ package com.icedberries.UBFunkeysServer.ArkOne.Plugins.Multiplayer; import com.icedberries.UBFunkeysServer.ArkOne.ArkOneParser; import com.icedberries.UBFunkeysServer.ArkOne.ArkOneSender; +import com.icedberries.UBFunkeysServer.domain.Multiplayer.RainbowShootout; import com.icedberries.UBFunkeysServer.domain.User; +import com.icedberries.UBFunkeysServer.service.RainbowShootoutService; import com.icedberries.UBFunkeysServer.service.UserService; +import javagrinko.spring.tcp.Connection; import javagrinko.spring.tcp.Server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -14,6 +17,10 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.TimeUnit; @Service public class RainbowShootoutPlugin { @@ -25,7 +32,219 @@ public class RainbowShootoutPlugin { UserService userService; @Autowired - private ArkOneSender arkOneSender; + ArkOneSender arkOneSender; + + @Autowired + RainbowShootoutService rainbowShootoutService; + + public String JoinGame(Element element, Connection connection) + throws InterruptedException, ParserConfigurationException, TransformerException { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + boolean exists = true; + int challenge = 0; + int challenger = 0; + connection.setTeamSide(5); + + String c = element.getAttribute("c"); + String pr = element.getAttribute("pr"); + + // See if there is an existing entry for this user in this table + User thisUser = server.getConnectedUsers().get(connection.getClientIdentifier()); + RainbowShootout rainbowShootout = rainbowShootoutService.findByUserId(thisUser.getUUID()).orElse(null); + + // If exists, grab the details or set to not exist yet + if (rainbowShootout != null) { + challenge = rainbowShootout.getChallenge(); + challenger = rainbowShootout.getChallenger(); + } else { + exists = false; + } + + // Insert a new matchmaking entry + if (!exists || challenger == 0) { + // Build the new entry + RainbowShootout newRS = RainbowShootout.builder() + .username(thisUser.getUsername()) + .userId(thisUser.getUUID()) + .challenge(Integer.valueOf(c)) + .playerInfo(pr) + .ready(0) + .score(0) + .build(); + + // Can't be set as part of the builder + newRS.setConnectionId(thisUser.getConnectionId()); + + // Save to the DB + rainbowShootoutService.save(newRS); + } + + // Random Matchmaking + if (c.equals("0") && challenge != 1) { + String opponentName = ""; + String opponentInfo = ""; + Integer isPlayerFound = 0; + int i = 0; + + // Time to look for a match + while (i < 30) { + // Get open players + List openPlayers = rainbowShootoutService.findOtherOpenPlayers(thisUser.getUUID()); + if (openPlayers.size() > 0) { + // Get a random open one + Random rand = new Random(); + RainbowShootout randomElement = openPlayers.get(rand.nextInt(openPlayers.size())); + + // Save information about the opponent + connection.setOpponentUID(randomElement.getUserId()); + connection.setOpponentConID(randomElement.getConnectionId()); + opponentName = randomElement.getUsername(); + opponentInfo = randomElement.getPlayerInfo(); + + // Get my data + RainbowShootout myRS = rainbowShootoutService.findByUserId(thisUser.getUUID()).orElse(null); + if (myRS != null) { + isPlayerFound = myRS.getChallenge(); + } + } + + // See if we found an opponent + if (connection.getOpponentConIDAsString().equals("") && isPlayerFound == 0) { + TimeUnit.SECONDS.sleep(1); + i++; + } else { + i = 30; + } + } + + // If player found an opponent + if (!connection.getOpponentConIDAsString().equals("")) { + // Update the matchmaking entries to reflect the found match + RainbowShootout myRS = rainbowShootoutService.findByUserId(thisUser.getUUID()).orElse(null); + RainbowShootout oppRS = rainbowShootoutService.findByUserId(connection.getOpponentUID()).orElse(null); + + if (myRS != null) { + myRS.setChallenger(connection.getOpponentUID()); + } + if (oppRS != null) { + oppRS.setChallenger(thisUser.getUUID()); + } + rainbowShootoutService.save(myRS); + rainbowShootoutService.save(oppRS); + + // Build the response to send to the opponent + Document resp1 = dBuilder.newDocument(); + Element rootElement = resp1.createElement("h5_0"); + resp1.appendChild(rootElement); + Element ojElement = resp1.createElement("oj"); + ojElement.setAttribute("n", thisUser.getUsername()); + ojElement.setAttribute("pr", pr); + rootElement.appendChild(ojElement); + + arkOneSender.SendToUser(connection.getOpponentConIDAsUUID(), ArkOneParser.RemoveXMLTag(resp1)); + + // Build the response to this user + Document resp2 = dBuilder.newDocument(); + Element rootElement2 = resp2.createElement("h5_0"); + resp2.appendChild(rootElement2); + Element jnElement = resp2.createElement("jn"); + jnElement.setAttribute("r", "0"); + rootElement2.appendChild(jnElement); + Element ojElement2 = resp2.createElement("oj"); + ojElement2.setAttribute("n", opponentName); + ojElement2.setAttribute("pr", opponentInfo); + rootElement2.appendChild(ojElement2); + + return ArkOneParser.RemoveXMLTag(resp2); + } + // If found by another player + else if (isPlayerFound == 1) { + RainbowShootout myRS = rainbowShootoutService.findByUserId(thisUser.getUUID()).orElse(null); + if (myRS != null) { + connection.setOpponentUID(myRS.getChallenger()); + + RainbowShootout oppRS = rainbowShootoutService.findByUserId(connection.getOpponentUID()).orElse(null); + + if (oppRS != null) { + connection.setOpponentConID(oppRS.getConnectionId()); + } + } + + return ""; + } + // Matchmaking timed out + else { + return ""; + } + } + + // If joining from invite + if (challenge == 1) { + String conID = ""; + String opponentName = ""; + String opponentInfo = ""; + + User opponent = userService.findByUUID(challenger).orElse(null); + if (opponent != null) { + opponentName = opponent.getUsername(); + if (opponent.getIsOnline() == 1) { + conID = opponent.getConnectionId().toString(); + } + } + + RainbowShootout myRS = rainbowShootoutService.findByUserId(thisUser.getUUID()).orElse(null); + if (myRS != null) { + opponentInfo = myRS.getChallengerInfo(); + + RainbowShootout oppRS = rainbowShootoutService.findByUserId(challenger).orElse(null); + if (oppRS != null) { + oppRS.setChallengerInfo(pr); + myRS.setConnectionId(connection.getClientIdentifier()); + + rainbowShootoutService.save(oppRS); + rainbowShootoutService.save(myRS); + } + } + + connection.setOpponentConID(UUID.fromString(conID)); + connection.setOpponentUID(challenger); + + // Build the response to send to the opponent + Document resp1 = dBuilder.newDocument(); + Element rootElement = resp1.createElement("h5_0"); + resp1.appendChild(rootElement); + Element ojElement = resp1.createElement("oj"); + ojElement.setAttribute("n", thisUser.getUsername()); + ojElement.setAttribute("pr", pr); + rootElement.appendChild(ojElement); + + arkOneSender.SendToUser(connection.getOpponentConIDAsUUID(), ArkOneParser.RemoveXMLTag(resp1)); + + // Build the response to this user + Document resp2 = dBuilder.newDocument(); + Element rootElement2 = resp2.createElement("h5_0"); + resp2.appendChild(rootElement2); + Element jnElement = resp2.createElement("jn"); + jnElement.setAttribute("r", "0"); + rootElement2.appendChild(jnElement); + Element ojElement2 = resp2.createElement("oj"); + ojElement2.setAttribute("n", opponentName); + ojElement2.setAttribute("pr", opponentInfo); + rootElement2.appendChild(ojElement2); + + return ArkOneParser.RemoveXMLTag(resp2); + } + + Document resp = dBuilder.newDocument(); + Element rootElement = resp.createElement("h5_0"); + resp.appendChild(rootElement); + Element jnElement = resp.createElement("jn"); + jnElement.setAttribute("r", "0"); + rootElement.appendChild(jnElement); + return ArkOneParser.RemoveXMLTag(resp); + } public String ShotParameters(Element element) throws ParserConfigurationException, TransformerException { diff --git a/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java b/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java index a9b748e..1a72f6f 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java @@ -439,6 +439,7 @@ public class GalaxyServer { .sharedDate(LocalDateTime.now()) .imagePath(tnurl) .rating(0) + .ratingCount(0) .playCount(0) .pos(0) .build(); diff --git a/src/main/java/com/icedberries/UBFunkeysServer/config/DatabaseCleanup.java b/src/main/java/com/icedberries/UBFunkeysServer/config/DatabaseCleanup.java index 9703899..4e22934 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/config/DatabaseCleanup.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/config/DatabaseCleanup.java @@ -4,6 +4,7 @@ import com.icedberries.UBFunkeysServer.domain.Multiplayer.RainbowShootout; import com.icedberries.UBFunkeysServer.domain.User; import com.icedberries.UBFunkeysServer.service.RainbowShootoutService; import com.icedberries.UBFunkeysServer.service.UserService; +import javagrinko.spring.tcp.Server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @@ -17,6 +18,9 @@ import java.util.List; @Component public class DatabaseCleanup { + @Autowired + Server server; + @Autowired UserService userService; @@ -49,7 +53,7 @@ public class DatabaseCleanup { // Calculate how many milliseconds since last ping/login long difference = Math.abs(Duration.between(user.getLastPing(), LocalDateTime.now()).toMillis()); if (difference > 60000) { - // USer has been online for more than 60 seconds without a new ping - set them offline + // User has been online for more than 60 seconds without a new ping - set them offline user.setIsOnline(0); userService.save(user); } @@ -67,10 +71,14 @@ public class DatabaseCleanup { public void clearOpenMultiplayerMatchmaking() { // Get all open multiplayer matchmaking entries in all tables List openRainbowShootout = rainbowShootoutService.findAll(); + List onlineUsers = userService.getOnlineUsers(); // Iterate over each game's entries for (RainbowShootout rainbowShootout : openRainbowShootout) { - rainbowShootoutService.delete(rainbowShootout); + User rsUser = userService.findByUUID(rainbowShootout.getUserId()).orElse(null); + if (rsUser == null || !onlineUsers.contains(rsUser)) { + rainbowShootoutService.delete(rainbowShootout); + } } } } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/domain/Level.java b/src/main/java/com/icedberries/UBFunkeysServer/domain/Level.java index 884b644..71186a5 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/domain/Level.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/domain/Level.java @@ -36,9 +36,20 @@ public class Level { private String imagePath; // 0 -> 5 "stars" - private Integer rating; + private double rating; + + private Integer ratingCount; private Integer playCount; private Integer pos; + + public Integer getRating() { + if (rating <= 0) { + return 0; + } + + double average = rating / ratingCount; + return (int)Math.rint(average); + } } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/domain/Multiplayer/RainbowShootout.java b/src/main/java/com/icedberries/UBFunkeysServer/domain/Multiplayer/RainbowShootout.java index d770ddd..1b83a53 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/domain/Multiplayer/RainbowShootout.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/domain/Multiplayer/RainbowShootout.java @@ -43,7 +43,7 @@ public class RainbowShootout { private Integer score; // If the user is ready to start the round - private String ready; + private Integer ready; public java.util.UUID getConnectionId() { return java.util.UUID.fromString(connectionId); diff --git a/src/main/java/com/icedberries/UBFunkeysServer/repository/RainbowShootoutRepository.java b/src/main/java/com/icedberries/UBFunkeysServer/repository/RainbowShootoutRepository.java index ec7da6e..9aebab9 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/repository/RainbowShootoutRepository.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/repository/RainbowShootoutRepository.java @@ -1,15 +1,23 @@ package com.icedberries.UBFunkeysServer.repository; import com.icedberries.UBFunkeysServer.domain.Multiplayer.RainbowShootout; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface RainbowShootoutRepository extends CrudRepository { List findAll(); + @Query("select rainbowShootout from RainbowShootout rainbowShootout where rainbowShootout.userId = :uuid") + Optional findByUserId(@Param("uuid") Integer uuid); + @Query("select rainbowShootout from RainbowShootout rainbowShootout" + + " where rainbowShootout.challenge = 0 and not rainbowShootout.userId = :uuid") + List findOtherOpenPlayers(@Param("uuid") Integer uuid); } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/service/RainbowShootoutService.java b/src/main/java/com/icedberries/UBFunkeysServer/service/RainbowShootoutService.java index ce0844c..658c268 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/service/RainbowShootoutService.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/service/RainbowShootoutService.java @@ -3,10 +3,17 @@ package com.icedberries.UBFunkeysServer.service; import com.icedberries.UBFunkeysServer.domain.Multiplayer.RainbowShootout; import java.util.List; +import java.util.Optional; public interface RainbowShootoutService { List findAll(); void delete(RainbowShootout rainbowShootout); + + Optional findByUserId(Integer userId); + + RainbowShootout save(RainbowShootout rainbowShootout); + + List findOtherOpenPlayers(Integer uuid); } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/service/impl/RainbowShootoutServiceImpl.java b/src/main/java/com/icedberries/UBFunkeysServer/service/impl/RainbowShootoutServiceImpl.java index 9453e34..fb7c8f4 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/service/impl/RainbowShootoutServiceImpl.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/service/impl/RainbowShootoutServiceImpl.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -23,4 +24,19 @@ public class RainbowShootoutServiceImpl implements RainbowShootoutService { public void delete(RainbowShootout rainbowShootout) { rainbowShootoutRepository.delete(rainbowShootout); } + + @Override + public Optional findByUserId(Integer userId) { + return rainbowShootoutRepository.findByUserId(userId); + } + + @Override + public RainbowShootout save(RainbowShootout rainbowShootout) { + return rainbowShootoutRepository.save(rainbowShootout); + } + + @Override + public List findOtherOpenPlayers(Integer uuid) { + return rainbowShootoutRepository.findOtherOpenPlayers(uuid); + } } diff --git a/src/main/java/javagrinko/spring/tcp/Connection.java b/src/main/java/javagrinko/spring/tcp/Connection.java index ebd8604..3396c76 100644 --- a/src/main/java/javagrinko/spring/tcp/Connection.java +++ b/src/main/java/javagrinko/spring/tcp/Connection.java @@ -3,6 +3,7 @@ package javagrinko.spring.tcp; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; +import java.util.HashMap; import java.util.UUID; public interface Connection { @@ -15,9 +16,15 @@ public interface Connection { void setClientIdentifier(UUID newId); Integer getChunksLeft(); void setChunksLeft(Integer chunksLeft); - + Integer getTeamSide(); + void setTeamSide(Integer teamSide); String getSaveData(); void setSaveData(String saveData); + void setOpponentUID(Integer opponentUID); + Integer getOpponentUID(); + void setOpponentConID(UUID opponentConID); + String getOpponentConIDAsString(); + UUID getOpponentConIDAsUUID(); interface Listener { void messageReceived(Connection connection, byte[] bytes) throws InvocationTargetException, IllegalAccessException; diff --git a/src/main/java/javagrinko/spring/tcp/Server.java b/src/main/java/javagrinko/spring/tcp/Server.java index 8e6a7c1..9a97e12 100644 --- a/src/main/java/javagrinko/spring/tcp/Server.java +++ b/src/main/java/javagrinko/spring/tcp/Server.java @@ -15,7 +15,6 @@ public interface Server { List getConnections(); void addListener(Connection.Listener listener); HashMap getConnectedUsers(); - void addConnectedUser(UUID uuid, User user); void removeConnectedUser(UUID uuid); } diff --git a/src/main/java/javagrinko/spring/tcp/TcpConnection.java b/src/main/java/javagrinko/spring/tcp/TcpConnection.java index be3a862..b30257f 100644 --- a/src/main/java/javagrinko/spring/tcp/TcpConnection.java +++ b/src/main/java/javagrinko/spring/tcp/TcpConnection.java @@ -21,6 +21,9 @@ public class TcpConnection implements Connection { private Socket socket; private List listeners = new CopyOnWriteArrayList<>(); private UUID clientIdentifier = null; + private Integer teamSide = 5; + private Integer opponentUID; + private String opponentConID = ""; private Integer chunksLeft = 0; private String saveData = ""; @@ -118,6 +121,16 @@ public class TcpConnection implements Connection { this.chunksLeft = chunksLeft; } + @Override + public Integer getTeamSide() { + return teamSide; + } + + @Override + public void setTeamSide(Integer teamSide) { + this.teamSide = teamSide; + } + @Override public String getSaveData() { return saveData; @@ -127,4 +140,29 @@ public class TcpConnection implements Connection { public void setSaveData(String saveData) { this.saveData = saveData; } + + @Override + public void setOpponentUID(Integer opponentUID) { + this.opponentUID = opponentUID; + } + + @Override + public Integer getOpponentUID() { + return opponentUID; + } + + @Override + public void setOpponentConID(UUID opponentConID) { + this.opponentConID = opponentConID.toString(); + } + + @Override + public String getOpponentConIDAsString() { + return opponentConID; + } + + @Override + public UUID getOpponentConIDAsUUID() { + return UUID.fromString(opponentConID); + } }