From e57d2a9ee22a32219209ca0abdfd9a31ede633f6 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 14 Sep 2024 17:33:42 +0200 Subject: [PATCH] Create account, chr tables & save chr to Postgres --- config.yaml | 1 + database/sql/1-db_database.sql | 1 - src/main/java/client/Character.java | 6 +- src/main/java/client/CharacterStats.java | 2 +- src/main/java/config/ServerConfig.java | 1 + src/main/java/database/PgDatabaseConfig.java | 6 +- .../character/CharacterRepository.java | 99 +++++++++++++++++++ .../database/character/CharacterSaver.java | 13 ++- .../java/database/migration/FlywayRunner.java | 4 + src/main/java/net/server/Server.java | 21 ++-- .../db/migration/postgresql/V0.8__account.sql | 30 ++++++ .../migration/postgresql/V0.9__character.sql | 80 +++++++++++++++ 12 files changed, 247 insertions(+), 17 deletions(-) create mode 100644 src/main/java/database/character/CharacterRepository.java create mode 100644 src/main/resources/db/migration/postgresql/V0.8__account.sql create mode 100644 src/main/resources/db/migration/postgresql/V0.9__character.sql diff --git a/config.yaml b/config.yaml index 804a0e7477..2d252ba255 100644 --- a/config.yaml +++ b/config.yaml @@ -172,6 +172,7 @@ server: PG_DB_ADMIN_PASSWORD: "redsnailshell" PG_DB_USERNAME: "cosmic_server" PG_DB_PASSWORD: "bluesnailshell" + PG_DB_CLEAN: false # !!! WARNING !!! Deletes the entire database - starts from scratch. #Login Configuration WORLDS: 1 #Initial number of worlds on the server. diff --git a/database/sql/1-db_database.sql b/database/sql/1-db_database.sql index 23f4d86992..7ad8a4a13a 100644 --- a/database/sql/1-db_database.sql +++ b/database/sql/1-db_database.sql @@ -136,7 +136,6 @@ CREATE TABLE IF NOT EXISTS `characters` ( `skincolor` int(11) NOT NULL DEFAULT '0', `gender` int(11) NOT NULL DEFAULT '0', `fame` int(11) NOT NULL DEFAULT '0', - `fquest` int(11) NOT NULL DEFAULT '0', `hair` int(11) NOT NULL DEFAULT '0', `face` int(11) NOT NULL DEFAULT '0', `ap` int(11) NOT NULL DEFAULT '0', diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 0d02b07413..1ae5eee7e2 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8461,7 +8461,7 @@ public class Character extends AbstractCharacterObject { ps.setInt(21, stats.mapId()); ps.setInt(22, stats.meso()); ps.setInt(23, stats.hpMpApUsed()); - ps.setInt(24, stats.spawnPoint()); + ps.setInt(24, stats.spawnPortal()); ps.setInt(25, stats.party()); ps.setInt(26, stats.buddyCapacity()); ps.setInt(27, stats.messenger()); @@ -8515,7 +8515,7 @@ public class Character extends AbstractCharacterObject { .meso(meso.get()) .hpMpApUsed(hpMpApUsed) .mapId(getSaveMap()) - .spawnPoint(getSaveSpawnpoint()) + .spawnPortal(getSaveSpawnPortal()) .buddyCapacity(buddylist.getCapacity()) .monsterBookCover(bookCover) .dojoVanquisherStage(vanquisherStage) @@ -8607,7 +8607,7 @@ public class Character extends AbstractCharacterObject { return map.getId(); } - private int getSaveSpawnpoint() { + private int getSaveSpawnPortal() { if (map == null || map.getId() == MapId.CRIMSONWOOD_VALLEY_1 || map.getId() == MapId.CRIMSONWOOD_VALLEY_2) { // TODO: clean up. Shouldn't hardcode these maps. return 0; } diff --git a/src/main/java/client/CharacterStats.java b/src/main/java/client/CharacterStats.java index dcdf2a8902..23311bfbf3 100644 --- a/src/main/java/client/CharacterStats.java +++ b/src/main/java/client/CharacterStats.java @@ -28,7 +28,7 @@ public record CharacterStats( int mapId, int meso, int hpMpApUsed, - int spawnPoint, + int spawnPortal, int party, int buddyCapacity, int messenger, diff --git a/src/main/java/config/ServerConfig.java b/src/main/java/config/ServerConfig.java index cf13bc007b..ad550e34d1 100644 --- a/src/main/java/config/ServerConfig.java +++ b/src/main/java/config/ServerConfig.java @@ -20,6 +20,7 @@ public class ServerConfig { public String PG_DB_ADMIN_PASSWORD; public String PG_DB_USERNAME; public String PG_DB_PASSWORD; + public boolean PG_DB_CLEAN; //Login Configuration public int WORLDS; diff --git a/src/main/java/database/PgDatabaseConfig.java b/src/main/java/database/PgDatabaseConfig.java index a290f23007..bec5c7a645 100644 --- a/src/main/java/database/PgDatabaseConfig.java +++ b/src/main/java/database/PgDatabaseConfig.java @@ -1,12 +1,16 @@ package database; +import lombok.Builder; + import java.time.Duration; +@Builder public record PgDatabaseConfig( String databaseName, String host, String schema, String adminUsername, String adminPassword, String username, String password, - Duration poolInitTimeout + Duration poolInitTimeout, + boolean clean ) { public PgDatabaseConfig { verifyNotBlank(databaseName); diff --git a/src/main/java/database/character/CharacterRepository.java b/src/main/java/database/character/CharacterRepository.java new file mode 100644 index 0000000000..0d544d4723 --- /dev/null +++ b/src/main/java/database/character/CharacterRepository.java @@ -0,0 +1,99 @@ +package database.character; + +import client.CharacterStats; +import org.jdbi.v3.core.Handle; + +import java.sql.Timestamp; + +public class CharacterRepository { + + public boolean update(Handle handle, CharacterStats stats) { + String sql = """ + UPDATE chr + SET level = :level, exp = :exp, str = :str, dex = :dex, "int" = :int, luk = :luk, hp = :hp, mp = :mp, + max_hp = :max_hp, max_mp = :max_mp, ap = :ap, sp = :sp, job = :job, fame = :fame, gender = :gender, + skin = :skin, hair = :hair, face = :face, meso = :meso, map_id = :map_id, spawn_portal = :spawn_portal, + gacha_exp = :gacha_exp, used_hp_mp_ap = :used_hp_mp_ap, gm_level = :gm_level, party_id = :party_id, + buddy_capacity = :buddy_capacity, messenger_id = :messenger_id, + messenger_position = :messenger_position, mount_level = :mount_level, mount_exp = :mount_exp, + mount_tiredness = :mount_tiredness, omok_wins = :omok_wins, omok_losses = :omok_losses, + omok_ties = :omok_ties, matchcard_wins = :matchcard_wins, matchcard_losses = :matchcard_losses, + matchcard_ties = :matchcard_ties, equip_slots = :equip_slots, use_slots = :use_slots, + setup_slots = :setup_slots, etc_slots = :etc_slots, monster_book_cover = :monster_book_cover, + dojo_tutorial_complete = :dojo_tutorial_complete, dojo_points = :dojo_points, + dojo_last_stage = :dojo_last_stage, dojo_vanquisher_stage = :dojo_vanquisher_stage, + dojo_vanquisher_kills = :dojo_vanquisher_kills, ariant_points = :ariant_points, + data_string = :data_string, party_search = :party_search, jail_expire = :jail_expire, + last_exp_gain = :last_exp_gain, partner_id = :partner_id, marriage_item_id = :marriage_item_id, + updated_at = now() + WHERE id = :id"""; + + int updatedRows = handle.createUpdate(sql) + .bind("level", stats.level()) + .bind("exp", stats.exp()) + .bind("str", stats.str()) + .bind("dex", stats.dex()) + .bind("int", stats.int_()) + .bind("luk", stats.luk()) + .bind("hp", stats.hp()) + .bind("mp", stats.mp()) + .bind("max_hp", stats.maxHp()) + .bind("max_mp", stats.maxMp()) + .bind("ap", stats.ap()) + .bind("sp", parseMultiSkillbookSp(stats.sp())) + .bind("job", stats.job()) + .bind("fame", stats.fame()) + .bind("gender", stats.gender()) + .bind("skin", stats.skin()) + .bind("hair", stats.hair()) + .bind("face", stats.face()) + .bind("meso", stats.meso()) + .bind("map_id", stats.mapId()) + .bind("spawn_portal", stats.spawnPortal()) + .bind("gacha_exp", stats.gachaExp()) + .bind("used_hp_mp_ap", stats.hpMpApUsed()) + .bind("gm_level", stats.gmLevel()) + .bind("party_id", stats.party()) + .bind("buddy_capacity", stats.buddyCapacity()) + .bind("messenger_id", stats.messenger()) + .bind("messenger_position", stats.messengerPosition()) + .bind("mount_level", stats.mountLevel()) + .bind("mount_exp", stats.mountExp()) + .bind("mount_tiredness", stats.mountTiredness()) + .bind("omok_wins", stats.omokWins()) + .bind("omok_losses", stats.omokLosses()) + .bind("omok_ties", stats.omokTies()) + .bind("matchcard_wins", stats.matchCardWins()) + .bind("matchcard_losses", stats.matchCardLosses()) + .bind("matchcard_ties", stats.matchCardTies()) + .bind("equip_slots", stats.equipSlots()) + .bind("use_slots", stats.useSlots()) + .bind("setup_slots", stats.setupSlots()) + .bind("etc_slots", stats.etcSlots()) + .bind("monster_book_cover", stats.monsterBookCover()) + .bind("dojo_tutorial_complete", stats.dojoTutorialComplete()) + .bind("dojo_points", stats.dojoPoints()) + .bind("dojo_last_stage", stats.dojoStage()) + .bind("dojo_vanquisher_stage", stats.dojoVanquisherStage()) + .bind("dojo_vanquisher_kills", stats.dojoVanquisherKills()) + .bind("ariant_points", stats.ariantPoints()) + .bind("data_string", stats.dataString()) + .bind("party_search", stats.canRecvPartySearchInvite()) + .bind("jail_expire", new Timestamp(stats.jailExpiration())) + .bind("last_exp_gain", new Timestamp(stats.lastExpGainTime())) + .bind("partner_id", stats.partnerId()) + .bind("marriage_item_id", stats.marriageItemId()) + .bind("id", stats.id()) + .execute(); + + return updatedRows > 0; + } + + private int parseMultiSkillbookSp(String sp) { + if (sp == null) { + return 0; + } + + return Integer.parseInt(sp.split(",")[0]); + } +} diff --git a/src/main/java/database/character/CharacterSaver.java b/src/main/java/database/character/CharacterSaver.java index 33bd9b6bd5..eb57167aeb 100644 --- a/src/main/java/database/character/CharacterSaver.java +++ b/src/main/java/database/character/CharacterSaver.java @@ -3,9 +3,8 @@ package database.character; import client.Character; import database.PgDatabaseConnection; import database.monsterbook.MonsterCardRepository; +import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.core.Handle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import tools.DatabaseConnection; import java.sql.Connection; @@ -13,14 +12,17 @@ import java.sql.SQLException; import java.time.Duration; import java.time.Instant; +@Slf4j public class CharacterSaver { - private static final Logger log = LoggerFactory.getLogger(CharacterSaver.class); private final PgDatabaseConnection pgConnection; + private final CharacterRepository characterRepository; private final MonsterCardRepository monsterCardRepository; public CharacterSaver(PgDatabaseConnection pgConnection, + CharacterRepository characterRepository, MonsterCardRepository monsterCardRepository) { this.pgConnection = pgConnection; + this.characterRepository = characterRepository; this.monsterCardRepository = monsterCardRepository; } @@ -61,13 +63,16 @@ public class CharacterSaver { try (Handle handle = pgConnection.getHandle()) { handle.useTransaction(h -> doPostgresSave(h, chr)); + } catch (Exception e) { + log.error("Error saving chr {} to PG", chr.getName(), e); } Duration saveDuration = Duration.between(before, Instant.now()); - log.debug("Saved {} to Postgres in {} ms", chr.getName(), saveDuration.toMillis()); + log.debug("Saved {} to PostgreSQL in {} ms", chr.getName(), saveDuration.toMillis()); } private void doPostgresSave(Handle handle, Character chr) { + characterRepository.update(handle, chr.getCharacterStats()); monsterCardRepository.save(handle, chr.getId(), chr.getMonsterBook().getCards()); } diff --git a/src/main/java/database/migration/FlywayRunner.java b/src/main/java/database/migration/FlywayRunner.java index edbb2aba25..a846b38c64 100644 --- a/src/main/java/database/migration/FlywayRunner.java +++ b/src/main/java/database/migration/FlywayRunner.java @@ -24,7 +24,11 @@ public class FlywayRunner { "server-username", dbConfig.username(), "server-password", dbConfig.password()) ) + .cleanDisabled(!dbConfig.clean()) .load(); + if (dbConfig.clean()) { + flyway.clean(); + } flyway.migrate(); } } diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index cb6d409a69..c58e0ba23b 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -44,6 +44,7 @@ import constants.net.ServerConstants; import database.PgDatabaseConfig; import database.PgDatabaseConnection; import database.character.CharacterLoader; +import database.character.CharacterRepository; import database.character.CharacterSaver; import database.drop.DropProvider; import database.drop.DropRepository; @@ -972,12 +973,17 @@ public class Server { if (pgDbHost == null) { pgDbHost = serverConfig.PG_DB_HOST; } - return new PgDatabaseConfig( - serverConfig.PG_DB_NAME, pgDbHost, serverConfig.PG_DB_SCHEMA, - serverConfig.PG_DB_ADMIN_USERNAME, serverConfig.PG_DB_ADMIN_PASSWORD, - serverConfig.PG_DB_USERNAME, serverConfig.PG_DB_PASSWORD, - Duration.ofSeconds(serverConfig.INIT_CONNECTION_POOL_TIMEOUT) - ); + return PgDatabaseConfig.builder() + .databaseName(serverConfig.PG_DB_NAME) + .host(pgDbHost) + .schema(serverConfig.PG_DB_SCHEMA) + .adminUsername(serverConfig.PG_DB_ADMIN_USERNAME) + .adminPassword(serverConfig.PG_DB_ADMIN_PASSWORD) + .username(serverConfig.PG_DB_USERNAME) + .password(serverConfig.PG_DB_PASSWORD) + .poolInitTimeout(Duration.ofSeconds(serverConfig.INIT_CONNECTION_POOL_TIMEOUT)) + .clean(serverConfig.PG_DB_CLEAN) + .build(); } private void runDatabaseMigration(PgDatabaseConfig config) { @@ -1002,9 +1008,10 @@ public class Server { } private ChannelDependencies registerChannelDependencies(PgDatabaseConnection connection) { + CharacterRepository characterRepository = new CharacterRepository(); MonsterCardRepository monsterCardRepository = new MonsterCardRepository(connection); CharacterLoader characterLoader = new CharacterLoader(monsterCardRepository); - CharacterSaver characterSaver = new CharacterSaver(connection, monsterCardRepository); + CharacterSaver characterSaver = new CharacterSaver(connection, characterRepository, monsterCardRepository); TransitionService transitionService = new TransitionService(characterSaver); BanService banService = new BanService(transitionService); NoteService noteService = new NoteService(new NoteDao(connection)); diff --git a/src/main/resources/db/migration/postgresql/V0.8__account.sql b/src/main/resources/db/migration/postgresql/V0.8__account.sql new file mode 100644 index 0000000000..c297c48249 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V0.8__account.sql @@ -0,0 +1,30 @@ +CREATE TABLE account +( + id serial NOT NULL, + name varchar(30) NOT NULL, + password varchar(200) NOT NULL, + pin varchar(4), + pic varchar(26), + logged_in smallint DEFAULT 0 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + last_login timestamp, + birthday date NOT NULL, + banned boolean DEFAULT false NOT NULL, + banreason text, + macs text, + nx_credit integer DEFAULT 0 NOT NULL, + maple_point integer DEFAULT 0 NOT NULL, + nx_prepaid integer DEFAULT 0 NOT NULL, + chr_slots smallint DEFAULT 3 NOT NULL, + gender smallint DEFAULT 10 NOT NULL, + temp_ban_timestamp timestamp, + greason smallint, + tos_accepted boolean DEFAULT false NOT NULL, + ip text, + hwid text, + PRIMARY KEY (id), + UNIQUE (name) +); +CREATE UNIQUE INDEX lower_account_name_idx ON "account" (lower(name) ); +GRANT SELECT, UPDATE ON TABLE account TO ${server-username}; +GRANT USAGE ON SEQUENCE account_id_seq TO ${server-username}; diff --git a/src/main/resources/db/migration/postgresql/V0.9__character.sql b/src/main/resources/db/migration/postgresql/V0.9__character.sql new file mode 100644 index 0000000000..7835fba30e --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V0.9__character.sql @@ -0,0 +1,80 @@ +CREATE TABLE chr +( + id serial NOT NULL, + account integer NOT NULL, + world integer NOT NULL, + name varchar(12) NOT NULL, + level smallint NOT NULL, + exp integer NOT NULL, + str smallint NOT NULL, + dex smallint NOT NULL, + "int" smallint NOT NULL, + luk smallint NOT NULL, + hp smallint NOT NULL, + mp smallint NOT NULL, + max_hp smallint NOT NULL, + max_mp smallint NOT NULL, + ap smallint NOT NULL, + sp smallint NOT NULL, + job smallint NOT NULL, + fame smallint NOT NULL, + gender smallint NOT NULL, + skin smallint NOT NULL, + hair integer NOT NULL, + face integer NOT NULL, + meso integer DEFAULT 0 NOT NULL, + map_id integer NOT NULL, + spawn_portal smallint NOT NULL, + gacha_exp integer NOT NULL, + used_hp_mp_ap integer NOT NULL, + gm_level smallint NOT NULL, + party_id integer, + buddy_capacity smallint, + "rank" integer, + rank_move integer, + job_rank integer, + job_rank_move integer, + guild_id integer, + guild_rank smallint, + alliance_rank smallint, + messenger_id integer, + messenger_position smallint, + mount_level smallint, + mount_exp integer, + mount_tiredness smallint, + omok_wins integer DEFAULT 0 NOT NULL, + omok_losses integer DEFAULT 0 NOT NULL, + omok_ties integer DEFAULT 0 NOT NULL, + matchcard_wins integer DEFAULT 0 NOT NULL, + matchcard_losses integer DEFAULT 0 NOT NULL, + matchcard_ties integer DEFAULT 0 NOT NULL, + merchant_mesos integer DEFAULT 0 NOT NULL, + has_merchant boolean DEFAULT false NOT NULL, + equip_slots smallint NOT NULL, + use_slots smallint NOT NULL, + setup_slots smallint NOT NULL, + etc_slots smallint NOT NULL, + family_id integer, + partner_id integer, + marriage_item_id integer, + monster_book_cover integer, + dojo_tutorial_complete boolean DEFAULT false NOT NULL, + dojo_points integer DEFAULT 0 NOT NULL, + dojo_last_stage integer, + dojo_vanquisher_stage integer, + dojo_vanquisher_kills integer DEFAULT 0 NOT NULL, + ariant_points integer DEFAULT 0 NOT NULL, + data_string text, + party_search boolean DEFAULT false NOT NULL, + last_logout timestamp, + last_exp_gain timestamp, + jail_expire timestamp, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + UNIQUE (name), + PRIMARY KEY (id), + FOREIGN KEY (account) REFERENCES account (id) ON DELETE RESTRICT +); +CREATE UNIQUE INDEX lower_character_name_idx ON chr (lower(name) ); +GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE chr TO ${server-username}; +GRANT USAGE ON SEQUENCE chr_id_seq TO ${server-username};