From 080cdeca339942199cf64bb0bc36888b1d2bd0f5 Mon Sep 17 00:00:00 2001 From: austin Date: Fri, 29 Nov 2024 20:47:52 -0500 Subject: [PATCH] character database work --- assets/DB/character/createCharacterTables.sql | 2 +- buildNumber.properties | 4 +- docs/src/progress/renderertodo.md | 2 + .../auth/AuthenticationManager.java | 41 ++++++++++-- .../character/CharacterDescription.java | 53 +++++++++++++++ .../character/ClientCharacterListDTO.java | 31 +++++++++ .../character/ClientCharacterManager.java | 30 +++++++++ .../menu/mainmenu/MenuCharacterCreation.java | 32 +++++++++- .../java/electrosphere/engine/Globals.java | 3 + .../engine/loadingthreads/ClientLoading.java | 1 + .../engine/loadingthreads/LoadingUtils.java | 3 +- .../net/client/ClientNetworking.java | 2 +- .../net/client/protocol/AuthProtocol.java | 3 + .../client/protocol/CharacterProtocol.java | 21 ++++-- .../net/client/protocol/PlayerProtocol.java | 2 +- .../parser/net/message/CharacterMessage.java | 42 ++++++++++-- .../net/parser/net/message/TypeBytes.java | 1 - .../net/server/player/Player.java | 25 +++++++- .../net/server/protocol/AuthProtocol.java | 7 +- .../server/protocol/CharacterProtocol.java | 64 +++++++++++++++++-- .../server/db/DatabaseResult.java | 57 +++++++++++++---- .../server/db/DatabaseResultIterator.java | 19 ++++++ src/net/character.json | 4 +- 23 files changed, 400 insertions(+), 49 deletions(-) create mode 100644 src/main/java/electrosphere/client/entity/character/CharacterDescription.java create mode 100644 src/main/java/electrosphere/client/entity/character/ClientCharacterListDTO.java create mode 100644 src/main/java/electrosphere/client/entity/character/ClientCharacterManager.java diff --git a/assets/DB/character/createCharacterTables.sql b/assets/DB/character/createCharacterTables.sql index f8527697..26b0e952 100644 --- a/assets/DB/character/createCharacterTables.sql +++ b/assets/DB/character/createCharacterTables.sql @@ -6,5 +6,5 @@ CREATE INDEX charaWorldPositionsIDIndex ON charaWorldPositions (id); CREATE INDEX charaWorldPositionsPosIndex ON charaWorldPositions (posX, posY); --data -CREATE TABLE charaData (playerId INTEGER PRIMARY KEY, id INTEGER, dataVal VARCHAR); +CREATE TABLE charaData (id INTEGER PRIMARY KEY AUTOINCREMENT, playerId INTEGER, dataVal VARCHAR); CREATE INDEX charaDataIDIndex ON charaData (id); diff --git a/buildNumber.properties b/buildNumber.properties index cb3950ab..51c3d3a2 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Mon Nov 25 18:13:49 EST 2024 -buildNumber=405 +#Fri Nov 29 20:08:47 EST 2024 +buildNumber=406 diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 2ce9a538..ecce7d89 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1184,6 +1184,8 @@ Actor panel additional functionality Better style for character creation menu Fix AABB calculation from assimp-loaded models Fix singleplayer launching at all +Store characters in database +Spawn characters from database # TODO diff --git a/src/main/java/electrosphere/auth/AuthenticationManager.java b/src/main/java/electrosphere/auth/AuthenticationManager.java index 60ba2309..40a48ce7 100644 --- a/src/main/java/electrosphere/auth/AuthenticationManager.java +++ b/src/main/java/electrosphere/auth/AuthenticationManager.java @@ -20,6 +20,16 @@ public class AuthenticationManager { */ boolean isMock = false; + /** + * An invalid login + */ + public static final int INVALID_LOGIN = -1; + + /** + * ID for a mock login + */ + public static final int MOCK_LOGIN = 0; + /** * Private constructor */ @@ -38,9 +48,15 @@ public class AuthenticationManager { return rVal; } - public boolean authenticate(String username, String password){ + /** + * Authenticates the player + * @param username The username of the player + * @param password The password of the player + * @return The id of the player if they logged in successfully, INVALID_LOGIN if the authentication failed + */ + public int authenticate(String username, String password){ if(isMock){ - return true; + return MOCK_LOGIN; } //first we hash the input password String hashedPassword = getHashedString(password); @@ -54,19 +70,32 @@ public class AuthenticationManager { String pwdhash = row.getAsString("pwdhash"); if(pwdhash.equals(hashedPassword)){ LoggerInterface.loggerAuth.INFO("Authenticated user " + username); - return true; + return row.getAsInteger("id"); } } //If we didn't find a single account, go ahead and create it if(!foundRow){ LoggerInterface.loggerAuth.INFO("Created user " + username); Globals.dbController.executePreparedStatement("INSERT INTO accounts (username, pwdhash) VALUES(?, ?);",username,hashedPassword); - //TODO: verify we created the account - return true; + + //verify the account was created + result = Globals.dbController.executePreparedQuery("SELECT id, username, pwdhash FROM accounts WHERE username=?;",username); + if(result.hasResult()){ + foundRow = false; + //if we get a valid response from the database, check that it actually matches hashes + for(DatabaseResultRow row : result){ + foundRow = true; + String pwdhash = row.getAsString("pwdhash"); + if(pwdhash.equals(hashedPassword)){ + LoggerInterface.loggerAuth.INFO("Authenticated user " + username); + return row.getAsInteger("id"); + } + } + } } } LoggerInterface.loggerAuth.INFO("Failed to authenticate user " + username); - return false; + return INVALID_LOGIN; } static final int saltLength = 16; diff --git a/src/main/java/electrosphere/client/entity/character/CharacterDescription.java b/src/main/java/electrosphere/client/entity/character/CharacterDescription.java new file mode 100644 index 00000000..24d15d35 --- /dev/null +++ b/src/main/java/electrosphere/client/entity/character/CharacterDescription.java @@ -0,0 +1,53 @@ +package electrosphere.client.entity.character; + +import electrosphere.entity.types.creature.CreatureTemplate; + +/** + * Describes a character + */ +public class CharacterDescription { + + /** + * The id of the character + */ + String id; + + /** + * The character's template data + */ + CreatureTemplate template; + + /** + * Gets the id of the character + * @return The id + */ + public String getId() { + return id; + } + + /** + * Sets the id of the character + * @param id The id + */ + public void setId(String id) { + this.id = id; + } + + /** + * Gets the creature template for the character + * @return The creature template + */ + public CreatureTemplate getTemplate() { + return template; + } + + /** + * Sets the creature template for the character + * @param template The creature template + */ + public void setTemplate(CreatureTemplate template) { + this.template = template; + } + + +} diff --git a/src/main/java/electrosphere/client/entity/character/ClientCharacterListDTO.java b/src/main/java/electrosphere/client/entity/character/ClientCharacterListDTO.java new file mode 100644 index 00000000..13140bbf --- /dev/null +++ b/src/main/java/electrosphere/client/entity/character/ClientCharacterListDTO.java @@ -0,0 +1,31 @@ +package electrosphere.client.entity.character; + +import java.util.List; + +/** + * DTO for sending available characters to the client + */ +public class ClientCharacterListDTO { + + /** + * The list of characters stored in the DTO + */ + List characters; + + /** + * Gets the list of characters + * @return The list of characters + */ + public List getCharacters() { + return characters; + } + + /** + * Sets the list of characters + * @param characters The list of characters + */ + public void setCharacters(List characters) { + this.characters = characters; + } + +} diff --git a/src/main/java/electrosphere/client/entity/character/ClientCharacterManager.java b/src/main/java/electrosphere/client/entity/character/ClientCharacterManager.java new file mode 100644 index 00000000..14b2e971 --- /dev/null +++ b/src/main/java/electrosphere/client/entity/character/ClientCharacterManager.java @@ -0,0 +1,30 @@ +package electrosphere.client.entity.character; + +/** + * Tracks the characers available to the client + */ +public class ClientCharacterManager { + + /** + * The list of characters available + */ + ClientCharacterListDTO characterList; + + /** + * Gets the character list + * @return The character list + */ + public ClientCharacterListDTO getCharacterList() { + return characterList; + } + + /** + * Sets the character list + * @param characterList The character list + */ + public void setCharacterList(ClientCharacterListDTO characterList) { + this.characterList = characterList; + } + + +} diff --git a/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuCharacterCreation.java b/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuCharacterCreation.java index 99a0bd5d..7f912dd2 100644 --- a/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuCharacterCreation.java +++ b/src/main/java/electrosphere/client/ui/menu/mainmenu/MenuCharacterCreation.java @@ -1,14 +1,20 @@ package electrosphere.client.ui.menu.mainmenu; +import electrosphere.client.entity.character.CharacterDescription; import electrosphere.client.ui.components.CharacterCustomizer; import electrosphere.client.ui.menu.WindowUtils; import electrosphere.engine.Globals; +import electrosphere.engine.loadingthreads.LoadingThread; +import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType; import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.net.parser.net.message.CharacterMessage; +import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.ui.elements.Button; +import electrosphere.renderer.ui.elements.Div; import electrosphere.renderer.ui.elements.FormElement; import electrosphere.renderer.ui.elements.StringCarousel; import electrosphere.renderer.ui.elementtypes.Element; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaAlignment; import electrosphere.renderer.ui.events.ValueChangeEvent; import electrosphere.util.Utilities; @@ -29,11 +35,35 @@ public class MenuCharacterCreation { public static Element createCharacterSelectionWindow(){ FormElement rVal = new FormElement(); + //the list of characters + Div selectContainer = Div.createCol(); + if(Globals.clientCharacterManager.getCharacterList() != null){ + for(CharacterDescription description : Globals.clientCharacterManager.getCharacterList().getCharacters()){ + String buttonTitle = "Character " + description.getId(); + Div charNameContainer = Div.createRow(Button.createButton(buttonTitle, () -> { + Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestSpawnCharacterMessage(description.getId())); + Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestMetadataMessage()); + Globals.threadManager.start(new LoadingThread(LoadingThreadType.CLIENT_WORLD)); + })); + selectContainer.addChild(charNameContainer); + } + } + //button (create) - rVal.addChild(Button.createButton("Create Character", () -> { + Div createContainer = Div.createDiv(); + createContainer.addChild(Button.createButton("Create Character", () -> { WindowUtils.replaceMainMenuContents(MenuCharacterCreation.createRaceSelectionMenu()); })); + + //main layout + Div mainLayout = Div.createCol( + selectContainer, + createContainer + ); + mainLayout.setAlignItems(YogaAlignment.Center); + rVal.addChild(mainLayout); + return rVal; } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 7cbbda62..f39af07b 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -17,6 +17,7 @@ import electrosphere.client.block.ClientBlockManager; import electrosphere.client.block.cells.BlockTextureAtlas; import electrosphere.client.block.cells.ClientBlockCellManager; import electrosphere.client.chemistry.ClientChemistryCollisionCallback; +import electrosphere.client.entity.character.ClientCharacterManager; import electrosphere.client.entity.particle.ParticleService; import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.fluid.manager.ClientFluidManager; @@ -352,6 +353,7 @@ public class Globals { //client world data public static ClientWorldData clientWorldData; + public static ClientCharacterManager clientCharacterManager = new ClientCharacterManager(); //client gridded manager public static ClientTerrainManager clientTerrainManager; @@ -703,6 +705,7 @@ public class Globals { if(Globals.realmManager != null){ Globals.realmManager.reset(); } + Globals.dbController.disconnect(); } /** diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index dd6c6b0e..21f65feb 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -102,6 +102,7 @@ public class ClientLoading { Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT); //initialize the "real" objects simulation initClientSimulation(); + LoadingUtils.setSimulationsToReady(); //initialize the gridded managers (client) initDrawCellManager(true); initFoliageManager(); diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java index e45abbc9..46979535 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java @@ -24,6 +24,7 @@ import electrosphere.net.parser.net.message.CharacterMessage; import electrosphere.net.server.Server; import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.server.player.Player; +import electrosphere.net.server.protocol.CharacterProtocol; import electrosphere.server.datacell.Realm; import electrosphere.server.simulation.MicroSimulation; @@ -167,7 +168,7 @@ public class LoadingUtils { template.getCreatureToolbarData().setSlotItem("2", new ToolbarItem(73, "entityinspector")); //set player character template serverPlayerConnection.setCreatureTemplate(template); - Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestSpawnCharacterMessage()); + Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestSpawnCharacterMessage(CharacterProtocol.SPAWN_EXISTING_TEMPLATE + "")); //set player world-space coordinates Player playerObject = Globals.playerManager.getFirstPlayer(); diff --git a/src/main/java/electrosphere/net/client/ClientNetworking.java b/src/main/java/electrosphere/net/client/ClientNetworking.java index 2cd68929..ab647781 100644 --- a/src/main/java/electrosphere/net/client/ClientNetworking.java +++ b/src/main/java/electrosphere/net/client/ClientNetworking.java @@ -219,7 +219,7 @@ public class ClientNetworking implements Runnable { long currentTime = System.currentTimeMillis(); //basically if we haven't sent a ping in a while, send one if(currentTime - lastPingTime > SEND_PING_THRESHOLD){ - queueOutgoingMessage(ServerMessage.constructPingMessage()); + this.queueOutgoingMessage(ServerMessage.constructPingMessage()); lastPingTime = currentTime; if(lastPongTime == 0){ lastPongTime = lastPingTime; diff --git a/src/main/java/electrosphere/net/client/protocol/AuthProtocol.java b/src/main/java/electrosphere/net/client/protocol/AuthProtocol.java index d6bbbe88..36577b69 100644 --- a/src/main/java/electrosphere/net/client/protocol/AuthProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/AuthProtocol.java @@ -3,6 +3,7 @@ package electrosphere.net.client.protocol; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.AuthMessage; +import electrosphere.net.parser.net.message.CharacterMessage; import electrosphere.net.parser.net.message.LoreMessage; import electrosphere.net.template.ClientProtocolTemplate; @@ -24,6 +25,8 @@ public class AuthProtocol implements ClientProtocolTemplate { Globals.clientPassword = ""; //request playable races Globals.clientConnection.queueOutgoingMessage(LoreMessage.constructRequestRacesMessage()); + //request characters available to this player + Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestCharacterListMessage()); //log that we succeeded LoggerInterface.loggerAuth.INFO("Successfully logged in"); break; diff --git a/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java b/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java index eb6ed320..308d91b4 100644 --- a/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/CharacterProtocol.java @@ -1,8 +1,14 @@ package electrosphere.net.client.protocol; +import com.google.gson.Gson; + +import electrosphere.client.entity.character.ClientCharacterListDTO; +import electrosphere.client.ui.menu.WindowUtils; +import electrosphere.client.ui.menu.mainmenu.MenuCharacterCreation; import electrosphere.engine.Globals; import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType; +import electrosphere.engine.signal.Signal.SignalType; import electrosphere.net.parser.net.message.CharacterMessage; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.net.template.ClientProtocolTemplate; @@ -20,17 +26,22 @@ public class CharacterProtocol implements ClientProtocolTemplate { + WindowUtils.replaceMainMenuContents(MenuCharacterCreation.createCharacterSelectionWindow()); + }); + } break; case REQUESTCHARACTERLIST: case REQUESTCREATECHARACTER: case REQUESTSPAWNCHARACTER: - case RESPONSECHARACTERLIST: case RESPONSECREATECHARACTERFAILURE: case RESPONSESPAWNCHARACTER: case EDITORSWAP: diff --git a/src/main/java/electrosphere/net/client/protocol/PlayerProtocol.java b/src/main/java/electrosphere/net/client/protocol/PlayerProtocol.java index 65b3bd6e..1ebe1fad 100644 --- a/src/main/java/electrosphere/net/client/protocol/PlayerProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/PlayerProtocol.java @@ -23,7 +23,7 @@ public class PlayerProtocol implements ClientProtocolTemplate { Globals.profiler.beginCpuSample("PlayerProtocol.handlePlayerMessage"); switch(message.getMessageSubtype()){ case SET_ID: - Globals.clientPlayer = new Player(message.getplayerID()); + Globals.clientPlayer = new Player(message.getplayerID(), Player.CLIENT_DB_ID); LoggerInterface.loggerNetworking.DEBUG("[CLIENT] Player ID is " + Globals.clientPlayer.getId()); break; case SETINITIALDISCRETEPOSITION: diff --git a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java index be9c476f..cf79f0ef 100644 --- a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java @@ -93,11 +93,7 @@ public class CharacterMessage extends NetworkMessage { return false; } case TypeBytes.CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER: - if(byteBuffer.getRemaining() >= TypeBytes.CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER_SIZE){ - return true; - } else { - return false; - } + return CharacterMessage.canParseRequestSpawnCharacterMessage(byteBuffer); case TypeBytes.CHARACTER_MESSAGE_TYPE_RESPONSESPAWNCHARACTER: return CharacterMessage.canParseResponseSpawnCharacterMessage(byteBuffer); case TypeBytes.CHARACTER_MESSAGE_TYPE_EDITORSWAP: @@ -248,20 +244,44 @@ public class CharacterMessage extends NetworkMessage { return rVal; } + /** + * Checks if a message of type RequestSpawnCharacter can be parsed from the byte stream + */ + public static boolean canParseRequestSpawnCharacterMessage(CircularByteBuffer byteBuffer){ + int currentStreamLength = byteBuffer.getRemaining(); + List temporaryByteQueue = new LinkedList(); + int dataSize = 0; + if(currentStreamLength < 6){ + return false; + } else { + temporaryByteQueue.add(byteBuffer.peek(2 + 0)); + temporaryByteQueue.add(byteBuffer.peek(2 + 1)); + temporaryByteQueue.add(byteBuffer.peek(2 + 2)); + temporaryByteQueue.add(byteBuffer.peek(2 + 3)); + dataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue); + } + if(currentStreamLength < 6 + dataSize){ + return false; + } + return true; + } + /** * Parses a message of type RequestSpawnCharacter */ public static CharacterMessage parseRequestSpawnCharacterMessage(CircularByteBuffer byteBuffer){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER); stripPacketHeader(byteBuffer); + rVal.setdata(ByteStreamUtils.popStringFromByteQueue(byteBuffer)); return rVal; } /** * Constructs a message of type RequestSpawnCharacter */ - public static CharacterMessage constructRequestSpawnCharacterMessage(){ + public static CharacterMessage constructRequestSpawnCharacterMessage(String data){ CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER); + rVal.setdata(data); rVal.serialize(); return rVal; } @@ -383,11 +403,19 @@ public class CharacterMessage extends NetworkMessage { rawBytes[1] = TypeBytes.CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERFAILURE; break; case REQUESTSPAWNCHARACTER: - rawBytes = new byte[2]; + rawBytes = new byte[2+4+data.length()]; //message header rawBytes[0] = TypeBytes.MESSAGE_TYPE_CHARACTER; //entity messaage header rawBytes[1] = TypeBytes.CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER; + intValues = ByteStreamUtils.serializeIntToBytes(data.length()); + for(int i = 0; i < 4; i++){ + rawBytes[2+i] = intValues[i]; + } + stringBytes = data.getBytes(); + for(int i = 0; i < data.length(); i++){ + rawBytes[6+i] = stringBytes[i]; + } break; case RESPONSESPAWNCHARACTER: rawBytes = new byte[2+4+data.length()]; diff --git a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java index f5228258..cd786137 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java +++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java @@ -137,7 +137,6 @@ public class TypeBytes { public static final byte CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERSUCCESS_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERFAILURE_SIZE = 2; - public static final byte CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER_SIZE = 2; public static final byte CHARACTER_MESSAGE_TYPE_EDITORSWAP_SIZE = 2; /* diff --git a/src/main/java/electrosphere/net/server/player/Player.java b/src/main/java/electrosphere/net/server/player/Player.java index 763f0fa7..a868d47d 100644 --- a/src/main/java/electrosphere/net/server/player/Player.java +++ b/src/main/java/electrosphere/net/server/player/Player.java @@ -21,6 +21,11 @@ public class Player { */ public static final int DEFAULT_SIMULATION_RADIUS = 3; + /** + * DBID the client assigns to the player object + */ + public static final int CLIENT_DB_ID = 0; + /** * Id incrementer lock */ @@ -41,6 +46,11 @@ public class Player { */ int id; + /** + * The database's id of the player + */ + int dbId; + /** * The world position of this player */ @@ -64,10 +74,12 @@ public class Player { /** * Constructor * @param connectionHandler The corresponding connection + * @param dbId The database's id of the player */ - public Player(ServerConnectionHandler connectionHandler){ + public Player(ServerConnectionHandler connectionHandler, int dbId){ this.connectionHandler = connectionHandler; id = connectionHandler.getPlayerId(); + this.dbId = dbId; this.simulationRadius = Globals.userSettings.getGameplayPhysicsCellRadius(); } @@ -75,8 +87,9 @@ public class Player { * Used when initing a local connection * @param id The id of the local connection */ - public Player(int id){ + public Player(int id, int dbId){ this.id = id; + this.dbId = dbId; } /** @@ -182,6 +195,14 @@ public class Player { this.hasSentPlayerEntity = hasSentPlayerEntity; } + /** + * Gets the database's id for the player + * @return The database's id for the player + */ + public int getDBID(){ + return this.dbId; + } + diff --git a/src/main/java/electrosphere/net/server/protocol/AuthProtocol.java b/src/main/java/electrosphere/net/server/protocol/AuthProtocol.java index d82cd1d5..77fb45b9 100644 --- a/src/main/java/electrosphere/net/server/protocol/AuthProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/AuthProtocol.java @@ -1,5 +1,6 @@ package electrosphere.net.server.protocol; +import electrosphere.auth.AuthenticationManager; import electrosphere.engine.Globals; import electrosphere.net.parser.net.message.AuthMessage; import electrosphere.net.parser.net.message.PlayerMessage; @@ -17,11 +18,11 @@ public class AuthProtocol implements ServerProtocolTemplate { switch(message.getMessageSubtype()){ case AUTHDETAILS: //auth check - boolean successfulLogin = Globals.authenticationManager.authenticate(message.getuser(), message.getpass()); - if(successfulLogin){ + int loginId = Globals.authenticationManager.authenticate(message.getuser(), message.getpass()); + if(loginId != AuthenticationManager.INVALID_LOGIN){ //TODO: actually set connection/protocol to authenticated connectionHandler.addMessagetoOutgoingQueue(AuthMessage.constructAuthSuccessMessage()); - Player newPlayer = new Player(connectionHandler); + Player newPlayer = new Player(connectionHandler, loginId); Globals.playerManager.registerPlayer(newPlayer); //there is a race condition here where if a local non-server client connects first then it breaks if(connectionHandler.getIPAddress().contains("127.0.0.1") && Globals.RUN_CLIENT == true && Globals.clientPlayer == null){ diff --git a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java index 0dcd995f..b7ea819e 100644 --- a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java @@ -1,10 +1,15 @@ package electrosphere.net.server.protocol; +import java.util.LinkedList; import java.util.List; import java.util.Random; import org.joml.Vector3d; +import com.google.gson.Gson; + +import electrosphere.client.entity.character.CharacterDescription; +import electrosphere.client.entity.character.ClientCharacterListDTO; import electrosphere.engine.Globals; import electrosphere.engine.loadingthreads.LoadingUtils; import electrosphere.entity.Entity; @@ -21,6 +26,8 @@ import electrosphere.net.server.player.Player; import electrosphere.net.template.ServerProtocolTemplate; import electrosphere.server.character.PlayerCharacterCreation; import electrosphere.server.datacell.Realm; +import electrosphere.server.db.DatabaseResult; +import electrosphere.server.db.DatabaseResultRow; import electrosphere.util.Utilities; /** @@ -28,20 +35,50 @@ import electrosphere.util.Utilities; */ public class CharacterProtocol implements ServerProtocolTemplate { + /** + * Should spawn the existing template + */ + public static final int SPAWN_EXISTING_TEMPLATE = -1; + @Override public CharacterMessage handleAsyncMessage(ServerConnectionHandler connectionHandler, CharacterMessage message) { + switch(message.getMessageSubtype()){ + case REQUESTCHARACTERLIST: { + Gson gson = new Gson(); + List characters = new LinkedList(); + DatabaseResult result = Globals.dbController.executePreparedQuery("SELECT id, dataVal FROM charaData WHERE playerId=?;",connectionHandler.getPlayer().getDBID()); + if(result.hasResult()){ + //if we get a valid response from the database, check that it actually matches hashes + for(DatabaseResultRow row : result){ + CharacterDescription description = new CharacterDescription(); + CreatureTemplate template = gson.fromJson(row.getAsString("dataVal"),CreatureTemplate.class); + description.setTemplate(template); + description.setId(row.getAsInteger("id") + ""); + characters.add(description); + } + } + ClientCharacterListDTO dto = new ClientCharacterListDTO(); + dto.setCharacters(characters); + connectionHandler.addMessagetoOutgoingQueue(CharacterMessage.constructResponseCharacterListMessage(gson.toJson(dto))); + return null; + } + default: { + } break; + } return message; } @Override public void handleSyncMessage(ServerConnectionHandler connectionHandler, CharacterMessage message) { switch(message.getMessageSubtype()){ - case REQUESTCHARACTERLIST: - //TODO - break; case REQUESTCREATECHARACTER: { CreatureTemplate template = Utilities.deserialize(message.getdata(), CreatureTemplate.class); if(template != null){ + Globals.dbController.executePreparedStatement( + "INSERT INTO charaData (playerId,dataVal) VALUES (?,?);", + connectionHandler.getPlayer().getDBID(), + new Gson().toJson(template) + ); connectionHandler.setCreatureTemplate(Utilities.deserialize(message.getdata(), CreatureTemplate.class)); connectionHandler.addMessagetoOutgoingQueue(CharacterMessage.constructResponseCreateCharacterSuccessMessage()); } else { @@ -49,11 +86,15 @@ public class CharacterProtocol implements ServerProtocolTemplate { - boolean isQuery = false; - boolean isStatement = false; - boolean succeeded = false; - boolean hasResultSet = false; - String code; - ResultSet rs; + /** + * The deserializer for the data + */ private static Gson deserializer; + /** + * Init deserializer + */ static { deserializer = new Gson(); } + + /** + * True if this was a query + */ + boolean isQuery = false; + + /** + * True if this was a statement + */ + boolean isStatement = false; + + /** + * True if the request succeeded, false otherwise + */ + boolean succeeded = false; + + /** + * True if the result has data, false otherwise + */ + boolean hasResultSet = false; + + /** + * The code for the query + */ + String code; + + /** + * The raw result data + */ + ResultSet rs; - + /** + * Creates a query + * @param code The code for the query + * @return The results of the query + */ protected static DatabaseResult createQuery(String code){ DatabaseResult rVal = new DatabaseResult(); rVal.isQuery = true; @@ -30,6 +64,11 @@ public class DatabaseResult implements Iterable { return rVal; } + /** + * Creates a statement + * @param code The code for the statement + * @return The results of the statement + */ protected static DatabaseResult createStatement(String code){ DatabaseResult rVal = new DatabaseResult(); rVal.isStatement = true; @@ -72,10 +111,6 @@ public class DatabaseResult implements Iterable { } return rVal; } - - - public void logRawResult(){ - } @Override public DatabaseResultIterator iterator() { diff --git a/src/main/java/electrosphere/server/db/DatabaseResultIterator.java b/src/main/java/electrosphere/server/db/DatabaseResultIterator.java index 1efcc49d..de02d37b 100644 --- a/src/main/java/electrosphere/server/db/DatabaseResultIterator.java +++ b/src/main/java/electrosphere/server/db/DatabaseResultIterator.java @@ -10,10 +10,24 @@ import java.util.List; import electrosphere.logger.LoggerInterface; +/** + * Iterates through a database result + */ public class DatabaseResultIterator implements Iterator { + /** + * The result set + */ ResultSet rs; + + /** + * The metadata of the result set + */ ResultSetMetaData metadata; + + /** + * The type list of each column + */ List typeList; /** @@ -34,6 +48,11 @@ public class DatabaseResultIterator implements Iterator { //if it did, we'd call this to be explicitly clear where we want to start //instead the assumption is it always starts ON the first element // this.rs.first(); + + //starts before the first, so must iterate once into the result set in order to not double-sample the first row + if(this.rs.isBeforeFirst()){ + this.rs.next(); + } } catch (SQLException e) { LoggerInterface.loggerEngine.ERROR("SQL Exception", e); } diff --git a/src/net/character.json b/src/net/character.json index 96c9395c..4b7468aa 100644 --- a/src/net/character.json +++ b/src/net/character.json @@ -43,7 +43,9 @@ { "messageName" : "RequestSpawnCharacter", "description" : "Requests that the server spawn the client in as a given character", - "data" : [] + "data" : [ + "data" + ] }, { "messageName" : "ResponseSpawnCharacter",