From 274c5c623428210098524f306bd7364d4c31cbdf Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:23:15 -0300 Subject: [PATCH 01/21] Introduce union Alliance server-side implementation. --- .../kinoko/database/AllianceAccessor.java | 18 +++ .../kinoko/database/DatabaseConnector.java | 2 + .../java/kinoko/database/DatabaseManager.java | 4 + src/main/java/kinoko/script/GuildHQ.java | 38 ++++- .../java/kinoko/server/alliance/Alliance.java | 146 ++++++++++++++++++ .../server/alliance/AllianceStorage.java | 41 +++++ src/main/java/kinoko/server/guild/Guild.java | 4 + .../kinoko/server/node/CentralServerNode.java | 23 +++ src/main/java/kinoko/world/GameConstants.java | 13 ++ .../java/kinoko/world/user/GuildInfo.java | 5 + 10 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 src/main/java/kinoko/database/AllianceAccessor.java create mode 100644 src/main/java/kinoko/server/alliance/Alliance.java create mode 100644 src/main/java/kinoko/server/alliance/AllianceStorage.java diff --git a/src/main/java/kinoko/database/AllianceAccessor.java b/src/main/java/kinoko/database/AllianceAccessor.java new file mode 100644 index 000000000..9050fef66 --- /dev/null +++ b/src/main/java/kinoko/database/AllianceAccessor.java @@ -0,0 +1,18 @@ +package kinoko.database; + +import kinoko.server.alliance.Alliance; + +import java.util.Optional; + +public interface AllianceAccessor { + Optional getAllianceById(int allianceId); + + boolean checkAllianceNameAvailable(String name); + + boolean newAlliance(Alliance alliance); + + boolean saveAlliance(Alliance alliance); + + boolean deleteAlliance(int allianceId); + +} \ No newline at end of file diff --git a/src/main/java/kinoko/database/DatabaseConnector.java b/src/main/java/kinoko/database/DatabaseConnector.java index 9b4bf0e09..9972f5120 100644 --- a/src/main/java/kinoko/database/DatabaseConnector.java +++ b/src/main/java/kinoko/database/DatabaseConnector.java @@ -10,6 +10,8 @@ public interface DatabaseConnector { FriendAccessor getFriendAccessor(); GuildAccessor getGuildAccessor(); + + AllianceAccessor getAllianceAccessor(); GiftAccessor getGiftAccessor(); diff --git a/src/main/java/kinoko/database/DatabaseManager.java b/src/main/java/kinoko/database/DatabaseManager.java index 2808fd090..919374195 100644 --- a/src/main/java/kinoko/database/DatabaseManager.java +++ b/src/main/java/kinoko/database/DatabaseManager.java @@ -24,6 +24,10 @@ public static FriendAccessor friendAccessor() { public static GuildAccessor guildAccessor() { return connector.getGuildAccessor(); } + + public static AllianceAccessor allianceAccessor() { + return connector.getAllianceAccessor(); + } public static GiftAccessor giftAccessor() { return connector.getGiftAccessor(); diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index 85d417f29..cfa6406a4 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -10,6 +10,9 @@ import kinoko.world.user.User; import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import kinoko.server.alliance.Alliance; public final class GuildHQ extends ScriptHandler { public static final int GUILD_HEADQUARTERS = 200000301; @@ -155,18 +158,45 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("Only the party leader can form a Guild Union."); return; } - sm.sayNext("You can create a Guild Union if your party consists of two people."); - // TODO + if (!sm.checkParty(2, (user) -> {return user.getGuildRank() == GuildRank.MASTER;})) { + sm.sayNext("You can create a Guild Union if your party consists of two people."); + } + + if (!sm.askYesNo("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_ALLIANCE_COST)) { + sm.sayNext("You're not ready yet? Come back to me when you want to create an alliance."); + return; + } + if (!sm.addMoney(-GameConstants.CREATE_ALLIANCE_COST)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request } else if (answer == 3) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master can expand the number of guilds in the Union."); } - // TODO + + final int currentCapacity = sm.getUser().getGuildInfo().getAllianceMemberMax(); + if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { + sm.sayNext("Your alliance already reached the maximum capacity for guilds."); + return; + } + final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); + if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { + return; + } + if (!sm.addMoney(-expandCost)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request } else if (answer == 4) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master may disband the Guild Union."); } - // TODO + //TODO alliance submit request } } } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java new file mode 100644 index 000000000..a8a8bad2b --- /dev/null +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -0,0 +1,146 @@ +package kinoko.server.alliance; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import kinoko.server.guild.Guild; +import static kinoko.server.guild.Guild.EMPTY_MEMBER; +import static kinoko.server.guild.Guild.MEMBER_COMPARATOR; +import kinoko.server.guild.GuildMember; +import kinoko.server.packet.OutPacket; +import kinoko.util.Encodable; +import kinoko.util.Lockable; +import kinoko.world.GameConstants; + +/** + * Alliance instance managed by CentralServerNode. + */ +public final class Alliance implements Encodable, Lockable { + + private final int allianceId; + private final String allianceName; + private final List gradeNames; + private int memberMax; + + private final Map guildInvites; // invitee ID -> inviter ID + private final Map guilds; // guild ID -> guild + + private final Lock lock = new ReentrantLock(); + + public Alliance(int allianceId, String allianceName) { + this.allianceId = allianceId; + this.allianceName = allianceName; + this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); + this.guildInvites = new HashMap<>(); + this.guilds = new HashMap<>(); + this.memberMax = GameConstants.GUILD_CAPACITY_MIN; + } + + public int getMemberMax() { + return memberMax; + } + + public void setMemberMax(int memberMax) { + this.memberMax = memberMax; + } + + public int getAllianceId() { + return allianceId; + } + + public String getAllianceName() { + return allianceName; + } + + public List getAllianceMembers() { + List list = new ArrayList<>(100); + + for (Guild guild : guilds.values()) { + list.addAll(guild.getGuildMembersUnsorted()); + } + + return list.stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level + .toList(); + } + + public List getMemberIds() { + return getAllianceMembers().keySet().stream().toList(); + } + + public List getMemberIds(int exceptId) { + return getAllianceMembers().keySet().stream() + .filter((id) -> id != exceptId) + .toList(); + } + + public boolean hasMember(int characterId) { + for (Guild guild : guilds.values()) { + if (guild.hasMember(characterId)) { + return true; + } + } + + return false; + } + + public GuildMember getMember(int characterId) { + for (Guild guild : guilds.values()) { + GuildMember member = guild.getMember(characterId); + if (member != EMPTY_MEMBER) { + return member; + } + } + + return EMPTY_MEMBER; + } + + public boolean hasGuild(int guildId) { + return guilds.containsKey(guildId); + } + + public Guild getGuild(int guildId) { + return guilds.get(guildId); + } + + public boolean canAddGuild(int guildId) { + if (guilds.size() >= getMemberMax()) { + return false; + } + return !guilds.containsKey(guildId); + } + + public boolean addGuild(Guild guild) { + if (!canAddGuild(guild.getGuildId())) { + return false; + } + guilds.put(guild.getGuildId(), guild); + return true; + } + + public void removeGuild(Guild guild) { + guilds.remove(guild.getGuildId()); + } + + public String getGradeNames(int rank) { + return gradeNames.get(rank - 1); + } + + @Override + public void encode(OutPacket outPacket) { + // TODO + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +} diff --git a/src/main/java/kinoko/server/alliance/AllianceStorage.java b/src/main/java/kinoko/server/alliance/AllianceStorage.java new file mode 100644 index 000000000..9a30b1997 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/AllianceStorage.java @@ -0,0 +1,41 @@ +package kinoko.server.alliance; + +import kinoko.database.DatabaseManager; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public final class AllianceStorage { + private final ConcurrentHashMap allianceMap = new ConcurrentHashMap<>(); + + public boolean addAlliance(Alliance alliance) { + if (allianceMap.containsKey(alliance.getAllianceId())) { + return false; + } + if (!DatabaseManager.allianceAccessor().newAlliance(alliance)) { + return false; + } + allianceMap.put(alliance.getAllianceId(), alliance); + return true; + } + + public boolean removeAlliance(Alliance alliance) { + if (!DatabaseManager.allianceAccessor().deleteAlliance(alliance.getAllianceId())) { + return false; + } + allianceMap.remove(alliance.getAllianceId()); + return true; + } + + public Optional getAllianceById(int allianceId) { + if (allianceId == 0) { + return Optional.empty(); + } + if (allianceMap.containsKey(allianceId)) { + return Optional.of(allianceMap.get(allianceId)); + } + final Optional guildResult = DatabaseManager.allianceAccessor().getAllianceById(allianceId); + guildResult.ifPresent(guild -> allianceMap.put(allianceId, guild)); + return guildResult; + } +} diff --git a/src/main/java/kinoko/server/guild/Guild.java b/src/main/java/kinoko/server/guild/Guild.java index 1f0d176b2..a10c3a564 100644 --- a/src/main/java/kinoko/server/guild/Guild.java +++ b/src/main/java/kinoko/server/guild/Guild.java @@ -77,6 +77,10 @@ public List getGuildMembers() { return guildMembers.values().stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level .toList(); } + + public List getGuildMembersUnsorted() { + return guildMembers.values().stream().toList(); + } public List getBoardEntries() { return boardEntries; diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 5a620ede0..5fdac5f87 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -4,6 +4,8 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import kinoko.packet.CentralPacket; +import kinoko.server.alliance.Alliance; +import kinoko.server.alliance.AllianceStorage; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildMember; import kinoko.server.guild.GuildRank; @@ -38,6 +40,7 @@ public final class CentralServerNode extends Node { private final MessengerStorage messengerStorage = new MessengerStorage(); private final PartyStorage partyStorage = new PartyStorage(); private final GuildStorage guildStorage = new GuildStorage(); + private final AllianceStorage allianceStorage = new AllianceStorage(); private final CompletableFuture initializeFuture = new CompletableFuture<>(); private final CompletableFuture shutdownFuture = new CompletableFuture<>(); private final int port; @@ -190,6 +193,26 @@ public Optional getGuildById(int guildId) { return guildStorage.getGuildById(guildId); } + // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- + + public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { + final Alliance alliance = new Alliance(allianceId, allianceName); + if (!allianceStorage.addAlliance(alliance)) { + return Optional.empty(); + } + return Optional.of(alliance); + } + + public boolean removeAlliance(Alliance alliance) { + return allianceStorage.removeAlliance(alliance); + } + + public Optional getAllianceById(int allianceId) { + if (allianceId == 0) { + return Optional.empty(); + } + return allianceStorage.getAllianceById(allianceId); + } // OVERRIDES ------------------------------------------------------------------------------------------------------- diff --git a/src/main/java/kinoko/world/GameConstants.java b/src/main/java/kinoko/world/GameConstants.java index 18617e519..6fd965820 100644 --- a/src/main/java/kinoko/world/GameConstants.java +++ b/src/main/java/kinoko/world/GameConstants.java @@ -64,6 +64,9 @@ public final class GameConstants { public static final int CREATE_EMBLEM_COST = 500_000; public static final int DELETE_EMBLEM_COST = 1_000_000; public static final int CREATE_UNION_COST = 5_000_000; + public static final int UNION_GRADE_MAX = GUILD_GRADE_NAMES.size(); + public static final int UNION_CAPACITY_MIN = 2; + public static final int UNION_CAPACITY_MAX = 5; public static final int MACRO_SYS_DATA_SIZE = 5; public static final int MACRO_SKILL_COUNT = 3; @@ -208,6 +211,16 @@ public static int getGuildExpandCost(int memberMax) { return 5_000_000; } } + + public static int getUnionExpandCost(int memberMax) { + if (memberMax < 3) { + return 500_000; + } else if (memberMax < 4) { + return 1_500_000; + } else { + return 5_000_000; + } + } public static Tuple getMoneyForMobLevel(int level) { // Modern maple values, probably not accurate diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 3cb351d12..e9b2b8563 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -18,6 +18,7 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; + private final int allianceMemberMax; public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { this.guildId = guildId; @@ -71,6 +72,10 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } + + public int getAllianceMemberMax() { + return allianceMemberMax; + } @Override public void encode(OutPacket outPacket) { From 5accb0698d18682d5ce3b4f7b314027fcf38c10d Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:23:20 -0300 Subject: [PATCH 02/21] Update GuildHQ.java --- src/main/java/kinoko/script/GuildHQ.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index cfa6406a4..ea7a05a33 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -42,7 +42,7 @@ public static void guild_proc(ScriptManager sm) { return; } if (sm.getLevel() < 101) { - sm.sayNext("Hey, you level is a bit low to be a guild leader. You need to be at least level 101 to create a guild."); + sm.sayNext("Hey, your level is a bit low to be a guild leader. You need to be at least level 101 to create a guild."); return; } if (!sm.canAddMoney(-GameConstants.CREATE_GUILD_COST)) { From 2344375d5ceac61567d6ceb56cf31ce203ad9d25 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:37:19 -0300 Subject: [PATCH 03/21] Attend fixes for compilation Prepare for compile. --- .../kinoko/database/cassandra/CassandraConnector.java | 6 ++++++ src/main/java/kinoko/script/GuildHQ.java | 4 ++-- src/main/java/kinoko/server/alliance/Alliance.java | 10 +++++----- src/main/java/kinoko/world/user/GuildInfo.java | 10 ++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/kinoko/database/cassandra/CassandraConnector.java b/src/main/java/kinoko/database/cassandra/CassandraConnector.java index 44ae4a0ee..a9c2db734 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraConnector.java +++ b/src/main/java/kinoko/database/cassandra/CassandraConnector.java @@ -47,6 +47,7 @@ public final class CassandraConnector implements DatabaseConnector { private CharacterAccessor characterAccessor; private FriendAccessor friendAccessor; private GuildAccessor guildAccessor; + private AllianceAccessor allianceAccessor; private GiftAccessor giftAccessor; private MemoAccessor memoAccessor; @@ -101,6 +102,11 @@ public FriendAccessor getFriendAccessor() { public GuildAccessor getGuildAccessor() { return guildAccessor; } + + @Override + public AllianceAccessor getAllianceAccessor() { + return allianceAccessor; + } @Override public GiftAccessor getGiftAccessor() { diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index ea7a05a33..2b5da38e2 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -162,11 +162,11 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("You can create a Guild Union if your party consists of two people."); } - if (!sm.askYesNo("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_ALLIANCE_COST)) { + if (!sm.askYesNo(String.format("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_UNION_COST))) { sm.sayNext("You're not ready yet? Come back to me when you want to create an alliance."); return; } - if (!sm.addMoney(-GameConstants.CREATE_ALLIANCE_COST)) { + if (!sm.addMoney(-GameConstants.CREATE_UNION_COST)) { sm.sayNext("You don't have enough mesos for this request."); return; } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index a8a8bad2b..bf10860d8 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -66,13 +66,13 @@ public List getAllianceMembers() { .toList(); } - public List getMemberIds() { - return getAllianceMembers().keySet().stream().toList(); + public List getMemberIds() { + return getAllianceMembers().stream().toList(); } - public List getMemberIds(int exceptId) { - return getAllianceMembers().keySet().stream() - .filter((id) -> id != exceptId) + public List getMemberIds(int exceptId) { + return getAllianceMembers().stream() + .filter((member) -> member.getCharacterId() != exceptId) .toList(); } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index e9b2b8563..62164f5f4 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -7,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", 0); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -20,7 +20,7 @@ public final class GuildInfo implements Encodable { private final String allianceName; private final int allianceMemberMax; - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, int allianceMemberMax) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -31,6 +31,7 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceMemberMax = allianceMemberMax; } public int getGuildId() { @@ -102,7 +103,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, 2); } public static GuildInfo from(Guild guild, int characterId) { @@ -116,7 +117,8 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName() + guild.getAllianceName(), + 2 ); } } From 3df268760b755bb44b5a4b4bae26795b26ceb9bd Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Sep 2025 18:55:08 -0300 Subject: [PATCH 04/21] First step on Alliances request and results A stretch on request and results of Alliances. --- .../kinoko/handler/user/GuildHandler.java | 22 +++++++++++- .../java/kinoko/packet/world/GuildPacket.java | 1 - src/main/java/kinoko/script/GuildHQ.java | 36 +++++++++++-------- src/main/java/kinoko/server/Server.java | 5 +++ .../java/kinoko/world/user/GuildInfo.java | 17 +++------ 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index e319afbfa..41774cd93 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -220,7 +220,27 @@ public static void handleGuildResult(User user, InPacket inPacket) { public static void handleAllianceRequest(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceRequestType requestType = AllianceRequestType.getByValue(type); - // TODO + + if (!user.hasGuild() || user.hasAlliance()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; + } + + if (requestType == AllianceRequestType.Create) { + // send CreateDone + } else if (requestType == AllianceRequestType.Invite) { + // send Invite_Done + } else if (requestType == AllianceRequestType.Join) { + if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { + // send InviteGuild_BlockedByOpt + return; + } + + if () { + // send InviteGuild_AlreadyInvited + return; + } + } } @Handler(InHeader.AllianceResult) diff --git a/src/main/java/kinoko/packet/world/GuildPacket.java b/src/main/java/kinoko/packet/world/GuildPacket.java index ae80fb2c3..1f70b2375 100644 --- a/src/main/java/kinoko/packet/world/GuildPacket.java +++ b/src/main/java/kinoko/packet/world/GuildPacket.java @@ -86,7 +86,6 @@ public static OutPacket createNewGuildBeginner() { return GuildPacket.of(GuildResultType.CreateNewGuild_Beginner); } - public static OutPacket createNewGuildUnknown() { return GuildPacket.of(GuildResultType.CreateNewGuild_Unknown); } diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index 2b5da38e2..d88b3f513 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -12,6 +12,8 @@ import java.util.Map; import java.util.Optional; import java.util.function.Predicate; + +import kinoko.server.Server; import kinoko.server.alliance.Alliance; public final class GuildHQ extends ScriptHandler { @@ -177,21 +179,27 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("Only the Guild Union Master can expand the number of guilds in the Union."); } - final int currentCapacity = sm.getUser().getGuildInfo().getAllianceMemberMax(); - if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { - sm.sayNext("Your alliance already reached the maximum capacity for guilds."); - return; - } - final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); - if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { - return; - } - if (!sm.addMoney(-expandCost)) { - sm.sayNext("You don't have enough mesos for this request."); - return; + Optional alliance = Server.getCentralServerNode().getAllianceById(sm.getUser().getAllianceId()); + if(!alliance.isEmpty()) { + final int currentCapacity = alliance.get().getMemberMax(); + if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { + sm.sayNext("Your alliance already reached the maximum capacity for guilds."); + return; + } + final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); + if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { + return; + } + if (!sm.addMoney(-expandCost)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request + } else { + sm.sayNext("Your alliance is nonexistent."); + return; } - - //TODO alliance submit request } else if (answer == 4) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master may disband the Guild Union."); diff --git a/src/main/java/kinoko/server/Server.java b/src/main/java/kinoko/server/Server.java index 2054053a8..8d1f6df37 100644 --- a/src/main/java/kinoko/server/Server.java +++ b/src/main/java/kinoko/server/Server.java @@ -114,4 +114,9 @@ private static void shutdown() throws Exception { DatabaseManager.shutdown(); LogManager.shutdown(); } + + public static CentralServerNode getCentralServerNode() { + return centralServerNode; + } + } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 62164f5f4..777188803 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -7,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", 0); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -18,9 +18,8 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; - private final int allianceMemberMax; - - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, int allianceMemberMax) { + + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -31,7 +30,6 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; - this.allianceMemberMax = allianceMemberMax; } public int getGuildId() { @@ -73,10 +71,6 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - - public int getAllianceMemberMax() { - return allianceMemberMax; - } @Override public void encode(OutPacket outPacket) { @@ -103,7 +97,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, 2); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); } public static GuildInfo from(Guild guild, int characterId) { @@ -117,8 +111,7 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName(), - 2 + guild.getAllianceName() ); } } From e553e0b8fec68d3930196cdba7dccaec668f171d Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Wed, 24 Sep 2025 18:50:37 -0300 Subject: [PATCH 05/21] Step 2 on Alliances definition Added CRUD for Alliances --- src/main/java/kinoko/database/IdAccessor.java | 2 + .../cassandra/CassandraIdAccessor.java | 5 ++ .../database/cassandra/table/IdTable.java | 1 + .../kinoko/handler/user/GuildHandler.java | 70 ++++++++++++++++--- .../java/kinoko/server/alliance/Alliance.java | 10 ++- .../kinoko/server/node/CentralServerNode.java | 5 +- .../java/kinoko/world/user/GuildInfo.java | 4 +- 7 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/main/java/kinoko/database/IdAccessor.java b/src/main/java/kinoko/database/IdAccessor.java index 399887c96..ab2ba23c1 100644 --- a/src/main/java/kinoko/database/IdAccessor.java +++ b/src/main/java/kinoko/database/IdAccessor.java @@ -10,6 +10,8 @@ public interface IdAccessor { Optional nextPartyId(); Optional nextGuildId(); + + Optional nextAllianceId(); Optional nextMemoId(); } diff --git a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java index 50b9249d3..a5777576d 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java +++ b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java @@ -59,6 +59,11 @@ public synchronized Optional nextPartyId() { public synchronized Optional nextGuildId() { return getNextId(IdTable.GUILD_ID); } + + @Override + public synchronized Optional nextAllianceId() { + return getNextId(IdTable.ALLIANCE_ID); + } @Override public synchronized Optional nextMemoId() { diff --git a/src/main/java/kinoko/database/cassandra/table/IdTable.java b/src/main/java/kinoko/database/cassandra/table/IdTable.java index f400149d7..3107be342 100644 --- a/src/main/java/kinoko/database/cassandra/table/IdTable.java +++ b/src/main/java/kinoko/database/cassandra/table/IdTable.java @@ -17,6 +17,7 @@ public final class IdTable { public static final String CHARACTER_ID = "character_id"; public static final String PARTY_ID = "party_id"; public static final String GUILD_ID = "guild_id"; + public static final String ALLIANCE_ID = "alliance_id"; public static final String MEMO_ID = "memo_id"; private static final String tableName = "id_table"; diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index 41774cd93..f838a0d44 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -6,11 +6,15 @@ import kinoko.packet.world.MessagePacket; import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; +import kinoko.server.Server; +import kinoko.server.alliance.Alliance; import kinoko.server.alliance.AllianceRequestType; import kinoko.server.alliance.AllianceResultType; +import kinoko.server.alliance.AllianceStorage; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; +import kinoko.server.user.RemoteUser; import kinoko.world.GameConstants; import kinoko.world.item.InventoryManager; import kinoko.world.user.User; @@ -227,27 +231,77 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { } if (requestType == AllianceRequestType.Create) { - // send CreateDone - } else if (requestType == AllianceRequestType.Invite) { - // send Invite_Done + // send CreateDone to alliance members + } else if (requestType == AllianceRequestType.Withdraw) { + // send Withdraw_Done to guild members + } else if (requestType == AllianceRequestType.Invite) { + if (user.getGuildRank() != GuildRank.MASTER) { + user.write(GuildPacket.serverMsg("You cannot invite other guilds since you are not the master of the guild.")); + return; + } + + if (false) { + // send InviteGuild_AlreadyInvited + return; + } } else if (requestType == AllianceRequestType.Join) { if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { // send InviteGuild_BlockedByOpt return; } - if () { - // send InviteGuild_AlreadyInvited - return; - } + // send Invite_Done + } + } + + // --- Alliance CRUD --- + + private static void create(String allianceName, User user) { + // Resolve new alliance ID + final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); + if (allianceIdResult.isEmpty()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; } + + int allianceId = allianceIdResult.get(); + Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); + if (alliance.isEmpty()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; + } } + + private static void addGuild(Alliance alliance, Guild guild) { + alliance.lock(); + try { + alliance.addGuild(guild); + } finally { + alliance.unlock(); + } + } + + private static void removeGuild(Alliance alliance, Guild guild) { + alliance.lock(); + try { + alliance.removeGuild(guild); + } finally { + alliance.unlock(); + } + } + + private static void destroy(Alliance alliance) { + Server.getCentralServerNode().removeAlliance(alliance); + } + + // --------------------- @Handler(InHeader.AllianceResult) public static void handleAllianceResult(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceResultType resultType = AllianceResultType.getByValue(type); - // TODO + + //TODO } @Handler(InHeader.GuildBBS) diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index bf10860d8..74618ed92 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -22,6 +22,7 @@ public final class Alliance implements Encodable, Lockable { private final int allianceId; private final String allianceName; + private final int allianceLordId; private final List gradeNames; private int memberMax; @@ -30,13 +31,14 @@ public final class Alliance implements Encodable, Lockable { private final Lock lock = new ReentrantLock(); - public Alliance(int allianceId, String allianceName) { + public Alliance(int allianceId, String allianceName, int allianceLordId) { this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceLordId = allianceLordId; this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); this.guildInvites = new HashMap<>(); this.guilds = new HashMap<>(); - this.memberMax = GameConstants.GUILD_CAPACITY_MIN; + this.memberMax = GameConstants.UNION_CAPACITY_MIN; } public int getMemberMax() { @@ -55,6 +57,10 @@ public String getAllianceName() { return allianceName; } + public int getLordId() { + return allianceLordId; + } + public List getAllianceMembers() { List list = new ArrayList<>(100); diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 5fdac5f87..727f130f4 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -23,6 +23,7 @@ import kinoko.server.party.PartyStorage; import kinoko.server.user.RemoteUser; import kinoko.server.user.UserStorage; +import kinoko.world.user.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -195,8 +196,8 @@ public Optional getGuildById(int guildId) { // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { - final Alliance alliance = new Alliance(allianceId, allianceName); + public Optional createNewAlliance(int allianceId, String allianceName, User user) { + final Alliance alliance = new Alliance(allianceId, allianceName, user.getCharacterId()); if (!allianceStorage.addAlliance(alliance)) { return Optional.empty(); } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 777188803..68d9a0738 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -1,5 +1,7 @@ package kinoko.world.user; +import kinoko.server.Server; +import kinoko.server.alliance.Alliance; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildRank; import kinoko.server.packet.InPacket; @@ -71,7 +73,7 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); From 9d13f506983423e1598bc5bddcb70109a7d922c5 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Thu, 25 Sep 2025 08:25:50 -0300 Subject: [PATCH 06/21] Third step on Alliances checkout Create methods for alliance management. --- .../kinoko/handler/user/GuildHandler.java | 2 ++ .../java/kinoko/server/alliance/Alliance.java | 20 ++++++++++++++++++- .../java/kinoko/world/user/GuildInfo.java | 17 ++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index f838a0d44..b002ff180 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -261,6 +261,7 @@ private static void create(String allianceName, User user) { final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); if (allianceIdResult.isEmpty()) { user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + log.error("Database error: allianceId"); return; } @@ -268,6 +269,7 @@ private static void create(String allianceName, User user) { Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); if (alliance.isEmpty()) { user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + log.error("Could not create alliance '{}' for user '{}'", allianceName, user.getCharacterName()); return; } } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 74618ed92..26194b21b 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -22,9 +22,10 @@ public final class Alliance implements Encodable, Lockable { private final int allianceId; private final String allianceName; - private final int allianceLordId; + private int allianceLordId; private final List gradeNames; private int memberMax; + private String notice; private final Map guildInvites; // invitee ID -> inviter ID private final Map guilds; // guild ID -> guild @@ -39,6 +40,7 @@ public Alliance(int allianceId, String allianceName, int allianceLordId) { this.guildInvites = new HashMap<>(); this.guilds = new HashMap<>(); this.memberMax = GameConstants.UNION_CAPACITY_MIN; + this.notice = ""; } public int getMemberMax() { @@ -61,6 +63,10 @@ public int getLordId() { return allianceLordId; } + public void setLordId(int characterId) { + allianceLordId = characterId; + } + public List getAllianceMembers() { List list = new ArrayList<>(100); @@ -134,6 +140,18 @@ public String getGradeNames(int rank) { return gradeNames.get(rank - 1); } + public String setGradeName(int rank, String name) { + return gradeNames.set(rank - 1, name); + } + + public String getNotice() { + return notice; + } + + public void setNotice(String str) { + notice = str; + } + @Override public void encode(OutPacket outPacket) { // TODO diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 68d9a0738..c5556d423 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -1,7 +1,5 @@ package kinoko.world.user; -import kinoko.server.Server; -import kinoko.server.alliance.Alliance; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildRank; import kinoko.server.packet.InPacket; @@ -9,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", GuildRank.NONE); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -20,8 +18,9 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; + private final GuildRank allianceRank; - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, GuildRank allianceRank) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -32,6 +31,7 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceRank = allianceRank; } public int getGuildId() { @@ -74,6 +74,10 @@ public String getAllianceName() { return allianceName; } + public GuildRank getAllianceRank() { + return allianceRank; + } + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); @@ -99,7 +103,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, GuildRank.NONE); } public static GuildInfo from(Guild guild, int characterId) { @@ -113,7 +117,8 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName() + guild.getAllianceName(), + guild.getMember(characterId).getAllianceRank() ); } } From 9ffdc8c9c7eef4831718f73c052828470378100e Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Thu, 25 Sep 2025 18:35:39 -0300 Subject: [PATCH 07/21] Alliances packets to CentralServerNode Made amends with proper packets to CentralServerNode on Alliances instead of calling Server direcly. --- .../kinoko/handler/user/GuildHandler.java | 60 +---- .../java/kinoko/packet/CentralPacket.java | 8 + .../java/kinoko/server/alliance/Alliance.java | 22 +- .../server/alliance/AllianceRequest.java | 219 ++++++++++++++++++ .../server/alliance/AllianceResultType.java | 10 +- .../kinoko/server/header/CentralHeader.java | 1 + .../server/netty/CentralServerHandler.java | 117 ++++++++++ .../kinoko/server/node/CentralServerNode.java | 22 +- .../kinoko/server/node/ChannelServerNode.java | 5 + src/main/java/kinoko/server/party/Party.java | 2 +- .../java/kinoko/server/user/RemoteUser.java | 17 +- .../java/kinoko/world/user/CharacterData.java | 9 + 12 files changed, 426 insertions(+), 66 deletions(-) create mode 100644 src/main/java/kinoko/server/alliance/AllianceRequest.java diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index b002ff180..d897c98ca 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -7,10 +7,7 @@ import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; import kinoko.server.Server; -import kinoko.server.alliance.Alliance; -import kinoko.server.alliance.AllianceRequestType; -import kinoko.server.alliance.AllianceResultType; -import kinoko.server.alliance.AllianceStorage; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; @@ -231,8 +228,11 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { } if (requestType == AllianceRequestType.Create) { + String allianceName = ""; + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.createNewAlliance(allianceName)); // send CreateDone to alliance members } else if (requestType == AllianceRequestType.Withdraw) { + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); // send Withdraw_Done to guild members } else if (requestType == AllianceRequestType.Invite) { if (user.getGuildRank() != GuildRank.MASTER) { @@ -250,53 +250,17 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { return; } + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.joinAlliance(user.getAllianceId())); // send Invite_Done + } else if (requestType == AllianceRequestType.Destroy) { + if (!user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER) { + user.write(GuildPacket.serverMsg("You cannot destroy in alliance since you are not the master of the alliance.")); + return; + } + + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); } } - - // --- Alliance CRUD --- - - private static void create(String allianceName, User user) { - // Resolve new alliance ID - final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); - if (allianceIdResult.isEmpty()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - log.error("Database error: allianceId"); - return; - } - - int allianceId = allianceIdResult.get(); - Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); - if (alliance.isEmpty()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - log.error("Could not create alliance '{}' for user '{}'", allianceName, user.getCharacterName()); - return; - } - } - - private static void addGuild(Alliance alliance, Guild guild) { - alliance.lock(); - try { - alliance.addGuild(guild); - } finally { - alliance.unlock(); - } - } - - private static void removeGuild(Alliance alliance, Guild guild) { - alliance.lock(); - try { - alliance.removeGuild(guild); - } finally { - alliance.unlock(); - } - } - - private static void destroy(Alliance alliance) { - Server.getCentralServerNode().removeAlliance(alliance); - } - - // --------------------- @Handler(InHeader.AllianceResult) public static void handleAllianceResult(User user, InPacket inPacket) { diff --git a/src/main/java/kinoko/packet/CentralPacket.java b/src/main/java/kinoko/packet/CentralPacket.java index 2c287faa7..5aba02319 100644 --- a/src/main/java/kinoko/packet/CentralPacket.java +++ b/src/main/java/kinoko/packet/CentralPacket.java @@ -1,5 +1,6 @@ package kinoko.packet; +import kinoko.server.alliance.AllianceRequest; import kinoko.server.guild.GuildBoardRequest; import kinoko.server.guild.GuildRequest; import kinoko.server.header.CentralHeader; @@ -238,6 +239,13 @@ public static OutPacket guildResult(int characterId, GuildInfo guildInfo) { } return outPacket; } + + public static OutPacket allianceRequest(int characterId, AllianceRequest allianceRequest) { + final OutPacket outPacket = OutPacket.of(CentralHeader.AllianceRequest); + outPacket.encodeInt(characterId); + allianceRequest.encode(outPacket); + return outPacket; + } public static OutPacket boardRequest(int characterId, GuildBoardRequest boardRequest) { final OutPacket outPacket = OutPacket.of(CentralHeader.BoardRequest); diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 26194b21b..4b1135e4f 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -125,15 +125,25 @@ public boolean canAddGuild(int guildId) { } public boolean addGuild(Guild guild) { - if (!canAddGuild(guild.getGuildId())) { - return false; - } - guilds.put(guild.getGuildId(), guild); - return true; + this.lock(); + try { + if (!canAddGuild(guild.getGuildId())) { + return false; + } + guilds.put(guild.getGuildId(), guild); + return true; + } finally { + this.unlock(); + } } public void removeGuild(Guild guild) { - guilds.remove(guild.getGuildId()); + this.lock(); + try { + guilds.remove(guild.getGuildId()); + } finally { + this.unlock(); + } } public String getGradeNames(int rank) { diff --git a/src/main/java/kinoko/server/alliance/AllianceRequest.java b/src/main/java/kinoko/server/alliance/AllianceRequest.java new file mode 100644 index 000000000..a146188e7 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -0,0 +1,219 @@ +package kinoko.server.alliance; + +import kinoko.server.guild.GuildRank; +import kinoko.server.packet.InPacket; +import kinoko.server.packet.OutPacket; +import kinoko.util.Encodable; +import kinoko.world.GameConstants; + +import java.util.ArrayList; +import java.util.List; + +public final class AllianceRequest implements Encodable { + private final AllianceRequestType requestType; + private int allianceId; + private String allianceName; + private String allianceNotice; + private List gradeNames; + private int memberMax; + + private int inviterId; + private int targetId; + private String targetName; + private GuildRank allianceRank; + + public AllianceRequest(AllianceRequestType requestType) { + this.requestType = requestType; + } + + public AllianceRequestType getRequestType() { + return requestType; + } + + public int getAllianceId() { + return allianceId; + } + + public String getAllianceName() { + return allianceName; + } + + public String getAllianceNotice() { + return allianceNotice; + } + + public List getGradeNames() { + return gradeNames; + } + + public int getMemberMax() { + return memberMax; + } + + public int getInviterId() { + return inviterId; + } + + public int getTargetId() { + return targetId; + } + + public String getTargetName() { + return targetName; + } + + public GuildRank getAllianceRank() { + return allianceRank; + } + + @Override + public void encode(OutPacket outPacket) { + outPacket.encodeByte(requestType.getValue()); + switch (requestType) { + case Create -> { + outPacket.encodeInt(allianceId); + outPacket.encodeString(allianceName); + } + case Invite -> { + outPacket.encodeString(targetName); + } + case Join -> { + outPacket.encodeInt(inviterId); + } + case Kick -> { + outPacket.encodeInt(targetId); + outPacket.encodeString(targetName); + } + case UpdateMemberCountMax -> { + outPacket.encodeInt(memberMax); + } + case SetGradeName -> { + for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + outPacket.encodeString(gradeNames.get(i)); + } + } + case ChangeGrade -> { + outPacket.encodeInt(targetId); + outPacket.encodeByte(allianceRank.getValue()); + } + case SetNotice -> { + outPacket.encodeString(allianceNotice); + } + } + } + + public static AllianceRequest decode(InPacket inPacket) { + final int type = inPacket.decodeByte(); + final AllianceRequest request = new AllianceRequest(AllianceRequestType.getByValue(type)); + switch (request.requestType) { + case Load, Destroy, Withdraw -> { + request.allianceId = inPacket.decodeInt(); + } + case Create -> { + request.allianceId = inPacket.decodeInt(); + request.allianceName = inPacket.decodeString(); + } + case Invite -> { + request.targetName = inPacket.decodeString(); + } + case Join -> { + request.inviterId = inPacket.decodeInt(); + } + case Kick -> { + request.targetId = inPacket.decodeInt(); + request.targetName = inPacket.decodeString(); + } + case UpdateMemberCountMax -> { + request.memberMax = inPacket.decodeInt(); + } + case SetGradeName -> { + request.gradeNames = new ArrayList<>(); + for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + request.gradeNames.add(inPacket.decodeString()); + } + } + case ChangeGrade -> { + request.targetId = inPacket.decodeInt(); + request.allianceRank = GuildRank.getByValue(inPacket.decodeByte()); + } + case SetNotice -> { + request.allianceNotice = inPacket.decodeString(); + } + case null -> { + throw new IllegalStateException(String.format("Unknown alliance request type %d", type)); + } + default -> { + throw new IllegalStateException(String.format("Unhandled alliance request type %d", type)); + } + } + return request; + } + + public static AllianceRequest loadAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Load); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest createNewAlliance(String allianceName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Create); + request.allianceName = allianceName; + return request; + } + + public static AllianceRequest inviteAlliance(String targetName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); + request.targetName = targetName; + return request; + } + + public static AllianceRequest joinAlliance(int inviterId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Join); + request.inviterId = inviterId; + return request; + } + + public static AllianceRequest withdrawAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Withdraw); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest kickAlliance(int targetId, String targetName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Kick); + request.targetId = targetId; + request.targetName = targetName; + return request; + } + + public static AllianceRequest removeAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Destroy); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest incMaxMemberNum(int memberMax) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.UpdateMemberCountMax); + request.memberMax = memberMax; + return request; + } + + public static AllianceRequest setGradeName(List gradeNames) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetGradeName); + request.gradeNames = gradeNames; + return request; + } + + public static AllianceRequest setMemberGrade(int targetId, GuildRank allianceRank) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.ChangeGrade); + request.targetId = targetId; + request.allianceRank = allianceRank; + return request; + } + + public static AllianceRequest setNotice(String notice) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetNotice); + request.allianceNotice = notice; + return request; + } +} diff --git a/src/main/java/kinoko/server/alliance/AllianceResultType.java b/src/main/java/kinoko/server/alliance/AllianceResultType.java index 593d9326c..9b10d48d7 100644 --- a/src/main/java/kinoko/server/alliance/AllianceResultType.java +++ b/src/main/java/kinoko/server/alliance/AllianceResultType.java @@ -3,16 +3,16 @@ public enum AllianceResultType { // AllianceRes LoadDone(12), - LoadGuildDone(13), + LoadallianceDone(13), NotifyLoginOrLogout(14), CreateDone(15), Withdraw_Done(16), Withdraw_Failed(17), Invite_Done(18), Invite_Failed(19), - InviteGuild_BlockedByOpt(20), - InviteGuild_AlreadyInvited(21), - InviteGuild_Rejected(22), + Invitealliance_BlockedByOpt(20), + Invitealliance_AlreadyInvited(21), + Invitealliance_Rejected(22), UpdateAllianceInfo(23), ChangeLevelOrJob(24), ChangeMaster_Done(25), @@ -20,7 +20,7 @@ public enum AllianceResultType { ChangeGrade_Done(27), SetNotice_Done(28), Destroy_Done(29), - UpdateGuildInfo(30); + UpdateallianceInfo(30); private final int value; diff --git a/src/main/java/kinoko/server/header/CentralHeader.java b/src/main/java/kinoko/server/header/CentralHeader.java index 7e5b6fa21..13745c90b 100644 --- a/src/main/java/kinoko/server/header/CentralHeader.java +++ b/src/main/java/kinoko/server/header/CentralHeader.java @@ -33,6 +33,7 @@ public enum CentralHeader { PartyResult, GuildRequest, GuildResult, + AllianceRequest, BoardRequest; private static final List headers = List.of(values()); diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index fade81374..f6114d78f 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -8,6 +8,7 @@ import kinoko.packet.world.BroadcastPacket; import kinoko.packet.world.GuildPacket; import kinoko.packet.world.PartyPacket; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.CentralHeader; import kinoko.server.memo.Memo; @@ -29,6 +30,7 @@ import kinoko.util.Util; import kinoko.world.GameConstants; import kinoko.world.user.GuildInfo; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -76,6 +78,7 @@ protected void channelRead0(ChannelHandlerContext ctx, InPacket inPacket) { case MessengerRequest -> handleMessengerRequest(remoteServerNode, inPacket); case PartyRequest -> handlePartyRequest(remoteServerNode, inPacket); case GuildRequest -> handleGuildRequest(remoteServerNode, inPacket); + case AllianceRequest -> handleAllianceRequest(remoteServerNode, inPacket); case BoardRequest -> handleBoardRequest(remoteServerNode, inPacket); case null -> log.error("Central Server received an unknown opcode : {}", op); default -> log.error("Central Server received an unhandled header : {}", header); @@ -1049,6 +1052,120 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa } } } + + private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { + final int characterId = inPacket.decodeInt(); + final AllianceRequest allianceRequest = AllianceRequest.decode(inPacket); + // Resolve requester user + final Optional remoteUserResult = centralServerNode.getUserByCharacterId(characterId); + if (remoteUserResult.isEmpty()) { + log.error("Failed to resolve user with character ID : {} for AllianceRequest", characterId); + return; + } + String allianceName = allianceRequest.getAllianceName(); + final RemoteUser remoteUser = remoteUserResult.get(); + switch (allianceRequest.getRequestType()) { + case Create -> { + // Check if alliance name is available + if (!DatabaseManager.allianceAccessor().checkAllianceNameAvailable(allianceName)) { + // TODO The name is already in use... Please try other ones.... + return; + } + + // Resolve new alliance ID + final Optional allianceIdResult = DatabaseManager.idAccessor().nextAllianceId(); + if (allianceIdResult.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + log.error("Database error: allianceId"); + return; + } + + int allianceId = allianceIdResult.get(); + Optional alliance = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); + if (alliance.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + + log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); + return; + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance.get()); + } + case Invite -> { + + } + case Join -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.addGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + } + case Withdraw, Kick -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.removeGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + } + case Destroy -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + centralServerNode.removeAlliance(alliance); + } finally { + alliance.unlock(); + } + } + } + case UpdateMemberCountMax -> { + + } + case SetGradeName -> { + + } + case ChangeGrade -> { + + } + + case SetNotice -> { + + } + } + } private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { final int characterId = inPacket.decodeInt(); diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 727f130f4..21b362abf 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -196,12 +196,24 @@ public Optional getGuildById(int guildId) { // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - public Optional createNewAlliance(int allianceId, String allianceName, User user) { - final Alliance alliance = new Alliance(allianceId, allianceName, user.getCharacterId()); - if (!allianceStorage.addAlliance(alliance)) { - return Optional.empty(); + public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { + final Alliance alliance = new Alliance(allianceId, allianceName, remoteUser.getCharacterId()); + + final GuildMember member = GuildMember.from(remoteUser); + member.setGuildRank(GuildRank.MASTER); + + Optional guild = guildStorage.getGuildById(remoteUser.getGuildId()); + if (!guild.isEmpty()) { + if (!alliance.addGuild(guild.get())) { + throw new IllegalStateException("Could not add guild to alliance"); + } + + if (allianceStorage.addAlliance(alliance)) { + return Optional.of(alliance); + } } - return Optional.of(alliance); + + return Optional.empty(); } public boolean removeAlliance(Alliance alliance) { diff --git a/src/main/java/kinoko/server/node/ChannelServerNode.java b/src/main/java/kinoko/server/node/ChannelServerNode.java index aa6b93406..0149e3dc4 100644 --- a/src/main/java/kinoko/server/node/ChannelServerNode.java +++ b/src/main/java/kinoko/server/node/ChannelServerNode.java @@ -6,6 +6,7 @@ import kinoko.packet.CentralPacket; import kinoko.server.ServerConfig; import kinoko.server.ServerConstants; +import kinoko.server.alliance.AllianceRequest; import kinoko.server.event.EventManager; import kinoko.server.event.EventState; import kinoko.server.event.EventType; @@ -216,6 +217,10 @@ public void submitPartyRequest(User user, PartyRequest partyRequest) { public void submitGuildRequest(User user, GuildRequest guildRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.guildRequest(user.getCharacterId(), guildRequest)); } + + public void submitAllianceRequest(User user, AllianceRequest allianceRequest) { + centralClientFuture.channel().writeAndFlush(CentralPacket.allianceRequest(user.getCharacterId(), allianceRequest)); + } public void submitBoardRequest(User user, GuildBoardRequest boardRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.boardRequest(user.getCharacterId(), boardRequest)); diff --git a/src/main/java/kinoko/server/party/Party.java b/src/main/java/kinoko/server/party/Party.java index ed7529a16..75d69c409 100644 --- a/src/main/java/kinoko/server/party/Party.java +++ b/src/main/java/kinoko/server/party/Party.java @@ -18,7 +18,7 @@ * the instance stored in UserStorage. */ public final class Party implements Encodable, Lockable { - private static final RemoteUser EMPTY_MEMBER = new RemoteUser(0, 0, "", 0, 0, GameConstants.CHANNEL_OFFLINE, GameConstants.UNDEFINED_FIELD_ID, 0, 0, 0, RemoteTownPortal.EMPTY); + private static final RemoteUser EMPTY_MEMBER = new RemoteUser(0, 0, "", 0, 0, GameConstants.CHANNEL_OFFLINE, GameConstants.UNDEFINED_FIELD_ID, 0, 0, 0, 0, RemoteTownPortal.EMPTY); private final Lock lock = new ReentrantLock(); private final int partyId; private final List partyMembers; diff --git a/src/main/java/kinoko/server/user/RemoteUser.java b/src/main/java/kinoko/server/user/RemoteUser.java index ba8971452..4c34df1a7 100644 --- a/src/main/java/kinoko/server/user/RemoteUser.java +++ b/src/main/java/kinoko/server/user/RemoteUser.java @@ -16,9 +16,10 @@ public final class RemoteUser implements Encodable { private int messengerId; private int partyId; private int guildId; + private int allianceId; private RemoteTownPortal townPortal; - public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, RemoteTownPortal townPortal) { + public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, int allianceId, RemoteTownPortal townPortal) { this.accountId = accountId; this.characterId = characterId; this.characterName = characterName; @@ -29,6 +30,7 @@ public RemoteUser(int accountId, int characterId, String characterName, int leve this.messengerId = messengerId; this.partyId = partyId; this.guildId = guildId; + this.allianceId = allianceId; this.townPortal = townPortal; } @@ -91,6 +93,14 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } + + public int getAllianceId() { + return guildId; + } + + public void setAllianceId(int allianceId) { + this.allianceId = allianceId; + } public RemoteTownPortal getTownPortal() { return townPortal != null ? townPortal : RemoteTownPortal.EMPTY; @@ -113,6 +123,7 @@ public String toString() { ", messengerId=" + messengerId + ", partyId=" + partyId + ", guildId=" + guildId + + ", allianceId=" + allianceId + ", townPortal=" + townPortal + '}'; } @@ -129,6 +140,7 @@ public void encode(OutPacket outPacket) { outPacket.encodeInt(messengerId); outPacket.encodeInt(partyId); outPacket.encodeInt(guildId); + outPacket.encodeInt(allianceId); outPacket.encodeByte(townPortal != null); if (townPortal != null) { townPortal.encode(outPacket); @@ -146,6 +158,7 @@ public static RemoteUser decode(InPacket inPacket) { final int messengerId = inPacket.decodeInt(); final int partyId = inPacket.decodeInt(); final int guildId = inPacket.decodeInt(); + final int allianceId = inPacket.decodeInt(); final RemoteTownPortal townPortal = inPacket.decodeBoolean() ? RemoteTownPortal.decode(inPacket) : null; return new RemoteUser( accountId, @@ -158,6 +171,7 @@ public static RemoteUser decode(InPacket inPacket) { messengerId, partyId, guildId, + allianceId, townPortal ); } @@ -174,6 +188,7 @@ public static RemoteUser from(User user) { user.getMessengerId(), user.getCharacterData().getPartyId(), user.getCharacterData().getGuildId(), + user.getCharacterData().getAllianceId(), user.getTownPortal() != null ? RemoteTownPortal.from(user.getTownPortal()) : null ); } diff --git a/src/main/java/kinoko/world/user/CharacterData.java b/src/main/java/kinoko/world/user/CharacterData.java index ed9e8e25a..0fe4d7220 100644 --- a/src/main/java/kinoko/world/user/CharacterData.java +++ b/src/main/java/kinoko/world/user/CharacterData.java @@ -39,6 +39,7 @@ public final class CharacterData implements Encodable { private int friendMax; private int partyId; private int guildId; + private int allianceId; private Instant creationTime; private Instant maxLevelTime; @@ -161,6 +162,14 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } + + public int getAllianceId() { + return guildId; + } + + public void setAllianceId(int allianceId) { + this.allianceId = allianceId; + } public Instant getCreationTime() { return creationTime; From bd377c9bef9c7066dd1921fa2b34f500628b55c7 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:23:15 -0300 Subject: [PATCH 08/21] Introduce union Alliance server-side implementation. --- .../kinoko/database/AllianceAccessor.java | 18 +++ .../kinoko/database/DatabaseConnector.java | 2 + .../java/kinoko/database/DatabaseManager.java | 4 + src/main/java/kinoko/script/GuildHQ.java | 38 ++++- .../java/kinoko/server/alliance/Alliance.java | 146 ++++++++++++++++++ .../server/alliance/AllianceStorage.java | 41 +++++ src/main/java/kinoko/server/guild/Guild.java | 4 + .../kinoko/server/node/CentralServerNode.java | 23 +++ src/main/java/kinoko/world/GameConstants.java | 13 ++ .../java/kinoko/world/user/GuildInfo.java | 5 + 10 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 src/main/java/kinoko/database/AllianceAccessor.java create mode 100644 src/main/java/kinoko/server/alliance/Alliance.java create mode 100644 src/main/java/kinoko/server/alliance/AllianceStorage.java diff --git a/src/main/java/kinoko/database/AllianceAccessor.java b/src/main/java/kinoko/database/AllianceAccessor.java new file mode 100644 index 000000000..9050fef66 --- /dev/null +++ b/src/main/java/kinoko/database/AllianceAccessor.java @@ -0,0 +1,18 @@ +package kinoko.database; + +import kinoko.server.alliance.Alliance; + +import java.util.Optional; + +public interface AllianceAccessor { + Optional getAllianceById(int allianceId); + + boolean checkAllianceNameAvailable(String name); + + boolean newAlliance(Alliance alliance); + + boolean saveAlliance(Alliance alliance); + + boolean deleteAlliance(int allianceId); + +} \ No newline at end of file diff --git a/src/main/java/kinoko/database/DatabaseConnector.java b/src/main/java/kinoko/database/DatabaseConnector.java index 9b4bf0e09..9972f5120 100644 --- a/src/main/java/kinoko/database/DatabaseConnector.java +++ b/src/main/java/kinoko/database/DatabaseConnector.java @@ -10,6 +10,8 @@ public interface DatabaseConnector { FriendAccessor getFriendAccessor(); GuildAccessor getGuildAccessor(); + + AllianceAccessor getAllianceAccessor(); GiftAccessor getGiftAccessor(); diff --git a/src/main/java/kinoko/database/DatabaseManager.java b/src/main/java/kinoko/database/DatabaseManager.java index 2808fd090..919374195 100644 --- a/src/main/java/kinoko/database/DatabaseManager.java +++ b/src/main/java/kinoko/database/DatabaseManager.java @@ -24,6 +24,10 @@ public static FriendAccessor friendAccessor() { public static GuildAccessor guildAccessor() { return connector.getGuildAccessor(); } + + public static AllianceAccessor allianceAccessor() { + return connector.getAllianceAccessor(); + } public static GiftAccessor giftAccessor() { return connector.getGiftAccessor(); diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index 85d417f29..cfa6406a4 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -10,6 +10,9 @@ import kinoko.world.user.User; import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import kinoko.server.alliance.Alliance; public final class GuildHQ extends ScriptHandler { public static final int GUILD_HEADQUARTERS = 200000301; @@ -155,18 +158,45 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("Only the party leader can form a Guild Union."); return; } - sm.sayNext("You can create a Guild Union if your party consists of two people."); - // TODO + if (!sm.checkParty(2, (user) -> {return user.getGuildRank() == GuildRank.MASTER;})) { + sm.sayNext("You can create a Guild Union if your party consists of two people."); + } + + if (!sm.askYesNo("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_ALLIANCE_COST)) { + sm.sayNext("You're not ready yet? Come back to me when you want to create an alliance."); + return; + } + if (!sm.addMoney(-GameConstants.CREATE_ALLIANCE_COST)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request } else if (answer == 3) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master can expand the number of guilds in the Union."); } - // TODO + + final int currentCapacity = sm.getUser().getGuildInfo().getAllianceMemberMax(); + if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { + sm.sayNext("Your alliance already reached the maximum capacity for guilds."); + return; + } + final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); + if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { + return; + } + if (!sm.addMoney(-expandCost)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request } else if (answer == 4) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master may disband the Guild Union."); } - // TODO + //TODO alliance submit request } } } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java new file mode 100644 index 000000000..a8a8bad2b --- /dev/null +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -0,0 +1,146 @@ +package kinoko.server.alliance; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import kinoko.server.guild.Guild; +import static kinoko.server.guild.Guild.EMPTY_MEMBER; +import static kinoko.server.guild.Guild.MEMBER_COMPARATOR; +import kinoko.server.guild.GuildMember; +import kinoko.server.packet.OutPacket; +import kinoko.util.Encodable; +import kinoko.util.Lockable; +import kinoko.world.GameConstants; + +/** + * Alliance instance managed by CentralServerNode. + */ +public final class Alliance implements Encodable, Lockable { + + private final int allianceId; + private final String allianceName; + private final List gradeNames; + private int memberMax; + + private final Map guildInvites; // invitee ID -> inviter ID + private final Map guilds; // guild ID -> guild + + private final Lock lock = new ReentrantLock(); + + public Alliance(int allianceId, String allianceName) { + this.allianceId = allianceId; + this.allianceName = allianceName; + this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); + this.guildInvites = new HashMap<>(); + this.guilds = new HashMap<>(); + this.memberMax = GameConstants.GUILD_CAPACITY_MIN; + } + + public int getMemberMax() { + return memberMax; + } + + public void setMemberMax(int memberMax) { + this.memberMax = memberMax; + } + + public int getAllianceId() { + return allianceId; + } + + public String getAllianceName() { + return allianceName; + } + + public List getAllianceMembers() { + List list = new ArrayList<>(100); + + for (Guild guild : guilds.values()) { + list.addAll(guild.getGuildMembersUnsorted()); + } + + return list.stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level + .toList(); + } + + public List getMemberIds() { + return getAllianceMembers().keySet().stream().toList(); + } + + public List getMemberIds(int exceptId) { + return getAllianceMembers().keySet().stream() + .filter((id) -> id != exceptId) + .toList(); + } + + public boolean hasMember(int characterId) { + for (Guild guild : guilds.values()) { + if (guild.hasMember(characterId)) { + return true; + } + } + + return false; + } + + public GuildMember getMember(int characterId) { + for (Guild guild : guilds.values()) { + GuildMember member = guild.getMember(characterId); + if (member != EMPTY_MEMBER) { + return member; + } + } + + return EMPTY_MEMBER; + } + + public boolean hasGuild(int guildId) { + return guilds.containsKey(guildId); + } + + public Guild getGuild(int guildId) { + return guilds.get(guildId); + } + + public boolean canAddGuild(int guildId) { + if (guilds.size() >= getMemberMax()) { + return false; + } + return !guilds.containsKey(guildId); + } + + public boolean addGuild(Guild guild) { + if (!canAddGuild(guild.getGuildId())) { + return false; + } + guilds.put(guild.getGuildId(), guild); + return true; + } + + public void removeGuild(Guild guild) { + guilds.remove(guild.getGuildId()); + } + + public String getGradeNames(int rank) { + return gradeNames.get(rank - 1); + } + + @Override + public void encode(OutPacket outPacket) { + // TODO + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +} diff --git a/src/main/java/kinoko/server/alliance/AllianceStorage.java b/src/main/java/kinoko/server/alliance/AllianceStorage.java new file mode 100644 index 000000000..9a30b1997 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/AllianceStorage.java @@ -0,0 +1,41 @@ +package kinoko.server.alliance; + +import kinoko.database.DatabaseManager; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public final class AllianceStorage { + private final ConcurrentHashMap allianceMap = new ConcurrentHashMap<>(); + + public boolean addAlliance(Alliance alliance) { + if (allianceMap.containsKey(alliance.getAllianceId())) { + return false; + } + if (!DatabaseManager.allianceAccessor().newAlliance(alliance)) { + return false; + } + allianceMap.put(alliance.getAllianceId(), alliance); + return true; + } + + public boolean removeAlliance(Alliance alliance) { + if (!DatabaseManager.allianceAccessor().deleteAlliance(alliance.getAllianceId())) { + return false; + } + allianceMap.remove(alliance.getAllianceId()); + return true; + } + + public Optional getAllianceById(int allianceId) { + if (allianceId == 0) { + return Optional.empty(); + } + if (allianceMap.containsKey(allianceId)) { + return Optional.of(allianceMap.get(allianceId)); + } + final Optional guildResult = DatabaseManager.allianceAccessor().getAllianceById(allianceId); + guildResult.ifPresent(guild -> allianceMap.put(allianceId, guild)); + return guildResult; + } +} diff --git a/src/main/java/kinoko/server/guild/Guild.java b/src/main/java/kinoko/server/guild/Guild.java index 1f0d176b2..a10c3a564 100644 --- a/src/main/java/kinoko/server/guild/Guild.java +++ b/src/main/java/kinoko/server/guild/Guild.java @@ -77,6 +77,10 @@ public List getGuildMembers() { return guildMembers.values().stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level .toList(); } + + public List getGuildMembersUnsorted() { + return guildMembers.values().stream().toList(); + } public List getBoardEntries() { return boardEntries; diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 5a620ede0..5fdac5f87 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -4,6 +4,8 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import kinoko.packet.CentralPacket; +import kinoko.server.alliance.Alliance; +import kinoko.server.alliance.AllianceStorage; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildMember; import kinoko.server.guild.GuildRank; @@ -38,6 +40,7 @@ public final class CentralServerNode extends Node { private final MessengerStorage messengerStorage = new MessengerStorage(); private final PartyStorage partyStorage = new PartyStorage(); private final GuildStorage guildStorage = new GuildStorage(); + private final AllianceStorage allianceStorage = new AllianceStorage(); private final CompletableFuture initializeFuture = new CompletableFuture<>(); private final CompletableFuture shutdownFuture = new CompletableFuture<>(); private final int port; @@ -190,6 +193,26 @@ public Optional getGuildById(int guildId) { return guildStorage.getGuildById(guildId); } + // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- + + public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { + final Alliance alliance = new Alliance(allianceId, allianceName); + if (!allianceStorage.addAlliance(alliance)) { + return Optional.empty(); + } + return Optional.of(alliance); + } + + public boolean removeAlliance(Alliance alliance) { + return allianceStorage.removeAlliance(alliance); + } + + public Optional getAllianceById(int allianceId) { + if (allianceId == 0) { + return Optional.empty(); + } + return allianceStorage.getAllianceById(allianceId); + } // OVERRIDES ------------------------------------------------------------------------------------------------------- diff --git a/src/main/java/kinoko/world/GameConstants.java b/src/main/java/kinoko/world/GameConstants.java index 18617e519..6fd965820 100644 --- a/src/main/java/kinoko/world/GameConstants.java +++ b/src/main/java/kinoko/world/GameConstants.java @@ -64,6 +64,9 @@ public final class GameConstants { public static final int CREATE_EMBLEM_COST = 500_000; public static final int DELETE_EMBLEM_COST = 1_000_000; public static final int CREATE_UNION_COST = 5_000_000; + public static final int UNION_GRADE_MAX = GUILD_GRADE_NAMES.size(); + public static final int UNION_CAPACITY_MIN = 2; + public static final int UNION_CAPACITY_MAX = 5; public static final int MACRO_SYS_DATA_SIZE = 5; public static final int MACRO_SKILL_COUNT = 3; @@ -208,6 +211,16 @@ public static int getGuildExpandCost(int memberMax) { return 5_000_000; } } + + public static int getUnionExpandCost(int memberMax) { + if (memberMax < 3) { + return 500_000; + } else if (memberMax < 4) { + return 1_500_000; + } else { + return 5_000_000; + } + } public static Tuple getMoneyForMobLevel(int level) { // Modern maple values, probably not accurate diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 3cb351d12..e9b2b8563 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -18,6 +18,7 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; + private final int allianceMemberMax; public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { this.guildId = guildId; @@ -71,6 +72,10 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } + + public int getAllianceMemberMax() { + return allianceMemberMax; + } @Override public void encode(OutPacket outPacket) { From 64521a9742d0cfc03e4e30584a63399aed2ac3db Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:23:20 -0300 Subject: [PATCH 09/21] Update GuildHQ.java --- src/main/java/kinoko/script/GuildHQ.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index cfa6406a4..ea7a05a33 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -42,7 +42,7 @@ public static void guild_proc(ScriptManager sm) { return; } if (sm.getLevel() < 101) { - sm.sayNext("Hey, you level is a bit low to be a guild leader. You need to be at least level 101 to create a guild."); + sm.sayNext("Hey, your level is a bit low to be a guild leader. You need to be at least level 101 to create a guild."); return; } if (!sm.canAddMoney(-GameConstants.CREATE_GUILD_COST)) { From 0da13876f722d4d0ddfd9fcd42da717693f51ea3 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Mon, 22 Sep 2025 21:37:19 -0300 Subject: [PATCH 10/21] Attend fixes for compilation Prepare for compile. --- .../kinoko/database/cassandra/CassandraConnector.java | 6 ++++++ src/main/java/kinoko/script/GuildHQ.java | 4 ++-- src/main/java/kinoko/server/alliance/Alliance.java | 10 +++++----- src/main/java/kinoko/world/user/GuildInfo.java | 10 ++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/kinoko/database/cassandra/CassandraConnector.java b/src/main/java/kinoko/database/cassandra/CassandraConnector.java index 44ae4a0ee..a9c2db734 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraConnector.java +++ b/src/main/java/kinoko/database/cassandra/CassandraConnector.java @@ -47,6 +47,7 @@ public final class CassandraConnector implements DatabaseConnector { private CharacterAccessor characterAccessor; private FriendAccessor friendAccessor; private GuildAccessor guildAccessor; + private AllianceAccessor allianceAccessor; private GiftAccessor giftAccessor; private MemoAccessor memoAccessor; @@ -101,6 +102,11 @@ public FriendAccessor getFriendAccessor() { public GuildAccessor getGuildAccessor() { return guildAccessor; } + + @Override + public AllianceAccessor getAllianceAccessor() { + return allianceAccessor; + } @Override public GiftAccessor getGiftAccessor() { diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index ea7a05a33..2b5da38e2 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -162,11 +162,11 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("You can create a Guild Union if your party consists of two people."); } - if (!sm.askYesNo("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_ALLIANCE_COST)) { + if (!sm.askYesNo(String.format("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_UNION_COST))) { sm.sayNext("You're not ready yet? Come back to me when you want to create an alliance."); return; } - if (!sm.addMoney(-GameConstants.CREATE_ALLIANCE_COST)) { + if (!sm.addMoney(-GameConstants.CREATE_UNION_COST)) { sm.sayNext("You don't have enough mesos for this request."); return; } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index a8a8bad2b..bf10860d8 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -66,13 +66,13 @@ public List getAllianceMembers() { .toList(); } - public List getMemberIds() { - return getAllianceMembers().keySet().stream().toList(); + public List getMemberIds() { + return getAllianceMembers().stream().toList(); } - public List getMemberIds(int exceptId) { - return getAllianceMembers().keySet().stream() - .filter((id) -> id != exceptId) + public List getMemberIds(int exceptId) { + return getAllianceMembers().stream() + .filter((member) -> member.getCharacterId() != exceptId) .toList(); } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index e9b2b8563..62164f5f4 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -7,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", 0); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -20,7 +20,7 @@ public final class GuildInfo implements Encodable { private final String allianceName; private final int allianceMemberMax; - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, int allianceMemberMax) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -31,6 +31,7 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceMemberMax = allianceMemberMax; } public int getGuildId() { @@ -102,7 +103,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, 2); } public static GuildInfo from(Guild guild, int characterId) { @@ -116,7 +117,8 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName() + guild.getAllianceName(), + 2 ); } } From 5e1369ba15d4963ebcfa9a0305e551fd79ce3200 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Sep 2025 18:55:08 -0300 Subject: [PATCH 11/21] First step on Alliances request and results A stretch on request and results of Alliances. --- .../kinoko/handler/user/GuildHandler.java | 22 +++++++++++- .../java/kinoko/packet/world/GuildPacket.java | 1 - src/main/java/kinoko/script/GuildHQ.java | 36 +++++++++++-------- src/main/java/kinoko/server/Server.java | 5 +++ .../java/kinoko/world/user/GuildInfo.java | 17 +++------ 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index e319afbfa..41774cd93 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -220,7 +220,27 @@ public static void handleGuildResult(User user, InPacket inPacket) { public static void handleAllianceRequest(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceRequestType requestType = AllianceRequestType.getByValue(type); - // TODO + + if (!user.hasGuild() || user.hasAlliance()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; + } + + if (requestType == AllianceRequestType.Create) { + // send CreateDone + } else if (requestType == AllianceRequestType.Invite) { + // send Invite_Done + } else if (requestType == AllianceRequestType.Join) { + if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { + // send InviteGuild_BlockedByOpt + return; + } + + if () { + // send InviteGuild_AlreadyInvited + return; + } + } } @Handler(InHeader.AllianceResult) diff --git a/src/main/java/kinoko/packet/world/GuildPacket.java b/src/main/java/kinoko/packet/world/GuildPacket.java index ae80fb2c3..1f70b2375 100644 --- a/src/main/java/kinoko/packet/world/GuildPacket.java +++ b/src/main/java/kinoko/packet/world/GuildPacket.java @@ -86,7 +86,6 @@ public static OutPacket createNewGuildBeginner() { return GuildPacket.of(GuildResultType.CreateNewGuild_Beginner); } - public static OutPacket createNewGuildUnknown() { return GuildPacket.of(GuildResultType.CreateNewGuild_Unknown); } diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index 2b5da38e2..d88b3f513 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -12,6 +12,8 @@ import java.util.Map; import java.util.Optional; import java.util.function.Predicate; + +import kinoko.server.Server; import kinoko.server.alliance.Alliance; public final class GuildHQ extends ScriptHandler { @@ -177,21 +179,27 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("Only the Guild Union Master can expand the number of guilds in the Union."); } - final int currentCapacity = sm.getUser().getGuildInfo().getAllianceMemberMax(); - if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { - sm.sayNext("Your alliance already reached the maximum capacity for guilds."); - return; - } - final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); - if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { - return; - } - if (!sm.addMoney(-expandCost)) { - sm.sayNext("You don't have enough mesos for this request."); - return; + Optional alliance = Server.getCentralServerNode().getAllianceById(sm.getUser().getAllianceId()); + if(!alliance.isEmpty()) { + final int currentCapacity = alliance.get().getMemberMax(); + if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { + sm.sayNext("Your alliance already reached the maximum capacity for guilds."); + return; + } + final int expandCost = GameConstants.getUnionExpandCost(currentCapacity); + if (!sm.askYesNo(String.format("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #r%,d mesos#k.", expandCost))) { + return; + } + if (!sm.addMoney(-expandCost)) { + sm.sayNext("You don't have enough mesos for this request."); + return; + } + + //TODO alliance submit request + } else { + sm.sayNext("Your alliance is nonexistent."); + return; } - - //TODO alliance submit request } else if (answer == 4) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master may disband the Guild Union."); diff --git a/src/main/java/kinoko/server/Server.java b/src/main/java/kinoko/server/Server.java index 2054053a8..8d1f6df37 100644 --- a/src/main/java/kinoko/server/Server.java +++ b/src/main/java/kinoko/server/Server.java @@ -114,4 +114,9 @@ private static void shutdown() throws Exception { DatabaseManager.shutdown(); LogManager.shutdown(); } + + public static CentralServerNode getCentralServerNode() { + return centralServerNode; + } + } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 62164f5f4..777188803 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -7,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", 0); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -18,9 +18,8 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; - private final int allianceMemberMax; - - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, int allianceMemberMax) { + + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -31,7 +30,6 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; - this.allianceMemberMax = allianceMemberMax; } public int getGuildId() { @@ -73,10 +71,6 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - - public int getAllianceMemberMax() { - return allianceMemberMax; - } @Override public void encode(OutPacket outPacket) { @@ -103,7 +97,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, 2); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); } public static GuildInfo from(Guild guild, int characterId) { @@ -117,8 +111,7 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName(), - 2 + guild.getAllianceName() ); } } From a114267c30192b4924d3cd750c8b37039c91d09c Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Wed, 24 Sep 2025 18:50:37 -0300 Subject: [PATCH 12/21] Step 2 on Alliances definition Added CRUD for Alliances --- src/main/java/kinoko/database/IdAccessor.java | 2 + .../cassandra/CassandraIdAccessor.java | 5 ++ .../database/cassandra/table/IdTable.java | 1 + .../kinoko/handler/user/GuildHandler.java | 70 ++++++++++++++++--- .../java/kinoko/server/alliance/Alliance.java | 10 ++- .../kinoko/server/node/CentralServerNode.java | 5 +- .../java/kinoko/world/user/GuildInfo.java | 4 +- 7 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/main/java/kinoko/database/IdAccessor.java b/src/main/java/kinoko/database/IdAccessor.java index 399887c96..ab2ba23c1 100644 --- a/src/main/java/kinoko/database/IdAccessor.java +++ b/src/main/java/kinoko/database/IdAccessor.java @@ -10,6 +10,8 @@ public interface IdAccessor { Optional nextPartyId(); Optional nextGuildId(); + + Optional nextAllianceId(); Optional nextMemoId(); } diff --git a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java index 50b9249d3..a5777576d 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java +++ b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java @@ -59,6 +59,11 @@ public synchronized Optional nextPartyId() { public synchronized Optional nextGuildId() { return getNextId(IdTable.GUILD_ID); } + + @Override + public synchronized Optional nextAllianceId() { + return getNextId(IdTable.ALLIANCE_ID); + } @Override public synchronized Optional nextMemoId() { diff --git a/src/main/java/kinoko/database/cassandra/table/IdTable.java b/src/main/java/kinoko/database/cassandra/table/IdTable.java index f400149d7..3107be342 100644 --- a/src/main/java/kinoko/database/cassandra/table/IdTable.java +++ b/src/main/java/kinoko/database/cassandra/table/IdTable.java @@ -17,6 +17,7 @@ public final class IdTable { public static final String CHARACTER_ID = "character_id"; public static final String PARTY_ID = "party_id"; public static final String GUILD_ID = "guild_id"; + public static final String ALLIANCE_ID = "alliance_id"; public static final String MEMO_ID = "memo_id"; private static final String tableName = "id_table"; diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index 41774cd93..f838a0d44 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -6,11 +6,15 @@ import kinoko.packet.world.MessagePacket; import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; +import kinoko.server.Server; +import kinoko.server.alliance.Alliance; import kinoko.server.alliance.AllianceRequestType; import kinoko.server.alliance.AllianceResultType; +import kinoko.server.alliance.AllianceStorage; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; +import kinoko.server.user.RemoteUser; import kinoko.world.GameConstants; import kinoko.world.item.InventoryManager; import kinoko.world.user.User; @@ -227,27 +231,77 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { } if (requestType == AllianceRequestType.Create) { - // send CreateDone - } else if (requestType == AllianceRequestType.Invite) { - // send Invite_Done + // send CreateDone to alliance members + } else if (requestType == AllianceRequestType.Withdraw) { + // send Withdraw_Done to guild members + } else if (requestType == AllianceRequestType.Invite) { + if (user.getGuildRank() != GuildRank.MASTER) { + user.write(GuildPacket.serverMsg("You cannot invite other guilds since you are not the master of the guild.")); + return; + } + + if (false) { + // send InviteGuild_AlreadyInvited + return; + } } else if (requestType == AllianceRequestType.Join) { if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { // send InviteGuild_BlockedByOpt return; } - if () { - // send InviteGuild_AlreadyInvited - return; - } + // send Invite_Done + } + } + + // --- Alliance CRUD --- + + private static void create(String allianceName, User user) { + // Resolve new alliance ID + final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); + if (allianceIdResult.isEmpty()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; } + + int allianceId = allianceIdResult.get(); + Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); + if (alliance.isEmpty()) { + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; + } } + + private static void addGuild(Alliance alliance, Guild guild) { + alliance.lock(); + try { + alliance.addGuild(guild); + } finally { + alliance.unlock(); + } + } + + private static void removeGuild(Alliance alliance, Guild guild) { + alliance.lock(); + try { + alliance.removeGuild(guild); + } finally { + alliance.unlock(); + } + } + + private static void destroy(Alliance alliance) { + Server.getCentralServerNode().removeAlliance(alliance); + } + + // --------------------- @Handler(InHeader.AllianceResult) public static void handleAllianceResult(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceResultType resultType = AllianceResultType.getByValue(type); - // TODO + + //TODO } @Handler(InHeader.GuildBBS) diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index bf10860d8..74618ed92 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -22,6 +22,7 @@ public final class Alliance implements Encodable, Lockable { private final int allianceId; private final String allianceName; + private final int allianceLordId; private final List gradeNames; private int memberMax; @@ -30,13 +31,14 @@ public final class Alliance implements Encodable, Lockable { private final Lock lock = new ReentrantLock(); - public Alliance(int allianceId, String allianceName) { + public Alliance(int allianceId, String allianceName, int allianceLordId) { this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceLordId = allianceLordId; this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); this.guildInvites = new HashMap<>(); this.guilds = new HashMap<>(); - this.memberMax = GameConstants.GUILD_CAPACITY_MIN; + this.memberMax = GameConstants.UNION_CAPACITY_MIN; } public int getMemberMax() { @@ -55,6 +57,10 @@ public String getAllianceName() { return allianceName; } + public int getLordId() { + return allianceLordId; + } + public List getAllianceMembers() { List list = new ArrayList<>(100); diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 5fdac5f87..727f130f4 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -23,6 +23,7 @@ import kinoko.server.party.PartyStorage; import kinoko.server.user.RemoteUser; import kinoko.server.user.UserStorage; +import kinoko.world.user.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -195,8 +196,8 @@ public Optional getGuildById(int guildId) { // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { - final Alliance alliance = new Alliance(allianceId, allianceName); + public Optional createNewAlliance(int allianceId, String allianceName, User user) { + final Alliance alliance = new Alliance(allianceId, allianceName, user.getCharacterId()); if (!allianceStorage.addAlliance(alliance)) { return Optional.empty(); } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 777188803..68d9a0738 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -1,5 +1,7 @@ package kinoko.world.user; +import kinoko.server.Server; +import kinoko.server.alliance.Alliance; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildRank; import kinoko.server.packet.InPacket; @@ -71,7 +73,7 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); From c3fd9cc1a38275063309660d5f05f5a278f5c628 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Thu, 25 Sep 2025 08:25:50 -0300 Subject: [PATCH 13/21] Third step on Alliances checkout Create methods for alliance management. --- .../kinoko/handler/user/GuildHandler.java | 2 ++ .../java/kinoko/server/alliance/Alliance.java | 20 ++++++++++++++++++- .../java/kinoko/world/user/GuildInfo.java | 17 ++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index f838a0d44..b002ff180 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -261,6 +261,7 @@ private static void create(String allianceName, User user) { final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); if (allianceIdResult.isEmpty()) { user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + log.error("Database error: allianceId"); return; } @@ -268,6 +269,7 @@ private static void create(String allianceName, User user) { Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); if (alliance.isEmpty()) { user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + log.error("Could not create alliance '{}' for user '{}'", allianceName, user.getCharacterName()); return; } } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 74618ed92..26194b21b 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -22,9 +22,10 @@ public final class Alliance implements Encodable, Lockable { private final int allianceId; private final String allianceName; - private final int allianceLordId; + private int allianceLordId; private final List gradeNames; private int memberMax; + private String notice; private final Map guildInvites; // invitee ID -> inviter ID private final Map guilds; // guild ID -> guild @@ -39,6 +40,7 @@ public Alliance(int allianceId, String allianceName, int allianceLordId) { this.guildInvites = new HashMap<>(); this.guilds = new HashMap<>(); this.memberMax = GameConstants.UNION_CAPACITY_MIN; + this.notice = ""; } public int getMemberMax() { @@ -61,6 +63,10 @@ public int getLordId() { return allianceLordId; } + public void setLordId(int characterId) { + allianceLordId = characterId; + } + public List getAllianceMembers() { List list = new ArrayList<>(100); @@ -134,6 +140,18 @@ public String getGradeNames(int rank) { return gradeNames.get(rank - 1); } + public String setGradeName(int rank, String name) { + return gradeNames.set(rank - 1, name); + } + + public String getNotice() { + return notice; + } + + public void setNotice(String str) { + notice = str; + } + @Override public void encode(OutPacket outPacket) { // TODO diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index 68d9a0738..c5556d423 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -1,7 +1,5 @@ package kinoko.world.user; -import kinoko.server.Server; -import kinoko.server.alliance.Alliance; import kinoko.server.guild.Guild; import kinoko.server.guild.GuildRank; import kinoko.server.packet.InPacket; @@ -9,7 +7,7 @@ import kinoko.util.Encodable; public final class GuildInfo implements Encodable { - public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, ""); + public static final GuildInfo EMPTY = new GuildInfo(0, "", GuildRank.NONE, 0, (short) 0, (byte) 0, (short) 0, (byte) 0, 0, "", GuildRank.NONE); private final int guildId; private final String guildName; private final GuildRank guildRank; @@ -20,8 +18,9 @@ public final class GuildInfo implements Encodable { private final byte markColor; private final int allianceId; private final String allianceName; + private final GuildRank allianceRank; - public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName) { + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, GuildRank allianceRank) { this.guildId = guildId; this.guildName = guildName; this.guildRank = guildRank; @@ -32,6 +31,7 @@ public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberM this.markColor = markColor; this.allianceId = allianceId; this.allianceName = allianceName; + this.allianceRank = allianceRank; } public int getGuildId() { @@ -74,6 +74,10 @@ public String getAllianceName() { return allianceName; } + public GuildRank getAllianceRank() { + return allianceRank; + } + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); @@ -99,7 +103,7 @@ public static GuildInfo decode(InPacket inPacket) { final byte markColor = inPacket.decodeByte(); final int allianceId = inPacket.decodeInt(); final String allianceName = inPacket.decodeString(); - return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName); + return new GuildInfo(guildId, guildName, guildRank, memberMax, markBg, markBgColor, mark, markColor, allianceId, allianceName, GuildRank.NONE); } public static GuildInfo from(Guild guild, int characterId) { @@ -113,7 +117,8 @@ public static GuildInfo from(Guild guild, int characterId) { guild.getMark(), guild.getMarkColor(), guild.getAllianceId(), - guild.getAllianceName() + guild.getAllianceName(), + guild.getMember(characterId).getAllianceRank() ); } } From ca4c8c1e5e21a29f3b94dd33cac279dd2c43d634 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Thu, 25 Sep 2025 18:35:39 -0300 Subject: [PATCH 14/21] Alliances packets to CentralServerNode Made amends with proper packets to CentralServerNode on Alliances instead of calling Server direcly. --- .../kinoko/handler/user/GuildHandler.java | 60 +---- .../java/kinoko/packet/CentralPacket.java | 8 + .../java/kinoko/server/alliance/Alliance.java | 22 +- .../server/alliance/AllianceRequest.java | 219 ++++++++++++++++++ .../server/alliance/AllianceResultType.java | 10 +- .../kinoko/server/header/CentralHeader.java | 1 + .../server/netty/CentralServerHandler.java | 117 ++++++++++ .../kinoko/server/node/CentralServerNode.java | 22 +- .../kinoko/server/node/ChannelServerNode.java | 5 + src/main/java/kinoko/server/party/Party.java | 2 +- .../java/kinoko/server/user/RemoteUser.java | 17 +- .../java/kinoko/world/user/CharacterData.java | 9 + 12 files changed, 426 insertions(+), 66 deletions(-) create mode 100644 src/main/java/kinoko/server/alliance/AllianceRequest.java diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index b002ff180..d897c98ca 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -7,10 +7,7 @@ import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; import kinoko.server.Server; -import kinoko.server.alliance.Alliance; -import kinoko.server.alliance.AllianceRequestType; -import kinoko.server.alliance.AllianceResultType; -import kinoko.server.alliance.AllianceStorage; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; @@ -231,8 +228,11 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { } if (requestType == AllianceRequestType.Create) { + String allianceName = ""; + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.createNewAlliance(allianceName)); // send CreateDone to alliance members } else if (requestType == AllianceRequestType.Withdraw) { + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); // send Withdraw_Done to guild members } else if (requestType == AllianceRequestType.Invite) { if (user.getGuildRank() != GuildRank.MASTER) { @@ -250,53 +250,17 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { return; } + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.joinAlliance(user.getAllianceId())); // send Invite_Done + } else if (requestType == AllianceRequestType.Destroy) { + if (!user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER) { + user.write(GuildPacket.serverMsg("You cannot destroy in alliance since you are not the master of the alliance.")); + return; + } + + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); } } - - // --- Alliance CRUD --- - - private static void create(String allianceName, User user) { - // Resolve new alliance ID - final Optional allianceIdResult = DatabaseManager.idAccessor().nextGuildId(); - if (allianceIdResult.isEmpty()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - log.error("Database error: allianceId"); - return; - } - - int allianceId = allianceIdResult.get(); - Optional alliance = Server.getCentralServerNode().createNewAlliance(allianceId, allianceName, user); - if (alliance.isEmpty()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - log.error("Could not create alliance '{}' for user '{}'", allianceName, user.getCharacterName()); - return; - } - } - - private static void addGuild(Alliance alliance, Guild guild) { - alliance.lock(); - try { - alliance.addGuild(guild); - } finally { - alliance.unlock(); - } - } - - private static void removeGuild(Alliance alliance, Guild guild) { - alliance.lock(); - try { - alliance.removeGuild(guild); - } finally { - alliance.unlock(); - } - } - - private static void destroy(Alliance alliance) { - Server.getCentralServerNode().removeAlliance(alliance); - } - - // --------------------- @Handler(InHeader.AllianceResult) public static void handleAllianceResult(User user, InPacket inPacket) { diff --git a/src/main/java/kinoko/packet/CentralPacket.java b/src/main/java/kinoko/packet/CentralPacket.java index 2c287faa7..5aba02319 100644 --- a/src/main/java/kinoko/packet/CentralPacket.java +++ b/src/main/java/kinoko/packet/CentralPacket.java @@ -1,5 +1,6 @@ package kinoko.packet; +import kinoko.server.alliance.AllianceRequest; import kinoko.server.guild.GuildBoardRequest; import kinoko.server.guild.GuildRequest; import kinoko.server.header.CentralHeader; @@ -238,6 +239,13 @@ public static OutPacket guildResult(int characterId, GuildInfo guildInfo) { } return outPacket; } + + public static OutPacket allianceRequest(int characterId, AllianceRequest allianceRequest) { + final OutPacket outPacket = OutPacket.of(CentralHeader.AllianceRequest); + outPacket.encodeInt(characterId); + allianceRequest.encode(outPacket); + return outPacket; + } public static OutPacket boardRequest(int characterId, GuildBoardRequest boardRequest) { final OutPacket outPacket = OutPacket.of(CentralHeader.BoardRequest); diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 26194b21b..4b1135e4f 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -125,15 +125,25 @@ public boolean canAddGuild(int guildId) { } public boolean addGuild(Guild guild) { - if (!canAddGuild(guild.getGuildId())) { - return false; - } - guilds.put(guild.getGuildId(), guild); - return true; + this.lock(); + try { + if (!canAddGuild(guild.getGuildId())) { + return false; + } + guilds.put(guild.getGuildId(), guild); + return true; + } finally { + this.unlock(); + } } public void removeGuild(Guild guild) { - guilds.remove(guild.getGuildId()); + this.lock(); + try { + guilds.remove(guild.getGuildId()); + } finally { + this.unlock(); + } } public String getGradeNames(int rank) { diff --git a/src/main/java/kinoko/server/alliance/AllianceRequest.java b/src/main/java/kinoko/server/alliance/AllianceRequest.java new file mode 100644 index 000000000..a146188e7 --- /dev/null +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -0,0 +1,219 @@ +package kinoko.server.alliance; + +import kinoko.server.guild.GuildRank; +import kinoko.server.packet.InPacket; +import kinoko.server.packet.OutPacket; +import kinoko.util.Encodable; +import kinoko.world.GameConstants; + +import java.util.ArrayList; +import java.util.List; + +public final class AllianceRequest implements Encodable { + private final AllianceRequestType requestType; + private int allianceId; + private String allianceName; + private String allianceNotice; + private List gradeNames; + private int memberMax; + + private int inviterId; + private int targetId; + private String targetName; + private GuildRank allianceRank; + + public AllianceRequest(AllianceRequestType requestType) { + this.requestType = requestType; + } + + public AllianceRequestType getRequestType() { + return requestType; + } + + public int getAllianceId() { + return allianceId; + } + + public String getAllianceName() { + return allianceName; + } + + public String getAllianceNotice() { + return allianceNotice; + } + + public List getGradeNames() { + return gradeNames; + } + + public int getMemberMax() { + return memberMax; + } + + public int getInviterId() { + return inviterId; + } + + public int getTargetId() { + return targetId; + } + + public String getTargetName() { + return targetName; + } + + public GuildRank getAllianceRank() { + return allianceRank; + } + + @Override + public void encode(OutPacket outPacket) { + outPacket.encodeByte(requestType.getValue()); + switch (requestType) { + case Create -> { + outPacket.encodeInt(allianceId); + outPacket.encodeString(allianceName); + } + case Invite -> { + outPacket.encodeString(targetName); + } + case Join -> { + outPacket.encodeInt(inviterId); + } + case Kick -> { + outPacket.encodeInt(targetId); + outPacket.encodeString(targetName); + } + case UpdateMemberCountMax -> { + outPacket.encodeInt(memberMax); + } + case SetGradeName -> { + for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + outPacket.encodeString(gradeNames.get(i)); + } + } + case ChangeGrade -> { + outPacket.encodeInt(targetId); + outPacket.encodeByte(allianceRank.getValue()); + } + case SetNotice -> { + outPacket.encodeString(allianceNotice); + } + } + } + + public static AllianceRequest decode(InPacket inPacket) { + final int type = inPacket.decodeByte(); + final AllianceRequest request = new AllianceRequest(AllianceRequestType.getByValue(type)); + switch (request.requestType) { + case Load, Destroy, Withdraw -> { + request.allianceId = inPacket.decodeInt(); + } + case Create -> { + request.allianceId = inPacket.decodeInt(); + request.allianceName = inPacket.decodeString(); + } + case Invite -> { + request.targetName = inPacket.decodeString(); + } + case Join -> { + request.inviterId = inPacket.decodeInt(); + } + case Kick -> { + request.targetId = inPacket.decodeInt(); + request.targetName = inPacket.decodeString(); + } + case UpdateMemberCountMax -> { + request.memberMax = inPacket.decodeInt(); + } + case SetGradeName -> { + request.gradeNames = new ArrayList<>(); + for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + request.gradeNames.add(inPacket.decodeString()); + } + } + case ChangeGrade -> { + request.targetId = inPacket.decodeInt(); + request.allianceRank = GuildRank.getByValue(inPacket.decodeByte()); + } + case SetNotice -> { + request.allianceNotice = inPacket.decodeString(); + } + case null -> { + throw new IllegalStateException(String.format("Unknown alliance request type %d", type)); + } + default -> { + throw new IllegalStateException(String.format("Unhandled alliance request type %d", type)); + } + } + return request; + } + + public static AllianceRequest loadAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Load); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest createNewAlliance(String allianceName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Create); + request.allianceName = allianceName; + return request; + } + + public static AllianceRequest inviteAlliance(String targetName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); + request.targetName = targetName; + return request; + } + + public static AllianceRequest joinAlliance(int inviterId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Join); + request.inviterId = inviterId; + return request; + } + + public static AllianceRequest withdrawAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Withdraw); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest kickAlliance(int targetId, String targetName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Kick); + request.targetId = targetId; + request.targetName = targetName; + return request; + } + + public static AllianceRequest removeAlliance(int allianceId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Destroy); + request.allianceId = allianceId; + return request; + } + + public static AllianceRequest incMaxMemberNum(int memberMax) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.UpdateMemberCountMax); + request.memberMax = memberMax; + return request; + } + + public static AllianceRequest setGradeName(List gradeNames) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetGradeName); + request.gradeNames = gradeNames; + return request; + } + + public static AllianceRequest setMemberGrade(int targetId, GuildRank allianceRank) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.ChangeGrade); + request.targetId = targetId; + request.allianceRank = allianceRank; + return request; + } + + public static AllianceRequest setNotice(String notice) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetNotice); + request.allianceNotice = notice; + return request; + } +} diff --git a/src/main/java/kinoko/server/alliance/AllianceResultType.java b/src/main/java/kinoko/server/alliance/AllianceResultType.java index 593d9326c..9b10d48d7 100644 --- a/src/main/java/kinoko/server/alliance/AllianceResultType.java +++ b/src/main/java/kinoko/server/alliance/AllianceResultType.java @@ -3,16 +3,16 @@ public enum AllianceResultType { // AllianceRes LoadDone(12), - LoadGuildDone(13), + LoadallianceDone(13), NotifyLoginOrLogout(14), CreateDone(15), Withdraw_Done(16), Withdraw_Failed(17), Invite_Done(18), Invite_Failed(19), - InviteGuild_BlockedByOpt(20), - InviteGuild_AlreadyInvited(21), - InviteGuild_Rejected(22), + Invitealliance_BlockedByOpt(20), + Invitealliance_AlreadyInvited(21), + Invitealliance_Rejected(22), UpdateAllianceInfo(23), ChangeLevelOrJob(24), ChangeMaster_Done(25), @@ -20,7 +20,7 @@ public enum AllianceResultType { ChangeGrade_Done(27), SetNotice_Done(28), Destroy_Done(29), - UpdateGuildInfo(30); + UpdateallianceInfo(30); private final int value; diff --git a/src/main/java/kinoko/server/header/CentralHeader.java b/src/main/java/kinoko/server/header/CentralHeader.java index 7e5b6fa21..13745c90b 100644 --- a/src/main/java/kinoko/server/header/CentralHeader.java +++ b/src/main/java/kinoko/server/header/CentralHeader.java @@ -33,6 +33,7 @@ public enum CentralHeader { PartyResult, GuildRequest, GuildResult, + AllianceRequest, BoardRequest; private static final List headers = List.of(values()); diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index fade81374..f6114d78f 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -8,6 +8,7 @@ import kinoko.packet.world.BroadcastPacket; import kinoko.packet.world.GuildPacket; import kinoko.packet.world.PartyPacket; +import kinoko.server.alliance.*; import kinoko.server.guild.*; import kinoko.server.header.CentralHeader; import kinoko.server.memo.Memo; @@ -29,6 +30,7 @@ import kinoko.util.Util; import kinoko.world.GameConstants; import kinoko.world.user.GuildInfo; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -76,6 +78,7 @@ protected void channelRead0(ChannelHandlerContext ctx, InPacket inPacket) { case MessengerRequest -> handleMessengerRequest(remoteServerNode, inPacket); case PartyRequest -> handlePartyRequest(remoteServerNode, inPacket); case GuildRequest -> handleGuildRequest(remoteServerNode, inPacket); + case AllianceRequest -> handleAllianceRequest(remoteServerNode, inPacket); case BoardRequest -> handleBoardRequest(remoteServerNode, inPacket); case null -> log.error("Central Server received an unknown opcode : {}", op); default -> log.error("Central Server received an unhandled header : {}", header); @@ -1049,6 +1052,120 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa } } } + + private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { + final int characterId = inPacket.decodeInt(); + final AllianceRequest allianceRequest = AllianceRequest.decode(inPacket); + // Resolve requester user + final Optional remoteUserResult = centralServerNode.getUserByCharacterId(characterId); + if (remoteUserResult.isEmpty()) { + log.error("Failed to resolve user with character ID : {} for AllianceRequest", characterId); + return; + } + String allianceName = allianceRequest.getAllianceName(); + final RemoteUser remoteUser = remoteUserResult.get(); + switch (allianceRequest.getRequestType()) { + case Create -> { + // Check if alliance name is available + if (!DatabaseManager.allianceAccessor().checkAllianceNameAvailable(allianceName)) { + // TODO The name is already in use... Please try other ones.... + return; + } + + // Resolve new alliance ID + final Optional allianceIdResult = DatabaseManager.idAccessor().nextAllianceId(); + if (allianceIdResult.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + log.error("Database error: allianceId"); + return; + } + + int allianceId = allianceIdResult.get(); + Optional alliance = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); + if (alliance.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + + log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); + return; + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance.get()); + } + case Invite -> { + + } + case Join -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.addGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + } + case Withdraw, Kick -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.removeGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + } + case Destroy -> { + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + centralServerNode.removeAlliance(alliance); + } finally { + alliance.unlock(); + } + } + } + case UpdateMemberCountMax -> { + + } + case SetGradeName -> { + + } + case ChangeGrade -> { + + } + + case SetNotice -> { + + } + } + } private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { final int characterId = inPacket.decodeInt(); diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 727f130f4..21b362abf 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -196,12 +196,24 @@ public Optional getGuildById(int guildId) { // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - public Optional createNewAlliance(int allianceId, String allianceName, User user) { - final Alliance alliance = new Alliance(allianceId, allianceName, user.getCharacterId()); - if (!allianceStorage.addAlliance(alliance)) { - return Optional.empty(); + public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { + final Alliance alliance = new Alliance(allianceId, allianceName, remoteUser.getCharacterId()); + + final GuildMember member = GuildMember.from(remoteUser); + member.setGuildRank(GuildRank.MASTER); + + Optional guild = guildStorage.getGuildById(remoteUser.getGuildId()); + if (!guild.isEmpty()) { + if (!alliance.addGuild(guild.get())) { + throw new IllegalStateException("Could not add guild to alliance"); + } + + if (allianceStorage.addAlliance(alliance)) { + return Optional.of(alliance); + } } - return Optional.of(alliance); + + return Optional.empty(); } public boolean removeAlliance(Alliance alliance) { diff --git a/src/main/java/kinoko/server/node/ChannelServerNode.java b/src/main/java/kinoko/server/node/ChannelServerNode.java index aa6b93406..0149e3dc4 100644 --- a/src/main/java/kinoko/server/node/ChannelServerNode.java +++ b/src/main/java/kinoko/server/node/ChannelServerNode.java @@ -6,6 +6,7 @@ import kinoko.packet.CentralPacket; import kinoko.server.ServerConfig; import kinoko.server.ServerConstants; +import kinoko.server.alliance.AllianceRequest; import kinoko.server.event.EventManager; import kinoko.server.event.EventState; import kinoko.server.event.EventType; @@ -216,6 +217,10 @@ public void submitPartyRequest(User user, PartyRequest partyRequest) { public void submitGuildRequest(User user, GuildRequest guildRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.guildRequest(user.getCharacterId(), guildRequest)); } + + public void submitAllianceRequest(User user, AllianceRequest allianceRequest) { + centralClientFuture.channel().writeAndFlush(CentralPacket.allianceRequest(user.getCharacterId(), allianceRequest)); + } public void submitBoardRequest(User user, GuildBoardRequest boardRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.boardRequest(user.getCharacterId(), boardRequest)); diff --git a/src/main/java/kinoko/server/party/Party.java b/src/main/java/kinoko/server/party/Party.java index ed7529a16..75d69c409 100644 --- a/src/main/java/kinoko/server/party/Party.java +++ b/src/main/java/kinoko/server/party/Party.java @@ -18,7 +18,7 @@ * the instance stored in UserStorage. */ public final class Party implements Encodable, Lockable { - private static final RemoteUser EMPTY_MEMBER = new RemoteUser(0, 0, "", 0, 0, GameConstants.CHANNEL_OFFLINE, GameConstants.UNDEFINED_FIELD_ID, 0, 0, 0, RemoteTownPortal.EMPTY); + private static final RemoteUser EMPTY_MEMBER = new RemoteUser(0, 0, "", 0, 0, GameConstants.CHANNEL_OFFLINE, GameConstants.UNDEFINED_FIELD_ID, 0, 0, 0, 0, RemoteTownPortal.EMPTY); private final Lock lock = new ReentrantLock(); private final int partyId; private final List partyMembers; diff --git a/src/main/java/kinoko/server/user/RemoteUser.java b/src/main/java/kinoko/server/user/RemoteUser.java index ba8971452..4c34df1a7 100644 --- a/src/main/java/kinoko/server/user/RemoteUser.java +++ b/src/main/java/kinoko/server/user/RemoteUser.java @@ -16,9 +16,10 @@ public final class RemoteUser implements Encodable { private int messengerId; private int partyId; private int guildId; + private int allianceId; private RemoteTownPortal townPortal; - public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, RemoteTownPortal townPortal) { + public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, int allianceId, RemoteTownPortal townPortal) { this.accountId = accountId; this.characterId = characterId; this.characterName = characterName; @@ -29,6 +30,7 @@ public RemoteUser(int accountId, int characterId, String characterName, int leve this.messengerId = messengerId; this.partyId = partyId; this.guildId = guildId; + this.allianceId = allianceId; this.townPortal = townPortal; } @@ -91,6 +93,14 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } + + public int getAllianceId() { + return guildId; + } + + public void setAllianceId(int allianceId) { + this.allianceId = allianceId; + } public RemoteTownPortal getTownPortal() { return townPortal != null ? townPortal : RemoteTownPortal.EMPTY; @@ -113,6 +123,7 @@ public String toString() { ", messengerId=" + messengerId + ", partyId=" + partyId + ", guildId=" + guildId + + ", allianceId=" + allianceId + ", townPortal=" + townPortal + '}'; } @@ -129,6 +140,7 @@ public void encode(OutPacket outPacket) { outPacket.encodeInt(messengerId); outPacket.encodeInt(partyId); outPacket.encodeInt(guildId); + outPacket.encodeInt(allianceId); outPacket.encodeByte(townPortal != null); if (townPortal != null) { townPortal.encode(outPacket); @@ -146,6 +158,7 @@ public static RemoteUser decode(InPacket inPacket) { final int messengerId = inPacket.decodeInt(); final int partyId = inPacket.decodeInt(); final int guildId = inPacket.decodeInt(); + final int allianceId = inPacket.decodeInt(); final RemoteTownPortal townPortal = inPacket.decodeBoolean() ? RemoteTownPortal.decode(inPacket) : null; return new RemoteUser( accountId, @@ -158,6 +171,7 @@ public static RemoteUser decode(InPacket inPacket) { messengerId, partyId, guildId, + allianceId, townPortal ); } @@ -174,6 +188,7 @@ public static RemoteUser from(User user) { user.getMessengerId(), user.getCharacterData().getPartyId(), user.getCharacterData().getGuildId(), + user.getCharacterData().getAllianceId(), user.getTownPortal() != null ? RemoteTownPortal.from(user.getTownPortal()) : null ); } diff --git a/src/main/java/kinoko/world/user/CharacterData.java b/src/main/java/kinoko/world/user/CharacterData.java index ed9e8e25a..0fe4d7220 100644 --- a/src/main/java/kinoko/world/user/CharacterData.java +++ b/src/main/java/kinoko/world/user/CharacterData.java @@ -39,6 +39,7 @@ public final class CharacterData implements Encodable { private int friendMax; private int partyId; private int guildId; + private int allianceId; private Instant creationTime; private Instant maxLevelTime; @@ -161,6 +162,14 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } + + public int getAllianceId() { + return guildId; + } + + public void setAllianceId(int allianceId) { + this.allianceId = allianceId; + } public Instant getCreationTime() { return creationTime; From ec1b7e93b1595e311633bf4d72c87d9366f17f7c Mon Sep 17 00:00:00 2001 From: iw2d <30405132+iw2d@users.noreply.github.com> Date: Sun, 23 Nov 2025 07:55:26 +1000 Subject: [PATCH 15/21] Auto-reformat --- .../kinoko/database/DatabaseConnector.java | 2 +- .../java/kinoko/database/DatabaseManager.java | 2 +- src/main/java/kinoko/database/IdAccessor.java | 2 +- .../cassandra/CassandraConnector.java | 2 +- .../cassandra/CassandraIdAccessor.java | 2 +- .../kinoko/handler/user/GuildHandler.java | 61 ++++---- .../java/kinoko/packet/CentralPacket.java | 2 +- src/main/java/kinoko/script/GuildHQ.java | 27 ++-- src/main/java/kinoko/server/Server.java | 6 +- .../java/kinoko/server/alliance/Alliance.java | 78 +++++----- .../server/alliance/AllianceRequest.java | 4 +- .../java/kinoko/server/command/Arguments.java | 6 +- src/main/java/kinoko/server/guild/Guild.java | 2 +- .../server/netty/CentralServerHandler.java | 142 +++++++++--------- .../kinoko/server/node/CentralServerNode.java | 13 +- .../kinoko/server/node/ChannelServerNode.java | 2 +- .../java/kinoko/server/user/RemoteUser.java | 2 +- src/main/java/kinoko/world/GameConstants.java | 2 +- .../java/kinoko/world/user/CharacterData.java | 2 +- .../java/kinoko/world/user/GuildInfo.java | 6 +- 20 files changed, 179 insertions(+), 186 deletions(-) diff --git a/src/main/java/kinoko/database/DatabaseConnector.java b/src/main/java/kinoko/database/DatabaseConnector.java index 9972f5120..8f6e862ea 100644 --- a/src/main/java/kinoko/database/DatabaseConnector.java +++ b/src/main/java/kinoko/database/DatabaseConnector.java @@ -10,7 +10,7 @@ public interface DatabaseConnector { FriendAccessor getFriendAccessor(); GuildAccessor getGuildAccessor(); - + AllianceAccessor getAllianceAccessor(); GiftAccessor getGiftAccessor(); diff --git a/src/main/java/kinoko/database/DatabaseManager.java b/src/main/java/kinoko/database/DatabaseManager.java index 919374195..2b2dc884d 100644 --- a/src/main/java/kinoko/database/DatabaseManager.java +++ b/src/main/java/kinoko/database/DatabaseManager.java @@ -24,7 +24,7 @@ public static FriendAccessor friendAccessor() { public static GuildAccessor guildAccessor() { return connector.getGuildAccessor(); } - + public static AllianceAccessor allianceAccessor() { return connector.getAllianceAccessor(); } diff --git a/src/main/java/kinoko/database/IdAccessor.java b/src/main/java/kinoko/database/IdAccessor.java index ab2ba23c1..0fe1e4c07 100644 --- a/src/main/java/kinoko/database/IdAccessor.java +++ b/src/main/java/kinoko/database/IdAccessor.java @@ -10,7 +10,7 @@ public interface IdAccessor { Optional nextPartyId(); Optional nextGuildId(); - + Optional nextAllianceId(); Optional nextMemoId(); diff --git a/src/main/java/kinoko/database/cassandra/CassandraConnector.java b/src/main/java/kinoko/database/cassandra/CassandraConnector.java index a9c2db734..fafbb2c11 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraConnector.java +++ b/src/main/java/kinoko/database/cassandra/CassandraConnector.java @@ -102,7 +102,7 @@ public FriendAccessor getFriendAccessor() { public GuildAccessor getGuildAccessor() { return guildAccessor; } - + @Override public AllianceAccessor getAllianceAccessor() { return allianceAccessor; diff --git a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java index a5777576d..72c56780f 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java +++ b/src/main/java/kinoko/database/cassandra/CassandraIdAccessor.java @@ -59,7 +59,7 @@ public synchronized Optional nextPartyId() { public synchronized Optional nextGuildId() { return getNextId(IdTable.GUILD_ID); } - + @Override public synchronized Optional nextAllianceId() { return getNextId(IdTable.ALLIANCE_ID); diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index d897c98ca..87d27e32a 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -6,12 +6,12 @@ import kinoko.packet.world.MessagePacket; import kinoko.packet.world.WvsContext; import kinoko.script.GuildHQ; -import kinoko.server.Server; -import kinoko.server.alliance.*; +import kinoko.server.alliance.AllianceRequest; +import kinoko.server.alliance.AllianceRequestType; +import kinoko.server.alliance.AllianceResultType; import kinoko.server.guild.*; import kinoko.server.header.InHeader; import kinoko.server.packet.InPacket; -import kinoko.server.user.RemoteUser; import kinoko.world.GameConstants; import kinoko.world.item.InventoryManager; import kinoko.world.user.User; @@ -221,44 +221,43 @@ public static void handleGuildResult(User user, InPacket inPacket) { public static void handleAllianceRequest(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceRequestType requestType = AllianceRequestType.getByValue(type); - + if (!user.hasGuild() || user.hasAlliance()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - return; - } - + user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. + return; + } + if (requestType == AllianceRequestType.Create) { - String allianceName = ""; - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.createNewAlliance(allianceName)); - // send CreateDone to alliance members + String allianceName = ""; + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.createNewAlliance(allianceName)); + // send CreateDone to alliance members } else if (requestType == AllianceRequestType.Withdraw) { - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); - // send Withdraw_Done to guild members - } else if (requestType == AllianceRequestType.Invite) { - if (user.getGuildRank() != GuildRank.MASTER) { + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); + // send Withdraw_Done to guild members + } else if (requestType == AllianceRequestType.Invite) { + if (user.getGuildRank() != GuildRank.MASTER) { user.write(GuildPacket.serverMsg("You cannot invite other guilds since you are not the master of the guild.")); return; - } - - if (false) { - // send InviteGuild_AlreadyInvited - return; + } + + if (false) { + // send InviteGuild_AlreadyInvited } } else if (requestType == AllianceRequestType.Join) { - if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { - // send InviteGuild_BlockedByOpt + if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { + // send InviteGuild_BlockedByOpt return; } - - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.joinAlliance(user.getAllianceId())); - // send Invite_Done + + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.joinAlliance(user.getAllianceId())); + // send Invite_Done } else if (requestType == AllianceRequestType.Destroy) { - if (!user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER) { - user.write(GuildPacket.serverMsg("You cannot destroy in alliance since you are not the master of the alliance.")); + if (!user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER) { + user.write(GuildPacket.serverMsg("You cannot destroy in alliance since you are not the master of the alliance.")); return; - } - - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); + } + + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); } } @@ -266,7 +265,7 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { public static void handleAllianceResult(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceResultType resultType = AllianceResultType.getByValue(type); - + //TODO } diff --git a/src/main/java/kinoko/packet/CentralPacket.java b/src/main/java/kinoko/packet/CentralPacket.java index 5aba02319..7a831b104 100644 --- a/src/main/java/kinoko/packet/CentralPacket.java +++ b/src/main/java/kinoko/packet/CentralPacket.java @@ -239,7 +239,7 @@ public static OutPacket guildResult(int characterId, GuildInfo guildInfo) { } return outPacket; } - + public static OutPacket allianceRequest(int characterId, AllianceRequest allianceRequest) { final OutPacket outPacket = OutPacket.of(CentralHeader.AllianceRequest); outPacket.encodeInt(characterId); diff --git a/src/main/java/kinoko/script/GuildHQ.java b/src/main/java/kinoko/script/GuildHQ.java index d88b3f513..3f21a4187 100644 --- a/src/main/java/kinoko/script/GuildHQ.java +++ b/src/main/java/kinoko/script/GuildHQ.java @@ -4,6 +4,8 @@ import kinoko.script.common.Script; import kinoko.script.common.ScriptHandler; import kinoko.script.common.ScriptManager; +import kinoko.server.Server; +import kinoko.server.alliance.Alliance; import kinoko.server.guild.GuildRank; import kinoko.server.guild.GuildRequest; import kinoko.world.GameConstants; @@ -11,10 +13,6 @@ import java.util.Map; import java.util.Optional; -import java.util.function.Predicate; - -import kinoko.server.Server; -import kinoko.server.alliance.Alliance; public final class GuildHQ extends ScriptHandler { public static final int GUILD_HEADQUARTERS = 200000301; @@ -160,28 +158,29 @@ public static void guild_union(ScriptManager sm) { sm.sayNext("Only the party leader can form a Guild Union."); return; } - if (!sm.checkParty(2, (user) -> {return user.getGuildRank() == GuildRank.MASTER;})) { + if (!sm.checkParty(2, (user) -> { + return user.getGuildRank() == GuildRank.MASTER; + })) { sm.sayNext("You can create a Guild Union if your party consists of two people."); } - + if (!sm.askYesNo(String.format("Oh, are you interested in forming a Guild Union? The current fee for this operation is #r%,d mesos#k.", GameConstants.CREATE_UNION_COST))) { sm.sayNext("You're not ready yet? Come back to me when you want to create an alliance."); return; } if (!sm.addMoney(-GameConstants.CREATE_UNION_COST)) { sm.sayNext("You don't have enough mesos for this request."); - return; } - + //TODO alliance submit request } else if (answer == 3) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { sm.sayNext("Only the Guild Union Master can expand the number of guilds in the Union."); } - + Optional alliance = Server.getCentralServerNode().getAllianceById(sm.getUser().getAllianceId()); - if(!alliance.isEmpty()) { - final int currentCapacity = alliance.get().getMemberMax(); + if (!alliance.isEmpty()) { + final int currentCapacity = alliance.get().getMemberMax(); if (currentCapacity >= GameConstants.UNION_CAPACITY_MAX) { sm.sayNext("Your alliance already reached the maximum capacity for guilds."); return; @@ -192,13 +191,11 @@ public static void guild_union(ScriptManager sm) { } if (!sm.addMoney(-expandCost)) { sm.sayNext("You don't have enough mesos for this request."); - return; } - + //TODO alliance submit request } else { - sm.sayNext("Your alliance is nonexistent."); - return; + sm.sayNext("Your alliance is nonexistent."); } } else if (answer == 4) { if (sm.getUser().getGuildRank() != GuildRank.MASTER) { diff --git a/src/main/java/kinoko/server/Server.java b/src/main/java/kinoko/server/Server.java index 8d1f6df37..5ee8b3f89 100644 --- a/src/main/java/kinoko/server/Server.java +++ b/src/main/java/kinoko/server/Server.java @@ -114,9 +114,9 @@ private static void shutdown() throws Exception { DatabaseManager.shutdown(); LogManager.shutdown(); } - + public static CentralServerNode getCentralServerNode() { - return centralServerNode; + return centralServerNode; } - + } diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 4b1135e4f..5675c17ec 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -1,37 +1,39 @@ package kinoko.server.alliance; +import kinoko.server.guild.Guild; +import kinoko.server.guild.GuildMember; +import kinoko.server.packet.OutPacket; +import kinoko.util.Encodable; +import kinoko.util.Lockable; +import kinoko.world.GameConstants; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import kinoko.server.guild.Guild; + import static kinoko.server.guild.Guild.EMPTY_MEMBER; import static kinoko.server.guild.Guild.MEMBER_COMPARATOR; -import kinoko.server.guild.GuildMember; -import kinoko.server.packet.OutPacket; -import kinoko.util.Encodable; -import kinoko.util.Lockable; -import kinoko.world.GameConstants; /** * Alliance instance managed by CentralServerNode. */ public final class Alliance implements Encodable, Lockable { - + private final int allianceId; private final String allianceName; private int allianceLordId; private final List gradeNames; private int memberMax; private String notice; - + private final Map guildInvites; // invitee ID -> inviter ID private final Map guilds; // guild ID -> guild - + private final Lock lock = new ReentrantLock(); - + public Alliance(int allianceId, String allianceName, int allianceLordId) { this.allianceId = allianceId; this.allianceName = allianceName; @@ -42,7 +44,7 @@ public Alliance(int allianceId, String allianceName, int allianceLordId) { this.memberMax = GameConstants.UNION_CAPACITY_MIN; this.notice = ""; } - + public int getMemberMax() { return memberMax; } @@ -58,26 +60,26 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - + public int getLordId() { return allianceLordId; } - + public void setLordId(int characterId) { allianceLordId = characterId; } - + public List getAllianceMembers() { List list = new ArrayList<>(100); - + for (Guild guild : guilds.values()) { list.addAll(guild.getGuildMembersUnsorted()); } - + return list.stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level .toList(); } - + public List getMemberIds() { return getAllianceMembers().stream().toList(); } @@ -94,7 +96,7 @@ public boolean hasMember(int characterId) { return true; } } - + return false; } @@ -105,10 +107,10 @@ public GuildMember getMember(int characterId) { return member; } } - + return EMPTY_MEMBER; } - + public boolean hasGuild(int guildId) { return guilds.containsKey(guildId); } @@ -123,45 +125,45 @@ public boolean canAddGuild(int guildId) { } return !guilds.containsKey(guildId); } - + public boolean addGuild(Guild guild) { - this.lock(); - try { - if (!canAddGuild(guild.getGuildId())) { + this.lock(); + try { + if (!canAddGuild(guild.getGuildId())) { return false; } guilds.put(guild.getGuildId(), guild); return true; - } finally { - this.unlock(); - } + } finally { + this.unlock(); + } } public void removeGuild(Guild guild) { - this.lock(); - try { - guilds.remove(guild.getGuildId()); - } finally { - this.unlock(); - } + this.lock(); + try { + guilds.remove(guild.getGuildId()); + } finally { + this.unlock(); + } } public String getGradeNames(int rank) { return gradeNames.get(rank - 1); } - + public String setGradeName(int rank, String name) { return gradeNames.set(rank - 1, name); } - + public String getNotice() { return notice; } - + public void setNotice(String str) { notice = str; } - + @Override public void encode(OutPacket outPacket) { // TODO @@ -176,5 +178,5 @@ public void lock() { public void unlock() { lock.unlock(); } - + } diff --git a/src/main/java/kinoko/server/alliance/AllianceRequest.java b/src/main/java/kinoko/server/alliance/AllianceRequest.java index a146188e7..7f3d28c32 100644 --- a/src/main/java/kinoko/server/alliance/AllianceRequest.java +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -16,12 +16,12 @@ public final class AllianceRequest implements Encodable { private String allianceNotice; private List gradeNames; private int memberMax; - + private int inviterId; private int targetId; private String targetName; private GuildRank allianceRank; - + public AllianceRequest(AllianceRequestType requestType) { this.requestType = requestType; } diff --git a/src/main/java/kinoko/server/command/Arguments.java b/src/main/java/kinoko/server/command/Arguments.java index 546df6778..17769e7dd 100644 --- a/src/main/java/kinoko/server/command/Arguments.java +++ b/src/main/java/kinoko/server/command/Arguments.java @@ -1,10 +1,6 @@ package kinoko.server.command; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * Annotation for command arguments. diff --git a/src/main/java/kinoko/server/guild/Guild.java b/src/main/java/kinoko/server/guild/Guild.java index a10c3a564..8937d2f31 100644 --- a/src/main/java/kinoko/server/guild/Guild.java +++ b/src/main/java/kinoko/server/guild/Guild.java @@ -77,7 +77,7 @@ public List getGuildMembers() { return guildMembers.values().stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level .toList(); } - + public List getGuildMembersUnsorted() { return guildMembers.values().stream().toList(); } diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index f6114d78f..f8468a402 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -8,7 +8,8 @@ import kinoko.packet.world.BroadcastPacket; import kinoko.packet.world.GuildPacket; import kinoko.packet.world.PartyPacket; -import kinoko.server.alliance.*; +import kinoko.server.alliance.Alliance; +import kinoko.server.alliance.AllianceRequest; import kinoko.server.guild.*; import kinoko.server.header.CentralHeader; import kinoko.server.memo.Memo; @@ -30,7 +31,6 @@ import kinoko.util.Util; import kinoko.world.GameConstants; import kinoko.world.user.GuildInfo; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -1052,7 +1052,7 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa } } } - + private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket inPacket) { final int characterId = inPacket.decodeInt(); final AllianceRequest allianceRequest = AllianceRequest.decode(inPacket); @@ -1066,103 +1066,103 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i final RemoteUser remoteUser = remoteUserResult.get(); switch (allianceRequest.getRequestType()) { case Create -> { - // Check if alliance name is available + // Check if alliance name is available if (!DatabaseManager.allianceAccessor().checkAllianceNameAvailable(allianceName)) { // TODO The name is already in use... Please try other ones.... return; } - - // Resolve new alliance ID + + // Resolve new alliance ID final Optional allianceIdResult = DatabaseManager.idAccessor().nextAllianceId(); if (allianceIdResult.isEmpty()) { - OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); log.error("Database error: allianceId"); return; } - + int allianceId = allianceIdResult.get(); - Optional alliance = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); - if (alliance.isEmpty()) { - OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); - - log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); + Optional alliance = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); + if (alliance.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + + log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); return; - } - - // Save to database + } + + // Save to database DatabaseManager.allianceAccessor().saveAlliance(alliance.get()); } case Invite -> { - + } case Join -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); - if (!target.isEmpty()) { - Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); - if (!guild.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - alliance.addGuild(guild.get()); - } finally { - alliance.unlock(); - } - - // Save to database + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.addGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database DatabaseManager.allianceAccessor().saveAlliance(alliance); - } - } - } + } + } + } } case Withdraw, Kick -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); - if (!target.isEmpty()) { - Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); - if (!guild.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - alliance.removeGuild(guild.get()); - } finally { - alliance.unlock(); - } - - // Save to database - DatabaseManager.allianceAccessor().saveAlliance(alliance); - } - } - } + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); + if (!target.isEmpty()) { + Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); + if (!guild.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + alliance.removeGuild(guild.get()); + } finally { + alliance.unlock(); + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } } case Destroy -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - centralServerNode.removeAlliance(alliance); - } finally { - alliance.unlock(); - } - } + Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); + if (!allianceOpt.isEmpty()) { + Alliance alliance = allianceOpt.get(); + alliance.lock(); + try { + centralServerNode.removeAlliance(alliance); + } finally { + alliance.unlock(); + } + } } case UpdateMemberCountMax -> { - + } case SetGradeName -> { - + } case ChangeGrade -> { - + } - + case SetNotice -> { - + } } } diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 21b362abf..703143dbe 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -23,7 +23,6 @@ import kinoko.server.party.PartyStorage; import kinoko.server.user.RemoteUser; import kinoko.server.user.UserStorage; -import kinoko.world.user.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -198,21 +197,21 @@ public Optional getGuildById(int guildId) { public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { final Alliance alliance = new Alliance(allianceId, allianceName, remoteUser.getCharacterId()); - + final GuildMember member = GuildMember.from(remoteUser); member.setGuildRank(GuildRank.MASTER); - + Optional guild = guildStorage.getGuildById(remoteUser.getGuildId()); if (!guild.isEmpty()) { - if (!alliance.addGuild(guild.get())) { + if (!alliance.addGuild(guild.get())) { throw new IllegalStateException("Could not add guild to alliance"); } - - if (allianceStorage.addAlliance(alliance)) { + + if (allianceStorage.addAlliance(alliance)) { return Optional.of(alliance); } } - + return Optional.empty(); } diff --git a/src/main/java/kinoko/server/node/ChannelServerNode.java b/src/main/java/kinoko/server/node/ChannelServerNode.java index 0149e3dc4..91d6a5aff 100644 --- a/src/main/java/kinoko/server/node/ChannelServerNode.java +++ b/src/main/java/kinoko/server/node/ChannelServerNode.java @@ -217,7 +217,7 @@ public void submitPartyRequest(User user, PartyRequest partyRequest) { public void submitGuildRequest(User user, GuildRequest guildRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.guildRequest(user.getCharacterId(), guildRequest)); } - + public void submitAllianceRequest(User user, AllianceRequest allianceRequest) { centralClientFuture.channel().writeAndFlush(CentralPacket.allianceRequest(user.getCharacterId(), allianceRequest)); } diff --git a/src/main/java/kinoko/server/user/RemoteUser.java b/src/main/java/kinoko/server/user/RemoteUser.java index 4c34df1a7..90fd46043 100644 --- a/src/main/java/kinoko/server/user/RemoteUser.java +++ b/src/main/java/kinoko/server/user/RemoteUser.java @@ -93,7 +93,7 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } - + public int getAllianceId() { return guildId; } diff --git a/src/main/java/kinoko/world/GameConstants.java b/src/main/java/kinoko/world/GameConstants.java index 6fd965820..ded739e22 100644 --- a/src/main/java/kinoko/world/GameConstants.java +++ b/src/main/java/kinoko/world/GameConstants.java @@ -211,7 +211,7 @@ public static int getGuildExpandCost(int memberMax) { return 5_000_000; } } - + public static int getUnionExpandCost(int memberMax) { if (memberMax < 3) { return 500_000; diff --git a/src/main/java/kinoko/world/user/CharacterData.java b/src/main/java/kinoko/world/user/CharacterData.java index 0fe4d7220..333ef02be 100644 --- a/src/main/java/kinoko/world/user/CharacterData.java +++ b/src/main/java/kinoko/world/user/CharacterData.java @@ -162,7 +162,7 @@ public int getGuildId() { public void setGuildId(int guildId) { this.guildId = guildId; } - + public int getAllianceId() { return guildId; } diff --git a/src/main/java/kinoko/world/user/GuildInfo.java b/src/main/java/kinoko/world/user/GuildInfo.java index c5556d423..137fc7acb 100644 --- a/src/main/java/kinoko/world/user/GuildInfo.java +++ b/src/main/java/kinoko/world/user/GuildInfo.java @@ -19,7 +19,7 @@ public final class GuildInfo implements Encodable { private final int allianceId; private final String allianceName; private final GuildRank allianceRank; - + public GuildInfo(int guildId, String guildName, GuildRank guildRank, int memberMax, short markBg, byte markBgColor, short mark, byte markColor, int allianceId, String allianceName, GuildRank allianceRank) { this.guildId = guildId; this.guildName = guildName; @@ -73,11 +73,11 @@ public int getAllianceId() { public String getAllianceName() { return allianceName; } - + public GuildRank getAllianceRank() { return allianceRank; } - + @Override public void encode(OutPacket outPacket) { outPacket.encodeInt(guildId); From 46bf53cc2a23f9a445a72b91b1f86bd7b05536a3 Mon Sep 17 00:00:00 2001 From: iw2d <30405132+iw2d@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:41:47 +1000 Subject: [PATCH 16/21] Fix some issues, implement packets and handling for basic alliance operations --- .../kinoko/database/AllianceAccessor.java | 1 - .../java/kinoko/database/GuildAccessor.java | 2 + .../cassandra/CassandraGuildAccessor.java | 19 +- .../database/cassandra/table/IdTable.java | 1 + .../kinoko/handler/user/GuildHandler.java | 100 ++-- .../kinoko/packet/world/AlliancePacket.java | 145 ++++++ .../java/kinoko/server/alliance/Alliance.java | 153 ++---- .../server/alliance/AllianceRequest.java | 140 +++--- .../server/alliance/AllianceResultType.java | 10 +- src/main/java/kinoko/server/guild/Guild.java | 4 +- .../kinoko/server/guild/GuildStorage.java | 83 +++- .../server/netty/CentralServerHandler.java | 461 ++++++++++++------ .../kinoko/server/node/CentralServerNode.java | 29 +- .../java/kinoko/server/user/RemoteUser.java | 17 +- 14 files changed, 716 insertions(+), 449 deletions(-) create mode 100644 src/main/java/kinoko/packet/world/AlliancePacket.java diff --git a/src/main/java/kinoko/database/AllianceAccessor.java b/src/main/java/kinoko/database/AllianceAccessor.java index 9050fef66..47c4d4980 100644 --- a/src/main/java/kinoko/database/AllianceAccessor.java +++ b/src/main/java/kinoko/database/AllianceAccessor.java @@ -14,5 +14,4 @@ public interface AllianceAccessor { boolean saveAlliance(Alliance alliance); boolean deleteAlliance(int allianceId); - } \ No newline at end of file diff --git a/src/main/java/kinoko/database/GuildAccessor.java b/src/main/java/kinoko/database/GuildAccessor.java index dc326191e..5557a82b6 100644 --- a/src/main/java/kinoko/database/GuildAccessor.java +++ b/src/main/java/kinoko/database/GuildAccessor.java @@ -9,6 +9,8 @@ public interface GuildAccessor { Optional getGuildById(int guildId); + Optional getGuildByName(String guildName); + boolean checkGuildNameAvailable(String name); boolean newGuild(Guild guild); diff --git a/src/main/java/kinoko/database/cassandra/CassandraGuildAccessor.java b/src/main/java/kinoko/database/cassandra/CassandraGuildAccessor.java index f8fd07a0f..c439b8cde 100644 --- a/src/main/java/kinoko/database/cassandra/CassandraGuildAccessor.java +++ b/src/main/java/kinoko/database/cassandra/CassandraGuildAccessor.java @@ -69,15 +69,28 @@ public Optional getGuildById(int guildId) { } @Override - public boolean checkGuildNameAvailable(String name) { + public Optional getGuildByName(String guildName) { final ResultSet selectResult = getSession().execute( selectFrom(getKeyspace(), GuildTable.getTableName()).all() - .whereColumn(GuildTable.GUILD_NAME_INDEX).isEqualTo(literal(lowerName(name))) + .whereColumn(GuildTable.GUILD_NAME_INDEX).isEqualTo(literal(lowerName(guildName))) + .build() + ); + for (Row row : selectResult) { + return Optional.of(loadGuild(row)); + } + return Optional.empty(); + } + + @Override + public boolean checkGuildNameAvailable(String guildName) { + final ResultSet selectResult = getSession().execute( + selectFrom(getKeyspace(), GuildTable.getTableName()).all() + .whereColumn(GuildTable.GUILD_NAME_INDEX).isEqualTo(literal(lowerName(guildName))) .build() ); for (Row row : selectResult) { final String existingName = row.getString(GuildTable.GUILD_NAME_INDEX); - if (existingName != null && existingName.equalsIgnoreCase(name)) { + if (existingName != null && existingName.equalsIgnoreCase(guildName)) { return false; } } diff --git a/src/main/java/kinoko/database/cassandra/table/IdTable.java b/src/main/java/kinoko/database/cassandra/table/IdTable.java index 3107be342..3f6231da0 100644 --- a/src/main/java/kinoko/database/cassandra/table/IdTable.java +++ b/src/main/java/kinoko/database/cassandra/table/IdTable.java @@ -41,6 +41,7 @@ public static void createTable(CqlSession session, String keyspace) { CHARACTER_ID, PARTY_ID, GUILD_ID, + ALLIANCE_ID, MEMO_ID )) { session.execute( diff --git a/src/main/java/kinoko/handler/user/GuildHandler.java b/src/main/java/kinoko/handler/user/GuildHandler.java index 87d27e32a..4e96cd52e 100644 --- a/src/main/java/kinoko/handler/user/GuildHandler.java +++ b/src/main/java/kinoko/handler/user/GuildHandler.java @@ -221,43 +221,62 @@ public static void handleGuildResult(User user, InPacket inPacket) { public static void handleAllianceRequest(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceRequestType requestType = AllianceRequestType.getByValue(type); - - if (!user.hasGuild() || user.hasAlliance()) { - user.write(GuildPacket.serverMsg(null)); // The guild request has not been accepted due to unknown reason. - return; - } - - if (requestType == AllianceRequestType.Create) { - String allianceName = ""; - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.createNewAlliance(allianceName)); - // send CreateDone to alliance members - } else if (requestType == AllianceRequestType.Withdraw) { - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); - // send Withdraw_Done to guild members - } else if (requestType == AllianceRequestType.Invite) { - if (user.getGuildRank() != GuildRank.MASTER) { - user.write(GuildPacket.serverMsg("You cannot invite other guilds since you are not the master of the guild.")); - return; + switch (requestType) { + case Load -> { + // CWvsContext::OnAllianceResult + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.load(user.getAllianceId())); } - - if (false) { - // send InviteGuild_AlreadyInvited + case Withdraw -> { + // CTabGuildAlliance::OnWithdraw + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.withdraw()); } - } else if (requestType == AllianceRequestType.Join) { - if (user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER || !user.hasGuild()) { - // send InviteGuild_BlockedByOpt - return; + case Invite -> { + // CTabGuildAlliance::OnInvite + final String guildName = inPacket.decodeString(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.invite(guildName)); } - - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.joinAlliance(user.getAllianceId())); - // send Invite_Done - } else if (requestType == AllianceRequestType.Destroy) { - if (!user.hasAlliance() || user.getGuildInfo().getGuildRank() != GuildRank.MASTER) { - user.write(GuildPacket.serverMsg("You cannot destroy in alliance since you are not the master of the alliance.")); - return; + case Join -> { + // CUIFadeYesNo::OnButtonClicked + final int inviterId = inPacket.decodeInt(); + final String guildName = inPacket.decodeString(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.join(inviterId, user.getGuildInfo().getGuildName())); + } + case Kick -> { + // CTabGuildAlliance::OnKick + final int guildId = inPacket.decodeInt(); + final int allianceId = inPacket.decodeInt(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.kick(guildId)); + } + case ChangeMaster -> { + // CTabGuildAlliance::OnChangeMaster + final int targetId = inPacket.decodeInt(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.changeMaster(user.getCharacterId(), targetId)); + } + case SetGradeName -> { + // CWndAllianceGrade::OnSaveGradeName + final List gradeNames = new ArrayList<>(); + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { + gradeNames.add(inPacket.decodeString()); + } + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.setGradeName(gradeNames)); + } + case ChangeGrade -> { + // CTabGuildAlliance::OnGradeChange + final int targetId = inPacket.decodeInt(); + final boolean gradeUp = inPacket.decodeBoolean(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.changeGrade(targetId, gradeUp)); + } + case SetNotice -> { + // CTabGuildAlliance::OnSetNotice + final String notice = inPacket.decodeString(); + user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.setNotice(notice)); + } + case null -> { + log.error("Unknown alliance request type : {}", type); + } + default -> { + log.error("Unhandled alliance request type : {}", requestType); } - - user.getConnectedServer().submitAllianceRequest(user, AllianceRequest.removeAlliance(user.getAllianceId())); } } @@ -265,8 +284,19 @@ public static void handleAllianceRequest(User user, InPacket inPacket) { public static void handleAllianceResult(User user, InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceResultType resultType = AllianceResultType.getByValue(type); - - //TODO + switch (resultType) { + case InviteGuild_BlockedByOpt, InviteGuild_AlreadyInvited, InviteGuild_Rejected -> { + final String inviterName = inPacket.decodeString(); + final String guildName = inPacket.decodeString(); + // TODO + } + case null -> { + log.error("Unknown alliance result type : {}", type); + } + default -> { + log.error("Unhandled alliance result type : {}", resultType); + } + } } @Handler(InHeader.GuildBBS) diff --git a/src/main/java/kinoko/packet/world/AlliancePacket.java b/src/main/java/kinoko/packet/world/AlliancePacket.java new file mode 100644 index 000000000..dc406b961 --- /dev/null +++ b/src/main/java/kinoko/packet/world/AlliancePacket.java @@ -0,0 +1,145 @@ +package kinoko.packet.world; + +import kinoko.server.alliance.Alliance; +import kinoko.server.alliance.AllianceRequestType; +import kinoko.server.alliance.AllianceResultType; +import kinoko.server.guild.Guild; +import kinoko.server.guild.GuildMember; +import kinoko.server.header.OutHeader; +import kinoko.server.packet.OutPacket; +import kinoko.world.GameConstants; + +import java.util.List; + +public final class AlliancePacket { + // CWvsContext::OnAllianceResult ----------------------------------------------------------------------------------- + + public static OutPacket invite(int inviterId, String inviterName) { + final OutPacket outPacket = OutPacket.of(OutHeader.AllianceResult); + outPacket.encodeByte(AllianceRequestType.Invite.getValue()); + outPacket.encodeInt(inviterId); // dwInviterID + outPacket.encodeString(inviterName); // sInviter + outPacket.encodeString(""); // not used + return outPacket; + } + + public static OutPacket loadDone(Alliance alliance) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.LoadDone); + outPacket.encodeByte(alliance != null); + if (alliance != null) { + alliance.encode(outPacket); // ALLIANCEDATA::Decode + } + return outPacket; + } + + public static OutPacket loadGuildDone(List guilds) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.LoadGuildDone); + outPacket.encodeInt(guilds.size()); + for (Guild guild : guilds) { + guild.encode(outPacket); // GUILDDATA::Decode + } + return outPacket; + } + + public static OutPacket notifyLoginOrLogout(int allianceId, int guildId, int characterId, boolean online) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.NotifyLoginOrLogout); + outPacket.encodeInt(allianceId); + outPacket.encodeInt(guildId); + outPacket.encodeInt(characterId); + outPacket.encodeByte(online); + return outPacket; + } + + public static OutPacket createDone(Alliance alliance, List guilds) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.CreateDone); + alliance.encode(outPacket); // ALLIANCEDATA::Decode + for (Guild guild : guilds) { + guild.encode(outPacket); // GUILDDATA::Decode + } + return outPacket; + } + + public static OutPacket withdrawDone(Alliance alliance, Guild guild, boolean kicked) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.Withdraw_Done); + alliance.encode(outPacket); // ALLIANCEDATA::Decode + outPacket.encodeInt(guild.getGuildId()); // nGuildID + guild.encode(outPacket); // GUILDDATA::Decode + outPacket.encodeByte(kicked); // You have been kicked from the alliance. | You have left the alliance. + return outPacket; + } + + public static OutPacket inviteDone(Alliance alliance, Guild guild) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.Invite_Done); + alliance.encode(outPacket); // ALLIANCEDATA::Decode + outPacket.encodeInt(guild.getGuildId()); // nGuildID + guild.encode(outPacket); // GUILDDATA::Decode + return outPacket; + } + + public static OutPacket updateAllianceInfo(Alliance alliance) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.UpdateAllianceInfo); + alliance.encode(outPacket); // ALLIANCEDATA::Decode + return outPacket; + } + + public static OutPacket changeLevelOrJob(int allianceId, int guildId, int characterId, int level, int job) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.ChangeLevelOrJob); + outPacket.encodeInt(allianceId); + outPacket.encodeInt(guildId); + outPacket.encodeInt(characterId); + outPacket.encodeInt(level); + outPacket.encodeInt(job); + return outPacket; + } + + public static OutPacket changeMasterDone(int allianceId, int oldMasterId, int newMasterId) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.ChangeLevelOrJob); + outPacket.encodeInt(allianceId); + outPacket.encodeInt(oldMasterId); + outPacket.encodeInt(newMasterId); + return outPacket; + } + + public static OutPacket setGradeNameDone(int allianceId, List gradeNames) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.SetGradeName_Done); + outPacket.encodeInt(allianceId); + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { + outPacket.encodeString(gradeNames.get(i)); + } + return outPacket; + } + + public static OutPacket changeGradeDone(GuildMember member) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.SetGradeName_Done); + outPacket.encodeInt(member.getCharacterId()); + outPacket.encodeByte(member.getAllianceRank().getValue()); // nAllianceGrade + return outPacket; + } + + public static OutPacket setNoticeDone(int allianceId, String notice) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.SetNotice_Done); + outPacket.encodeInt(allianceId); + outPacket.encodeString(notice); + return outPacket; + } + + public static OutPacket destroyDone(int allianceId) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.Destroy_Done); + outPacket.encodeInt(allianceId); + return outPacket; + } + + public static OutPacket updateGuildInfo(Guild guild) { + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.UpdateGuildInfo); + outPacket.encodeInt(guild.getAllianceId()); // nAllianceID + outPacket.encodeInt(guild.getGuildId()); // nGuildID + guild.encode(outPacket); // GUILDDATA::Decode + return outPacket; + } + + private static OutPacket of(AllianceResultType resultType) { + final OutPacket outPacket = OutPacket.of(OutHeader.AllianceResult); + outPacket.encodeByte(resultType.getValue()); + return outPacket; + } +} diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 5675c17ec..b145697e3 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -1,56 +1,34 @@ package kinoko.server.alliance; -import kinoko.server.guild.Guild; -import kinoko.server.guild.GuildMember; import kinoko.server.packet.OutPacket; import kinoko.util.Encodable; import kinoko.util.Lockable; import kinoko.world.GameConstants; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import static kinoko.server.guild.Guild.EMPTY_MEMBER; -import static kinoko.server.guild.Guild.MEMBER_COMPARATOR; - /** * Alliance instance managed by CentralServerNode. */ public final class Alliance implements Encodable, Lockable { - + private final Lock lock = new ReentrantLock(); private final int allianceId; private final String allianceName; - private int allianceLordId; private final List gradeNames; + private final List guilds; + private final Map guildInvites; // invitee ID -> inviter ID private int memberMax; private String notice; - private final Map guildInvites; // invitee ID -> inviter ID - private final Map guilds; // guild ID -> guild - - private final Lock lock = new ReentrantLock(); - - public Alliance(int allianceId, String allianceName, int allianceLordId) { + public Alliance(int allianceId, String allianceName) { this.allianceId = allianceId; this.allianceName = allianceName; - this.allianceLordId = allianceLordId; - this.gradeNames = new ArrayList<>(GameConstants.GUILD_GRADE_NAMES); + this.gradeNames = GameConstants.GUILD_GRADE_NAMES; + this.guilds = new ArrayList<>(); this.guildInvites = new HashMap<>(); - this.guilds = new HashMap<>(); this.memberMax = GameConstants.UNION_CAPACITY_MIN; - this.notice = ""; - } - - public int getMemberMax() { - return memberMax; - } - - public void setMemberMax(int memberMax) { - this.memberMax = memberMax; } public int getAllianceId() { @@ -61,112 +39,59 @@ public String getAllianceName() { return allianceName; } - public int getLordId() { - return allianceLordId; - } - - public void setLordId(int characterId) { - allianceLordId = characterId; - } - - public List getAllianceMembers() { - List list = new ArrayList<>(100); - - for (Guild guild : guilds.values()) { - list.addAll(guild.getGuildMembersUnsorted()); - } - - return list.stream().sorted(MEMBER_COMPARATOR) // sort by rank, then level - .toList(); - } - - public List getMemberIds() { - return getAllianceMembers().stream().toList(); - } - - public List getMemberIds(int exceptId) { - return getAllianceMembers().stream() - .filter((member) -> member.getCharacterId() != exceptId) - .toList(); - } - - public boolean hasMember(int characterId) { - for (Guild guild : guilds.values()) { - if (guild.hasMember(characterId)) { - return true; - } - } - - return false; - } - - public GuildMember getMember(int characterId) { - for (Guild guild : guilds.values()) { - GuildMember member = guild.getMember(characterId); - if (member != EMPTY_MEMBER) { - return member; - } - } - - return EMPTY_MEMBER; - } - - public boolean hasGuild(int guildId) { - return guilds.containsKey(guildId); - } - - public Guild getGuild(int guildId) { - return guilds.get(guildId); - } - public boolean canAddGuild(int guildId) { if (guilds.size() >= getMemberMax()) { return false; } - return !guilds.containsKey(guildId); + return !guilds.contains(guildId); } - public boolean addGuild(Guild guild) { - this.lock(); - try { - if (!canAddGuild(guild.getGuildId())) { - return false; - } - guilds.put(guild.getGuildId(), guild); - return true; - } finally { - this.unlock(); + public boolean addGuild(int guildId) { + if (canAddGuild(guildId)) { + return false; } + guilds.add(guildId); + return true; } - public void removeGuild(Guild guild) { - this.lock(); - try { - guilds.remove(guild.getGuildId()); - } finally { - this.unlock(); - } + public boolean removeGuild(int guildId) { + return guilds.remove((Integer) guildId); + } + + public List getGuilds() { + return Collections.unmodifiableList(guilds); } - public String getGradeNames(int rank) { - return gradeNames.get(rank - 1); + public void registerInvite(int inviterId, int targetId) { + guildInvites.put(targetId, inviterId); } - public String setGradeName(int rank, String name) { - return gradeNames.set(rank - 1, name); + public boolean unregisterInvite(int inviterId, int targetId) { + return guildInvites.remove(targetId) == inviterId; } - public String getNotice() { - return notice; + public int getMemberMax() { + return memberMax; } - public void setNotice(String str) { - notice = str; + public void setMemberMax(int memberMax) { + this.memberMax = memberMax; } @Override public void encode(OutPacket outPacket) { - // TODO + // ALLIANCEDATA::Decode + outPacket.encodeInt(allianceId); // nAllianceID + outPacket.encodeString(allianceName); // sAllianceName + for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + outPacket.encodeString(gradeNames.get(i)); // asGradeName + } + outPacket.encodeByte(guilds.size()); // adwGuildID + for (int guildId : guilds) { + outPacket.encodeInt(guildId); + } + outPacket.encodeInt(memberMax); // nMaxMemberNum + outPacket.encodeString(notice); } @Override diff --git a/src/main/java/kinoko/server/alliance/AllianceRequest.java b/src/main/java/kinoko/server/alliance/AllianceRequest.java index 7f3d28c32..7f5652553 100644 --- a/src/main/java/kinoko/server/alliance/AllianceRequest.java +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -1,6 +1,5 @@ package kinoko.server.alliance; -import kinoko.server.guild.GuildRank; import kinoko.server.packet.InPacket; import kinoko.server.packet.OutPacket; import kinoko.util.Encodable; @@ -12,15 +11,16 @@ public final class AllianceRequest implements Encodable { private final AllianceRequestType requestType; private int allianceId; - private String allianceName; - private String allianceNotice; - private List gradeNames; - private int memberMax; + private int guildId; + private String guildName; private int inviterId; + private int oldMasterId; + private int newMasterId; + private List gradeNames; private int targetId; - private String targetName; - private GuildRank allianceRank; + private boolean gradeUp; + private String notice; public AllianceRequest(AllianceRequestType requestType) { this.requestType = requestType; @@ -34,70 +34,76 @@ public int getAllianceId() { return allianceId; } - public String getAllianceName() { - return allianceName; + public int getGuildId() { + return guildId; } - public String getAllianceNotice() { - return allianceNotice; + public String getGuildName() { + return guildName; } - public List getGradeNames() { - return gradeNames; + public int getInviterId() { + return inviterId; } - public int getMemberMax() { - return memberMax; + public int getOldMasterId() { + return oldMasterId; } - public int getInviterId() { - return inviterId; + public int getNewMasterId() { + return newMasterId; + } + + public List getGradeNames() { + return gradeNames; } public int getTargetId() { return targetId; } - public String getTargetName() { - return targetName; + public boolean isGradeUp() { + return gradeUp; } - public GuildRank getAllianceRank() { - return allianceRank; + public String getNotice() { + return notice; } @Override public void encode(OutPacket outPacket) { outPacket.encodeByte(requestType.getValue()); switch (requestType) { - case Create -> { + case Load -> { outPacket.encodeInt(allianceId); - outPacket.encodeString(allianceName); + } + case Withdraw -> { } case Invite -> { - outPacket.encodeString(targetName); + outPacket.encodeString(guildName); } case Join -> { outPacket.encodeInt(inviterId); + outPacket.encodeString(guildName); } case Kick -> { - outPacket.encodeInt(targetId); - outPacket.encodeString(targetName); + outPacket.encodeInt(guildId); } - case UpdateMemberCountMax -> { - outPacket.encodeInt(memberMax); + case ChangeMaster -> { + outPacket.encodeInt(oldMasterId); + outPacket.encodeInt(newMasterId); } case SetGradeName -> { - for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { outPacket.encodeString(gradeNames.get(i)); } } case ChangeGrade -> { outPacket.encodeInt(targetId); - outPacket.encodeByte(allianceRank.getValue()); + outPacket.encodeByte(gradeUp); } case SetNotice -> { - outPacket.encodeString(allianceNotice); + outPacket.encodeString(notice); } } } @@ -106,38 +112,37 @@ public static AllianceRequest decode(InPacket inPacket) { final int type = inPacket.decodeByte(); final AllianceRequest request = new AllianceRequest(AllianceRequestType.getByValue(type)); switch (request.requestType) { - case Load, Destroy, Withdraw -> { + case Load -> { request.allianceId = inPacket.decodeInt(); } - case Create -> { - request.allianceId = inPacket.decodeInt(); - request.allianceName = inPacket.decodeString(); + case Withdraw -> { } case Invite -> { - request.targetName = inPacket.decodeString(); + request.guildName = inPacket.decodeString(); } case Join -> { request.inviterId = inPacket.decodeInt(); + request.guildName = inPacket.decodeString(); } case Kick -> { - request.targetId = inPacket.decodeInt(); - request.targetName = inPacket.decodeString(); + request.guildId = inPacket.decodeInt(); } - case UpdateMemberCountMax -> { - request.memberMax = inPacket.decodeInt(); + case ChangeMaster -> { + request.oldMasterId = inPacket.decodeInt(); + request.newMasterId = inPacket.decodeInt(); } case SetGradeName -> { request.gradeNames = new ArrayList<>(); - for (int i = 0; i < GameConstants.GUILD_GRADE_MAX; i++) { + for (int i = 0; i < GameConstants.UNION_GRADE_MAX; i++) { request.gradeNames.add(inPacket.decodeString()); } } case ChangeGrade -> { request.targetId = inPacket.decodeInt(); - request.allianceRank = GuildRank.getByValue(inPacket.decodeByte()); + request.gradeUp = inPacket.decodeBoolean(); } case SetNotice -> { - request.allianceNotice = inPacket.decodeString(); + request.notice = inPacket.decodeString(); } case null -> { throw new IllegalStateException(String.format("Unknown alliance request type %d", type)); @@ -149,71 +154,58 @@ public static AllianceRequest decode(InPacket inPacket) { return request; } - public static AllianceRequest loadAlliance(int allianceId) { + public static AllianceRequest load(int allianceId) { final AllianceRequest request = new AllianceRequest(AllianceRequestType.Load); request.allianceId = allianceId; return request; } - public static AllianceRequest createNewAlliance(String allianceName) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.Create); - request.allianceName = allianceName; - return request; + public static AllianceRequest withdraw() { + return new AllianceRequest(AllianceRequestType.Withdraw); } - public static AllianceRequest inviteAlliance(String targetName) { + public static AllianceRequest invite(String guildName) { final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); - request.targetName = targetName; + request.guildName = guildName; return request; } - public static AllianceRequest joinAlliance(int inviterId) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.Join); + public static AllianceRequest join(int inviterId, String guildName) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); request.inviterId = inviterId; + request.guildName = guildName; return request; } - public static AllianceRequest withdrawAlliance(int allianceId) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.Withdraw); - request.allianceId = allianceId; - return request; - } - - public static AllianceRequest kickAlliance(int targetId, String targetName) { + public static AllianceRequest kick(int guildId) { final AllianceRequest request = new AllianceRequest(AllianceRequestType.Kick); - request.targetId = targetId; - request.targetName = targetName; + request.guildId = guildId; return request; } - public static AllianceRequest removeAlliance(int allianceId) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.Destroy); - request.allianceId = allianceId; - return request; - } - - public static AllianceRequest incMaxMemberNum(int memberMax) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.UpdateMemberCountMax); - request.memberMax = memberMax; + public static AllianceRequest changeMaster(int oldMasterId, int newMasterId) { + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Invite); + request.oldMasterId = oldMasterId; + request.newMasterId = newMasterId; return request; } public static AllianceRequest setGradeName(List gradeNames) { - final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetGradeName); + final AllianceRequest request = new AllianceRequest(AllianceRequestType.Kick); request.gradeNames = gradeNames; return request; } - public static AllianceRequest setMemberGrade(int targetId, GuildRank allianceRank) { + public static AllianceRequest changeGrade(int targetId, boolean gradeUp) { final AllianceRequest request = new AllianceRequest(AllianceRequestType.ChangeGrade); request.targetId = targetId; - request.allianceRank = allianceRank; + request.gradeUp = gradeUp; return request; } public static AllianceRequest setNotice(String notice) { final AllianceRequest request = new AllianceRequest(AllianceRequestType.SetNotice); - request.allianceNotice = notice; + request.notice = notice; return request; } } diff --git a/src/main/java/kinoko/server/alliance/AllianceResultType.java b/src/main/java/kinoko/server/alliance/AllianceResultType.java index 9b10d48d7..593d9326c 100644 --- a/src/main/java/kinoko/server/alliance/AllianceResultType.java +++ b/src/main/java/kinoko/server/alliance/AllianceResultType.java @@ -3,16 +3,16 @@ public enum AllianceResultType { // AllianceRes LoadDone(12), - LoadallianceDone(13), + LoadGuildDone(13), NotifyLoginOrLogout(14), CreateDone(15), Withdraw_Done(16), Withdraw_Failed(17), Invite_Done(18), Invite_Failed(19), - Invitealliance_BlockedByOpt(20), - Invitealliance_AlreadyInvited(21), - Invitealliance_Rejected(22), + InviteGuild_BlockedByOpt(20), + InviteGuild_AlreadyInvited(21), + InviteGuild_Rejected(22), UpdateAllianceInfo(23), ChangeLevelOrJob(24), ChangeMaster_Done(25), @@ -20,7 +20,7 @@ public enum AllianceResultType { ChangeGrade_Done(27), SetNotice_Done(28), Destroy_Done(29), - UpdateallianceInfo(30); + UpdateGuildInfo(30); private final int value; diff --git a/src/main/java/kinoko/server/guild/Guild.java b/src/main/java/kinoko/server/guild/Guild.java index 8937d2f31..91cc56a58 100644 --- a/src/main/java/kinoko/server/guild/Guild.java +++ b/src/main/java/kinoko/server/guild/Guild.java @@ -78,8 +78,8 @@ public List getGuildMembers() { .toList(); } - public List getGuildMembersUnsorted() { - return guildMembers.values().stream().toList(); + public Optional getGuildMaster() { + return guildMembers.values().stream().filter((member) -> member.getGuildRank() == GuildRank.MASTER).findFirst(); } public List getBoardEntries() { diff --git a/src/main/java/kinoko/server/guild/GuildStorage.java b/src/main/java/kinoko/server/guild/GuildStorage.java index 6ec8d53c2..ef9da17a2 100644 --- a/src/main/java/kinoko/server/guild/GuildStorage.java +++ b/src/main/java/kinoko/server/guild/GuildStorage.java @@ -2,40 +2,87 @@ import kinoko.database.DatabaseManager; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; public final class GuildStorage { - private final ConcurrentHashMap guildMap = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Map mapByGuildId = new HashMap<>(); + private final Map mapByGuildName = new HashMap<>(); public boolean addGuild(Guild guild) { - if (guildMap.containsKey(guild.getGuildId())) { - return false; + lock.lock(); + try { + if (mapByGuildId.containsKey(guild.getGuildId())) { + return false; + } + if (mapByGuildName.containsKey(guild.getGuildName())) { + return false; + } + if (!DatabaseManager.guildAccessor().newGuild(guild)) { + return false; + } + mapByGuildId.put(guild.getGuildId(), guild); + mapByGuildName.put(guild.getGuildName(), guild); + return true; + } finally { + lock.unlock(); } - if (!DatabaseManager.guildAccessor().newGuild(guild)) { - return false; - } - guildMap.put(guild.getGuildId(), guild); - return true; } public boolean removeGuild(Guild guild) { - if (!DatabaseManager.guildAccessor().deleteGuild(guild.getGuildId())) { - return false; + lock.lock(); + try { + if (!DatabaseManager.guildAccessor().deleteGuild(guild.getGuildId())) { + return false; + } + mapByGuildId.remove(guild.getGuildId()); + mapByGuildName.remove(guild.getGuildName()); + return true; + } finally { + lock.unlock(); } - guildMap.remove(guild.getGuildId()); - return true; } public Optional getGuildById(int guildId) { if (guildId == 0) { return Optional.empty(); } - if (guildMap.containsKey(guildId)) { - return Optional.of(guildMap.get(guildId)); + lock.lock(); + try { + if (mapByGuildId.containsKey(guildId)) { + return Optional.of(mapByGuildId.get(guildId)); + } + final Optional guildResult = DatabaseManager.guildAccessor().getGuildById(guildId); + if (guildResult.isPresent()) { + final Guild guild = guildResult.get(); + mapByGuildId.put(guild.getGuildId(), guild); + mapByGuildName.put(guild.getGuildName(), guild); + } + return guildResult; + } finally { + lock.unlock(); + } + } + + public Optional getGuildByName(String guildName) { + lock.lock(); + try { + if (mapByGuildName.containsKey(guildName)) { + return Optional.of(mapByGuildId.get(guildName)); + } + final Optional guildResult = DatabaseManager.guildAccessor().getGuildByName(guildName); + if (guildResult.isPresent()) { + final Guild guild = guildResult.get(); + mapByGuildId.put(guild.getGuildId(), guild); + mapByGuildName.put(guild.getGuildName(), guild); + } + return guildResult; + } finally { + lock.unlock(); } - final Optional guildResult = DatabaseManager.guildAccessor().getGuildById(guildId); - guildResult.ifPresent(guild -> guildMap.put(guildId, guild)); - return guildResult; } } diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index f8468a402..faf7960dc 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -5,6 +5,7 @@ import kinoko.database.DatabaseManager; import kinoko.packet.CentralPacket; import kinoko.packet.field.MessengerPacket; +import kinoko.packet.world.AlliancePacket; import kinoko.packet.world.BroadcastPacket; import kinoko.packet.world.GuildPacket; import kinoko.packet.world.PartyPacket; @@ -669,115 +670,119 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa final Optional guildResult = centralServerNode.getGuildById(guildId); if (guildResult.isEmpty()) { remoteUser.setGuildId(0); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.loadGuildDone(null))); + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.loadGuildDone(null))); return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); - if (!guild.hasMember(remoteUser.getCharacterId())) { + if (!guild.hasMember(characterId)) { remoteUser.setGuildId(0); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.loadGuildDone(null))); + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.loadGuildDone(null))); return; } guild.updateMember(remoteUser); remoteUser.setGuildId(guild.getGuildId()); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), GuildInfo.from(guild, remoteUser.getCharacterId()))); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.loadGuildDone(guild))); + remoteServerNode.write(CentralPacket.guildResult(characterId, GuildInfo.from(guild, characterId))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.loadGuildDone(guild))); } } case CreateNewGuild -> { // Create new guild in storage + database final Optional guildResult = centralServerNode.createNewGuild(guildRequest.getGuildId(), guildRequest.getGuildName(), remoteUser); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.createNewGuildUnknown())); // The problem has happened during the process of forming the guild... Plese try again later.. + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.createNewGuildUnknown())); // The problem has happened during the process of forming the guild... Plese try again later.. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); remoteUser.setGuildId(guild.getGuildId()); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), GuildInfo.from(guild, remoteUser.getCharacterId()))); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.createNewGuildDone(guild))); + remoteServerNode.write(CentralPacket.guildResult(characterId, GuildInfo.from(guild, characterId))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.createNewGuildDone(guild))); } } case InviteGuild -> { // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. return; } // Resolve target final String targetName = guildRequest.getTargetName(); final Optional targetResult = centralServerNode.getUserByCharacterName(targetName); // target name if (targetResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg(String.format("Unable to find '%s'", targetName)))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Unable to find '%s'", targetName)))); return; } final RemoteUser target = targetResult.get(); if (target.getGuildId() != 0) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg(String.format("'%s' is already in a guild.", targetName)))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("'%s' is already in a guild.", targetName)))); return; } // Resolve target node final Optional targetNodeResult = centralServerNode.getChannelServerNodeById(target.getChannelId()); if (targetNodeResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg(String.format("Unable to find '%s'", targetName)))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Unable to find '%s'", targetName)))); return; } final RemoteServerNode targetNode = targetNodeResult.get(); try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check requester rank - final GuildRank guildRank = guild.getMember(remoteUser.getCharacterId()).getGuildRank(); + final GuildRank guildRank = guild.getMember(characterId).getGuildRank(); if (guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); return; } // Check capacity if (!guild.canAddMember(target.getCharacterId())) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You cannot invite more people to the guild."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You cannot invite more people to the guild."))); return; } // Register invite and send guild invite to target - guild.registerInvite(remoteUser.getCharacterId(), target.getCharacterId()); + guild.registerInvite(characterId, target.getCharacterId()); targetNodeResult.get().write(CentralPacket.userPacketReceive(target.getCharacterId(), GuildPacket.inviteGuild(remoteUser))); } } case JoinGuild -> { + if (remoteUser.getGuildId() != 0) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You have already joined a guild."))); + return; + } // Resolve inviter final int inviterId = guildRequest.getInviterId(); final Optional inviterResult = centralServerNode.getUserByCharacterId(inviterId); if (inviterResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. return; } final RemoteUser inviter = inviterResult.get(); // Resolve guild final Optional guildResult = centralServerNode.getGuildById(inviter.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check if user has been invited by the inviter - if (!guild.unregisterInvite(inviterId, remoteUser.getCharacterId())) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. + if (!guild.unregisterInvite(inviterId, characterId)) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.joinGuildUnknown())); // The guild request has not been accepted due to unknown reason. return; } // Try adding user as a member to the guild final GuildMember newMember = GuildMember.from(remoteUser); newMember.setGuildRank(GuildRank.MEMBER3); if (!guild.addMember(newMember)) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.joinGuildAlreadyFull())); // The guild you are trying to join has already reached the max number of users. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.joinGuildAlreadyFull())); // The guild you are trying to join has already reached the max number of users. return; } // Update user remoteUser.setGuildId(guild.getGuildId()); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), GuildInfo.from(guild, remoteUser.getCharacterId()))); + remoteServerNode.write(CentralPacket.guildResult(characterId, GuildInfo.from(guild, characterId))); // Update members final OutPacket outPacket = GuildPacket.joinGuildDone(guild.getGuildId(), newMember); forEachGuildMember(guild, (member, node) -> { @@ -791,20 +796,20 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(guildRequest.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.withdrawGuildUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.withdrawGuildUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check if user can leave the guild - if (!guild.hasMember(remoteUser.getCharacterId())) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.withdrawGuildNotJoined())); // You are not in the guild. + if (!guild.hasMember(characterId)) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.withdrawGuildNotJoined())); // You are not in the guild. return; } - final GuildMember removedMember = guild.getMember(remoteUser.getCharacterId()); + final GuildMember removedMember = guild.getMember(characterId); if (removedMember.getGuildRank() == GuildRank.MASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You cannot quit the guild since you are the master of the guild."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You cannot quit the guild since you are the master of the guild."))); return; } // Remove user from guild @@ -812,8 +817,8 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Update members final OutPacket outPacket = GuildPacket.withdrawGuildDone(guild.getGuildId(), removedMember); remoteUser.setGuildId(0); - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, outPacket)); forEachGuildMember(guild, (member, node) -> { node.write(CentralPacket.userPacketReceive(member.getCharacterId(), outPacket)); }); @@ -825,29 +830,29 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.kickGuildUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.kickGuildUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check requester rank - final GuildRank guildRank = guild.getMember(remoteUser.getCharacterId()).getGuildRank(); + final GuildRank guildRank = guild.getMember(characterId).getGuildRank(); if (guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); return; } // Check if target can be kicked if (!guild.hasMember(guildRequest.getTargetId())) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg(String.format("Unable to find '%s'", guildRequest.getTargetName())))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Unable to find '%s'", guildRequest.getTargetName())))); return; } final GuildMember targetMember = guild.getMember(guildRequest.getTargetId()); if (targetMember.getGuildRank() == GuildRank.MASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You may not ban the Guild Master."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You may not ban the Guild Master."))); return; } if (targetMember.getGuildRank() == GuildRank.SUBMASTER && guildRank != GuildRank.MASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("Only Guild Master can expel Jr.Masters."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Only Guild Master can expel Jr.Masters."))); return; } // Remove target from guild @@ -889,20 +894,20 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(guildRequest.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.guildResult(remoteUser.getCharacterId(), null)); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.removeGuildUnknown())); // The problem has happened during the process of disbanding the guild... Plese try again later... + remoteServerNode.write(CentralPacket.guildResult(characterId, null)); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.removeGuildUnknown())); // The problem has happened during the process of disbanding the guild... Plese try again later... return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check requester rank - if (guild.getMember(remoteUser.getCharacterId()).getGuildRank() != GuildRank.MASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild."))); + if (guild.getMember(characterId).getGuildRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); return; } // Remove guild from storage + database if (!centralServerNode.removeGuild(guild)) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.removeGuildUnknown())); // The problem has happened during the process of disbanding the guild... Plese try again later... + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.removeGuildUnknown())); // The problem has happened during the process of disbanding the guild... Plese try again later... return; } // Update members @@ -918,19 +923,19 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.incMaxMemberNumUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.incMaxMemberNumUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Check requester rank - if (guild.getMember(remoteUser.getCharacterId()).getGuildRank() != GuildRank.MASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild."))); + if (guild.getMember(characterId).getGuildRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); return; } // Check that max member can be increased if (guild.getMemberMax() >= GameConstants.GUILD_CAPACITY_MAX || guild.getMemberMax() >= guildRequest.getMemberMax()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.incMaxMemberNumUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.incMaxMemberNumUnknown())); // The guild request has not been accepted due to unknown reason. return; } // Set guild capacity @@ -949,7 +954,7 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setGradeNameUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setGradeNameUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { @@ -968,28 +973,28 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { final Guild guild = lockedGuild.get(); // Resolve target if (!guild.hasMember(guildRequest.getTargetId())) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. return; } final GuildMember targetMember = guild.getMember(guildRequest.getTargetId()); // Check if requester can modify target's rank if (targetMember.getGuildRank() == GuildRank.MASTER || guildRequest.getGuildRank() == GuildRank.MASTER || guildRequest.getGuildRank() == GuildRank.NONE) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setMemberGradeUnknown())); // The guild request has not been accepted due to unknown reason. return; } - final GuildRank requesterRank = guild.getMember(remoteUser.getCharacterId()).getGuildRank(); + final GuildRank requesterRank = guild.getMember(characterId).getGuildRank(); if ((requesterRank != GuildRank.MASTER && requesterRank != GuildRank.SUBMASTER) || (requesterRank != GuildRank.MASTER && targetMember.getGuildRank() == GuildRank.SUBMASTER) || (requesterRank != GuildRank.MASTER && guildRequest.getGuildRank() == GuildRank.SUBMASTER)) { // Non-master/submaster trying to modify rank; Non-master trying to modify rank of submaster; Non-master trying to modify rank to submaster - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); return; } // Update rank @@ -1011,7 +1016,7 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setMarkUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setMarkUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { @@ -1035,7 +1040,7 @@ private void handleGuildRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.setMarkUnknown())); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.setMarkUnknown())); // The guild request has not been accepted due to unknown reason. return; } try (var lockedGuild = guildResult.get().acquire()) { @@ -1062,108 +1067,220 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i log.error("Failed to resolve user with character ID : {} for AllianceRequest", characterId); return; } - String allianceName = allianceRequest.getAllianceName(); final RemoteUser remoteUser = remoteUserResult.get(); + // Resolve guild + final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); + if (guildResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not in a guild yet."))); + return; + } + final Guild guild = guildResult.get(); switch (allianceRequest.getRequestType()) { - case Create -> { - // Check if alliance name is available - if (!DatabaseManager.allianceAccessor().checkAllianceNameAvailable(allianceName)) { - // TODO The name is already in use... Please try other ones.... + case Load -> { + // Load alliance from storage / database + final int allianceId = allianceRequest.getAllianceId() != 0 ? allianceRequest.getAllianceId() : guild.getAllianceId(); + final Optional allianceResult = centralServerNode.getAllianceById(allianceId); + if (allianceResult.isEmpty()) { + remoteServerNode.write(CentralPacket.guildResult(characterId, GuildInfo.from(guild, characterId))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, AlliancePacket.loadDone(null))); return; } - - // Resolve new alliance ID - final Optional allianceIdResult = DatabaseManager.idAccessor().nextAllianceId(); - if (allianceIdResult.isEmpty()) { - OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); - log.error("Database error: allianceId"); - return; + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, AlliancePacket.loadDone(alliance))); + final List guilds = new ArrayList<>(); + for (int guildId : alliance.getGuilds()) { + centralServerNode.getGuildById(guildId).ifPresent(guilds::add); + } + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, AlliancePacket.loadGuildDone(guilds))); } - - int allianceId = allianceIdResult.get(); - Optional alliance = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); - if (alliance.isEmpty()) { - OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); - - log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); + } + case Withdraw -> { + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance to withdraw from"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. return; } - - // Save to database - DatabaseManager.allianceAccessor().saveAlliance(alliance.get()); + // Remove guild from alliance + try (var lockedGuild = guild.acquire()) { + final GuildMember leader = guild.getMember(characterId); + if (leader == null || leader.getGuildRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + if (!alliance.removeGuild(guild.getGuildId())) { + log.error("Could not remove guild from alliance"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + guild.setAllianceId(0); + // Update clients + final OutPacket withdrawPacket = AlliancePacket.withdrawDone(alliance, guild, false); + forEachGuildMember(guild, (member, node) -> { + node.write(CentralPacket.guildResult(member.getCharacterId(), GuildInfo.from(guild, member.getCharacterId()))); + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + }); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + }); + // Save to database + DatabaseManager.guildAccessor().saveGuild(guild); + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } } case Invite -> { - - } - case Join -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); - if (!target.isEmpty()) { - Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); - if (!guild.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - alliance.addGuild(guild.get()); - } finally { - alliance.unlock(); - } - - // Save to database - DatabaseManager.allianceAccessor().saveAlliance(alliance); + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for invite"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + // Resolve target guild + final Optional targetGuildResult = centralServerNode.getGuildByName(allianceRequest.getGuildName()); + if (targetGuildResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Could not invite guild \"%s\" to the alliance.", allianceRequest.getGuildName())))); + return; } + final Guild targetGuild = targetGuildResult.get(); + if (targetGuild.getAllianceId() != 0 || !alliance.canAddGuild(targetGuild.getGuildId())) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Could not invite guild \"%s\" to the alliance.", allianceRequest.getGuildName())))); + return; + } + // Resolve target user + final Optional targetResult = targetGuild.getGuildMaster().flatMap((member) -> centralServerNode.getUserByCharacterId(member.getCharacterId())); + if (targetResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Unable to find the master of the guild."))); + return; + } + final RemoteUser target = targetResult.get(); + final Optional targetNodeResult = centralServerNode.getChannelServerNodeById(target.getChannelId()); + if (targetNodeResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Unable to find the master of the guild."))); + return; + } + // Register invite and send alliance invite to target + alliance.registerInvite(characterId, target.getCharacterId()); + targetNodeResult.get().write(CentralPacket.userPacketReceive(target.getCharacterId(), AlliancePacket.invite(characterId, remoteUser.getCharacterName()))); } } } - case Withdraw, Kick -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Optional target = centralServerNode.getUserByCharacterId(allianceRequest.getTargetId()); - if (!target.isEmpty()) { - Optional guild = centralServerNode.getGuildById(target.get().getGuildId()); - if (!guild.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - alliance.removeGuild(guild.get()); - } finally { - alliance.unlock(); - } - - // Save to database - DatabaseManager.allianceAccessor().saveAlliance(alliance); + case Join -> { + // Resolve inviter + final int inviterId = allianceRequest.getInviterId(); + final Optional inviterResult = centralServerNode.getUserByCharacterId(inviterId); + if (inviterResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + final RemoteUser inviter = inviterResult.get(); + // Resolve alliance + final Optional inviterGuildResult = centralServerNode.getGuildById(inviter.getGuildId()); + if (inviterGuildResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + final Guild inviterGuild = inviterGuildResult.get(); + final Optional allianceResult = centralServerNode.getAllianceById(inviterGuild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for join"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + // Check if user has been invited by the inviter + if (!alliance.unregisterInvite(inviterId, characterId)) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + // Try adding guild to alliance + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild."))); + return; + } + if (guild.getAllianceId() != 0) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You have already joined an alliance."))); + return; } + if (!alliance.addGuild(guild.getGuildId())) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + guild.setAllianceId(alliance.getAllianceId()); + master.setAllianceRank(GuildRank.SUBMASTER); + // Update members + final OutPacket inviteDonePacket = AlliancePacket.inviteDone(alliance, guild); + forEachAllianceMember(alliance, (member, node) -> { + if (member.getGuildId() == guild.getGuildId()) { + node.write(CentralPacket.guildResult(member.getCharacterId(), GuildInfo.from(guild, member.getCharacterId()))); + } + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), inviteDonePacket)); + }); + // Save to database + DatabaseManager.guildAccessor().saveGuild(guild); + DatabaseManager.allianceAccessor().saveAlliance(alliance); } } } - case Destroy -> { - Optional allianceOpt = centralServerNode.getAllianceById(allianceRequest.getAllianceId()); - if (!allianceOpt.isEmpty()) { - Alliance alliance = allianceOpt.get(); - alliance.lock(); - try { - centralServerNode.removeAlliance(alliance); - } finally { - alliance.unlock(); + case Kick -> { + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for kick"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + // Resolve and target guild from alliance + final Optional targetGuildResult = centralServerNode.getGuildById(allianceRequest.getGuildId()); + if (targetGuildResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + final Guild targetGuild = targetGuildResult.get(); + if (targetGuild.getGuildId() == guild.getGuildId() || !alliance.removeGuild(targetGuild.getGuildId())) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(String.format("Cannot kick guild \"%s\" from the alliance.", targetGuild.getGuildName())))); + return; + } + targetGuild.setAllianceId(0); + // Update clients + final OutPacket withdrawPacket = AlliancePacket.withdrawDone(alliance, targetGuild, true); + forEachGuildMember(targetGuild, (member, node) -> { + node.write(CentralPacket.guildResult(member.getCharacterId(), GuildInfo.from(targetGuild, member.getCharacterId()))); + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + }); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + }); + // Save to database + DatabaseManager.guildAccessor().saveGuild(targetGuild); + DatabaseManager.allianceAccessor().saveAlliance(alliance); } } } - case UpdateMemberCountMax -> { - - } - case SetGradeName -> { - - } - case ChangeGrade -> { - - } - - case SetNotice -> { - - } } } @@ -1180,7 +1297,7 @@ private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve guild final Optional guildResult = centralServerNode.getGuildById(remoteUser.getGuildId()); if (guildResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not in a guild yet."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not in a guild yet."))); return; } try (var lockedGuild = guildResult.get().acquire()) { @@ -1192,12 +1309,12 @@ private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPa // Resolve entry final Optional entryResult = guild.getBoardEntry(boardRequest.getEntryId()); if (entryResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.boardEntryNotFound())); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.boardEntryNotFound())); return; } final GuildBoardEntry entry = entryResult.get(); - if (entry.getCharacterId() != remoteUser.getCharacterId() && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You cannot edit this entry."))); + if (entry.getCharacterId() != characterId && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You cannot edit this entry."))); return; } // Modify entry @@ -1209,18 +1326,18 @@ private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPa if (boardRequest.isNotice()) { // Check if notice can be created if (guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You are not the master of the guild yet."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the guild yet."))); return; } if (guild.getBoardNoticeEntry() != null) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("Please delete the current notice and then try again."))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Please delete the current notice and then try again."))); return; } } // Create board entry final GuildBoardEntry entry = new GuildBoardEntry( guild.getNextBoardEntryId(), - remoteUser.getCharacterId(), + characterId, boardRequest.getTitle(), boardRequest.getText(), Instant.now(), @@ -1231,62 +1348,62 @@ private void handleBoardRequest(RemoteServerNode remoteServerNode, InPacket inPa } else { guild.addBoardEntry(entry); } - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.viewEntryResult(entry))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.viewEntryResult(entry))); } } case Delete -> { final Optional entryResult = guild.getBoardEntry(boardRequest.getEntryId()); if (entryResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.boardEntryNotFound())); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.boardEntryNotFound())); return; } - if (entryResult.get().getCharacterId() != remoteUser.getCharacterId() && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You cannot delete this entry."))); + if (entryResult.get().getCharacterId() != characterId && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You cannot delete this entry."))); return; } guild.removeBoardEntry(boardRequest.getEntryId()); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.loadEntryListResult(guild.getBoardNoticeEntry(), guild.getBoardEntryList(0), guild.getBoardEntries().size()))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.loadEntryListResult(guild.getBoardNoticeEntry(), guild.getBoardEntryList(0), guild.getBoardEntries().size()))); } case LoadListRequest -> { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.loadEntryListResult(guild.getBoardNoticeEntry(), guild.getBoardEntryList(boardRequest.getStart()), guild.getBoardEntries().size()))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.loadEntryListResult(guild.getBoardNoticeEntry(), guild.getBoardEntryList(boardRequest.getStart()), guild.getBoardEntries().size()))); } case ViewEntryRequest -> { final Optional entryResult = guild.getBoardEntry(boardRequest.getEntryId()); if (entryResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.boardEntryNotFound())); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.boardEntryNotFound())); return; } - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.viewEntryResult(entryResult.get()))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.viewEntryResult(entryResult.get()))); } case RegisterComment -> { final Optional entryResult = guild.getBoardEntry(boardRequest.getEntryId()); if (entryResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.boardEntryNotFound())); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.boardEntryNotFound())); return; } final GuildBoardEntry entry = entryResult.get(); entry.addComment(new GuildBoardComment( entry.getNextCommentSn(), - remoteUser.getCharacterId(), + characterId, boardRequest.getText(), Instant.now() )); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.viewEntryResult(entry))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.viewEntryResult(entry))); } case DeleteComment -> { final Optional entryResult = guild.getBoardEntry(boardRequest.getEntryId()); if (entryResult.isEmpty()) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.boardEntryNotFound())); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.boardEntryNotFound())); return; } final GuildBoardEntry entry = entryResult.get(); final Optional commentResult = entry.getComment(boardRequest.getCommentSn()); - if (commentResult.isEmpty() || (commentResult.get().getCharacterId() != remoteUser.getCharacterId() && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER)) { - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.serverMsg("You cannot delete this comment."))); + if (commentResult.isEmpty() || (commentResult.get().getCharacterId() != characterId && guildRank != GuildRank.MASTER && guildRank != GuildRank.SUBMASTER)) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You cannot delete this comment."))); return; } entry.removeComment(boardRequest.getCommentSn()); - remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), GuildPacket.viewEntryResult(entry))); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.viewEntryResult(entry))); } } } @@ -1419,9 +1536,31 @@ private void forEachGuildMember(Guild guild, BiConsumer targetNodeResult = centralServerNode.getChannelServerNodeById(member.getChannelId()); if (targetNodeResult.isEmpty()) { - return; + continue; } biConsumer.accept(member, targetNodeResult.get()); } } + + private void forEachAllianceMember(Alliance alliance, BiConsumer biConsumer) { + for (int guildId : alliance.getGuilds()) { + final Optional guildResult = centralServerNode.getGuildById(guildId); + if (guildResult.isEmpty()) { + continue; + } + final Guild guild = guildResult.get(); + for (int memberId : guild.getMemberIds()) { + final Optional remoteMemberResult = centralServerNode.getUserByCharacterId(memberId); + if (remoteMemberResult.isEmpty()) { + continue; + } + final RemoteUser member = remoteMemberResult.get(); + final Optional targetNodeResult = centralServerNode.getChannelServerNodeById(member.getChannelId()); + if (targetNodeResult.isEmpty()) { + continue; + } + biConsumer.accept(member, targetNodeResult.get()); + } + } + } } diff --git a/src/main/java/kinoko/server/node/CentralServerNode.java b/src/main/java/kinoko/server/node/CentralServerNode.java index 703143dbe..3b3241c1d 100644 --- a/src/main/java/kinoko/server/node/CentralServerNode.java +++ b/src/main/java/kinoko/server/node/CentralServerNode.java @@ -187,32 +187,21 @@ public boolean removeGuild(Guild guild) { } public Optional getGuildById(int guildId) { - if (guildId == 0) { - return Optional.empty(); - } return guildStorage.getGuildById(guildId); } - // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - - public Optional createNewAlliance(int allianceId, String allianceName, RemoteUser remoteUser) { - final Alliance alliance = new Alliance(allianceId, allianceName, remoteUser.getCharacterId()); - - final GuildMember member = GuildMember.from(remoteUser); - member.setGuildRank(GuildRank.MASTER); + public Optional getGuildByName(String guildName) { + return guildStorage.getGuildByName(guildName); + } - Optional guild = guildStorage.getGuildById(remoteUser.getGuildId()); - if (!guild.isEmpty()) { - if (!alliance.addGuild(guild.get())) { - throw new IllegalStateException("Could not add guild to alliance"); - } + // ALLIANCE METHODS --------------------------------------------------------------------------------------------------- - if (allianceStorage.addAlliance(alliance)) { - return Optional.of(alliance); - } + public Optional createNewAlliance(int allianceId, String allianceName) { + final Alliance alliance = new Alliance(allianceId, allianceName); + if (!allianceStorage.addAlliance(alliance)) { + return Optional.empty(); } - - return Optional.empty(); + return Optional.of(alliance); } public boolean removeAlliance(Alliance alliance) { diff --git a/src/main/java/kinoko/server/user/RemoteUser.java b/src/main/java/kinoko/server/user/RemoteUser.java index 90fd46043..ba8971452 100644 --- a/src/main/java/kinoko/server/user/RemoteUser.java +++ b/src/main/java/kinoko/server/user/RemoteUser.java @@ -16,10 +16,9 @@ public final class RemoteUser implements Encodable { private int messengerId; private int partyId; private int guildId; - private int allianceId; private RemoteTownPortal townPortal; - public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, int allianceId, RemoteTownPortal townPortal) { + public RemoteUser(int accountId, int characterId, String characterName, int level, int job, int channelId, int fieldId, int messengerId, int partyId, int guildId, RemoteTownPortal townPortal) { this.accountId = accountId; this.characterId = characterId; this.characterName = characterName; @@ -30,7 +29,6 @@ public RemoteUser(int accountId, int characterId, String characterName, int leve this.messengerId = messengerId; this.partyId = partyId; this.guildId = guildId; - this.allianceId = allianceId; this.townPortal = townPortal; } @@ -94,14 +92,6 @@ public void setGuildId(int guildId) { this.guildId = guildId; } - public int getAllianceId() { - return guildId; - } - - public void setAllianceId(int allianceId) { - this.allianceId = allianceId; - } - public RemoteTownPortal getTownPortal() { return townPortal != null ? townPortal : RemoteTownPortal.EMPTY; } @@ -123,7 +113,6 @@ public String toString() { ", messengerId=" + messengerId + ", partyId=" + partyId + ", guildId=" + guildId + - ", allianceId=" + allianceId + ", townPortal=" + townPortal + '}'; } @@ -140,7 +129,6 @@ public void encode(OutPacket outPacket) { outPacket.encodeInt(messengerId); outPacket.encodeInt(partyId); outPacket.encodeInt(guildId); - outPacket.encodeInt(allianceId); outPacket.encodeByte(townPortal != null); if (townPortal != null) { townPortal.encode(outPacket); @@ -158,7 +146,6 @@ public static RemoteUser decode(InPacket inPacket) { final int messengerId = inPacket.decodeInt(); final int partyId = inPacket.decodeInt(); final int guildId = inPacket.decodeInt(); - final int allianceId = inPacket.decodeInt(); final RemoteTownPortal townPortal = inPacket.decodeBoolean() ? RemoteTownPortal.decode(inPacket) : null; return new RemoteUser( accountId, @@ -171,7 +158,6 @@ public static RemoteUser decode(InPacket inPacket) { messengerId, partyId, guildId, - allianceId, townPortal ); } @@ -188,7 +174,6 @@ public static RemoteUser from(User user) { user.getMessengerId(), user.getCharacterData().getPartyId(), user.getCharacterData().getGuildId(), - user.getCharacterData().getAllianceId(), user.getTownPortal() != null ? RemoteTownPortal.from(user.getTownPortal()) : null ); } From d235afd97562738c0cea7f3555b7d7c0a3dc8560 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Dec 2025 11:06:57 -0300 Subject: [PATCH 17/21] Update Alliance.java Clear inverse logic. --- src/main/java/kinoko/server/alliance/Alliance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/kinoko/server/alliance/Alliance.java b/src/main/java/kinoko/server/alliance/Alliance.java index 1362cc70c..d9a843a27 100644 --- a/src/main/java/kinoko/server/alliance/Alliance.java +++ b/src/main/java/kinoko/server/alliance/Alliance.java @@ -165,7 +165,7 @@ public void setNotice(String str) { } public boolean addGuild(int guildId, Guild guild) { - if (canAddGuild(guildId)) { + if (!canAddGuild(guildId)) { return false; } guilds.put(guildId, guild); From a2902d4bf606e1b992fa183ee7f25fc932d06b57 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Dec 2025 12:23:07 -0300 Subject: [PATCH 18/21] ChangeGrade and Destroy alliances Implement ChangeGrade and Destroy alliances. --- .../server/netty/CentralServerHandler.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index f81ceaa2f..e75f9f0fd 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -1281,6 +1281,103 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i } } } + + case ChangeGrade -> { + int targetId = allianceRequest.getTargetId(); + boolean gradeUp = allianceRequest.isGradeUp(); + + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for ChangeGrade"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + + GuildMember targetMember = null; + try (var lockedGuild = guild.acquire()) { + targetMember = guild.getMember(targetId); + + int newRank = targetMember.getAllianceRank().getValue() + (gradeUp ? -1 : 1); + newRank = Math.clamp(newRank, GuildRank.MASTER.getValue(), GuildRank.MEMBER3.getValue()); + + targetMember.setAllianceRank(GuildRank.getByValue(newRank)); + } + + if (targetMember != null) { + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + final OutPacket noticePacket = AlliancePacket.changeGradeDone(targetMember); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), noticePacket)); + }); + } + } + } + + case SetNotice -> { + String notice = allianceRequest.getNotice(); + + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for SetNotice"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + + alliance.setNotice(notice); + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + + final OutPacket noticePacket = AlliancePacket.setNoticeDone(alliance.getAllianceId(), notice); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), noticePacket)); + }); + } + } + } + + case Destroy -> { + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for destroy"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + + // Update clients + final OutPacket withdrawPacket = AlliancePacket.withdrawDone(alliance, guild, true); + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + }); + for (int guildId : alliance.getGuilds()) { + final Optional targetGuildResult = centralServerNode.getGuildById(guildId); + if (targetGuildResult.isEmpty()) { + continue; + } + + final Guild targetGuild = targetGuildResult.get(); + targetGuild.setAllianceId(0); + + // Save to database + DatabaseManager.guildAccessor().saveGuild(targetGuild); + } + + DatabaseManager.allianceAccessor().deleteAlliance(alliance.getAllianceId()); + } + } } } From 67cff318e5a109bd26005016bde85723f1ee0bcc Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Dec 2025 14:11:32 -0300 Subject: [PATCH 19/21] Set grade names for alliance Implemented central server node handling for set grade names on alliances. --- .../kinoko/packet/world/AlliancePacket.java | 4 +- .../server/alliance/AllianceRequest.java | 4 + .../server/alliance/AllianceResultType.java | 2 +- .../server/netty/CentralServerHandler.java | 100 +++++++++++++++++- 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/main/java/kinoko/packet/world/AlliancePacket.java b/src/main/java/kinoko/packet/world/AlliancePacket.java index 7bd9744b8..fecf1adb2 100644 --- a/src/main/java/kinoko/packet/world/AlliancePacket.java +++ b/src/main/java/kinoko/packet/world/AlliancePacket.java @@ -33,7 +33,7 @@ public static OutPacket loadDone(Alliance alliance) { } public static OutPacket loadGuildDone(List guilds) { - final OutPacket outPacket = AlliancePacket.of(AllianceResultType.LoadallianceDone); + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.LoadAllianceDone); outPacket.encodeInt(guilds.size()); for (Guild guild : guilds) { guild.encode(outPacket); // GUILDDATA::Decode @@ -110,7 +110,7 @@ public static OutPacket setGradeNameDone(int allianceId, List gradeNames } public static OutPacket changeGradeDone(GuildMember member) { - final OutPacket outPacket = AlliancePacket.of(AllianceResultType.SetGradeName_Done); + final OutPacket outPacket = AlliancePacket.of(AllianceResultType.ChangeGrade_Done); outPacket.encodeInt(member.getCharacterId()); outPacket.encodeByte(member.getAllianceRank().getValue()); // nAllianceGrade return outPacket; diff --git a/src/main/java/kinoko/server/alliance/AllianceRequest.java b/src/main/java/kinoko/server/alliance/AllianceRequest.java index 026ce75f3..2cc9d3561 100644 --- a/src/main/java/kinoko/server/alliance/AllianceRequest.java +++ b/src/main/java/kinoko/server/alliance/AllianceRequest.java @@ -65,6 +65,10 @@ public boolean isGradeUp() { public String getNotice() { return notice; } + + public List getGradeNames() { + return gradeNames; + } @Override public void encode(OutPacket outPacket) { diff --git a/src/main/java/kinoko/server/alliance/AllianceResultType.java b/src/main/java/kinoko/server/alliance/AllianceResultType.java index aeacc96c2..b61af5797 100644 --- a/src/main/java/kinoko/server/alliance/AllianceResultType.java +++ b/src/main/java/kinoko/server/alliance/AllianceResultType.java @@ -3,7 +3,7 @@ public enum AllianceResultType { // AllianceRes LoadDone(12), - LoadallianceDone(13), + LoadAllianceDone(13), NotifyLoginOrLogout(14), CreateDone(15), Withdraw_Done(16), diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index e75f9f0fd..a411ec14b 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -1074,8 +1074,71 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not in a guild yet."))); return; } + final Guild guild = guildResult.get(); switch (allianceRequest.getRequestType()) { + case Create -> { + String allianceName = allianceRequest.getGuildName(); + // Check if alliance name is available + if (!DatabaseManager.allianceAccessor().checkAllianceNameAvailable(allianceName)) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Alliance Name is already in use. Please use another one."))); + return; + } + + int otherGuildId = allianceRequest.getGuildId(); + final Optional otherGuildResult = centralServerNode.getGuildById(otherGuildId); + + if (otherGuildResult.isEmpty()) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Player not in a guild."))); + return; + } + + if (guild.getGuildId() == otherGuildId) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("Cannot form an alliance for the same guild."))); + return; + } + + // Resolve new alliance ID + final Optional allianceIdResult = DatabaseManager.idAccessor().nextAllianceId(); + if (allianceIdResult.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + log.error("Database error: allianceId"); + return; + } + + int allianceId = allianceIdResult.get(); + Optional allianceResult = centralServerNode.createNewAlliance(allianceId, allianceName, remoteUser); + if (allianceResult.isEmpty()) { + OutPacket outPacket = GuildPacket.serverMsg(null); // The guild request has not been accepted due to unknown reason. + remoteServerNode.write(CentralPacket.userPacketReceive(remoteUser.getCharacterId(), outPacket)); + + log.error("Could not create alliance '{}' for user '{}'", allianceName, remoteUser.getCharacterName()); + return; + } + + Alliance alliance = allianceResult.get(); + Guild otherGuild = otherGuildResult.get(); + if (!alliance.addGuild(otherGuild)) { + log.error("Could not add guild '{}' into alliance '{}' for user '{}'", otherGuild.getGuildName(), allianceName, remoteUser.getCharacterName()); + return; + } + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + + List guilds = new ArrayList<>(2); + guilds.add(guild); + guilds.add(otherGuild); + + // Update clients + final OutPacket createPacket = AlliancePacket.createDone(alliance, guilds); + + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), createPacket)); + }); + } + case Load -> { // Load alliance from storage / database final int allianceId = allianceRequest.getAllianceId() != 0 ? allianceRequest.getAllianceId() : guild.getAllianceId(); @@ -1282,6 +1345,35 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i } } + case SetGradeName -> { + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for kick"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + + List gradeNames = allianceRequest.getGradeNames(); + final OutPacket gradeNamesPacket = AlliancePacket.setGradeNameDone(alliance.getAllianceId(), gradeNames); + + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), gradeNamesPacket)); + }); + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + case ChangeGrade -> { int targetId = allianceRequest.getTargetId(); boolean gradeUp = allianceRequest.isGradeUp(); @@ -1307,9 +1399,9 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i if (targetMember != null) { try (var lockedAlliance = allianceResult.get().acquire()) { final Alliance alliance = lockedAlliance.get(); - final OutPacket noticePacket = AlliancePacket.changeGradeDone(targetMember); + final OutPacket changeGradePacket = AlliancePacket.changeGradeDone(targetMember); forEachAllianceMember(alliance, (member, node) -> { - node.write(CentralPacket.userPacketReceive(member.getCharacterId(), noticePacket)); + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), changeGradePacket)); }); } } @@ -1358,9 +1450,9 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i final Alliance alliance = lockedAlliance.get(); // Update clients - final OutPacket withdrawPacket = AlliancePacket.withdrawDone(alliance, guild, true); + final OutPacket destroyPacket = AlliancePacket.destroyDone(alliance.getAllianceId()); forEachAllianceMember(alliance, (member, node) -> { - node.write(CentralPacket.userPacketReceive(member.getCharacterId(), withdrawPacket)); + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), destroyPacket)); }); for (int guildId : alliance.getGuilds()) { final Optional targetGuildResult = centralServerNode.getGuildById(guildId); From f9620b44f68f502ea32697c14167882f40b28371 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Dec 2025 14:23:23 -0300 Subject: [PATCH 20/21] Update member count in alliances Implemented central server node handling for update member count for alliances. --- .../server/netty/CentralServerHandler.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index a411ec14b..e6c713145 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -1345,11 +1345,42 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i } } + case UpdateMemberCountMax -> { + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for updateMemberCountMax"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + + if (alliance.getMemberMax() >= GameConstants.UNION_CAPACITY_MAX) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("The alliance already reached maximum capacity."))); + return; + } + + int count = Math.min(alliance.getMemberMax() + 1, GameConstants.UNION_CAPACITY_MAX); + alliance.setMemberMax(count); + + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + case SetGradeName -> { // Resolve alliance final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); if (allianceResult.isEmpty()) { - log.error("Could not resolve alliance for kick"); + log.error("Could not resolve alliance for setGradeName"); remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. return; } From a7e836e04777721c26b00a7b16305bfcd13110a0 Mon Sep 17 00:00:00 2001 From: Ronan Lana Date: Tue, 23 Dec 2025 14:32:29 -0300 Subject: [PATCH 21/21] ChangeMaster in alliances Implemented central server node handling for change master in alliances. --- .../server/netty/CentralServerHandler.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/kinoko/server/netty/CentralServerHandler.java b/src/main/java/kinoko/server/netty/CentralServerHandler.java index e6c713145..228cef059 100644 --- a/src/main/java/kinoko/server/netty/CentralServerHandler.java +++ b/src/main/java/kinoko/server/netty/CentralServerHandler.java @@ -1376,6 +1376,36 @@ private void handleAllianceRequest(RemoteServerNode remoteServerNode, InPacket i } } + case ChangeMaster -> { + // Resolve alliance + final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId()); + if (allianceResult.isEmpty()) { + log.error("Could not resolve alliance for changeMaster"); + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg(null))); // The guild request has not been accepted due to unknown reason. + return; + } + try (var lockedAlliance = allianceResult.get().acquire()) { + final Alliance alliance = lockedAlliance.get(); + try (var lockedGuild = guild.acquire()) { + final GuildMember master = guild.getMember(characterId); + if (master == null || master.getGuildRank() != GuildRank.MASTER || master.getAllianceRank() != GuildRank.MASTER) { + remoteServerNode.write(CentralPacket.userPacketReceive(characterId, GuildPacket.serverMsg("You are not the master of the alliance."))); + return; + } + + int oldMasterId = characterId; + int newMasterId = allianceRequest.getTargetId(); + final OutPacket gradeNamesPacket = AlliancePacket.changeMasterDone(alliance.getAllianceId(), oldMasterId, newMasterId); + + forEachAllianceMember(alliance, (member, node) -> { + node.write(CentralPacket.userPacketReceive(member.getCharacterId(), gradeNamesPacket)); + }); + // Save to database + DatabaseManager.allianceAccessor().saveAlliance(alliance); + } + } + } + case SetGradeName -> { // Resolve alliance final Optional allianceResult = centralServerNode.getAllianceById(guild.getAllianceId());