Create account, chr tables & save chr to Postgres

This commit is contained in:
P0nk
2024-09-14 17:33:42 +02:00
parent f827e23ccc
commit e57d2a9ee2
12 changed files with 247 additions and 17 deletions

View File

@@ -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;
}

View File

@@ -28,7 +28,7 @@ public record CharacterStats(
int mapId,
int meso,
int hpMpApUsed,
int spawnPoint,
int spawnPortal,
int party,
int buddyCapacity,
int messenger,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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]);
}
}

View File

@@ -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());
}

View File

@@ -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();
}
}

View File

@@ -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));

View File

@@ -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};

View File

@@ -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};